diff --git a/scripts/BITMAP_REFERENCE.md b/scripts/BITMAP_REFERENCE.md new file mode 100644 index 00000000..1d081162 --- /dev/null +++ b/scripts/BITMAP_REFERENCE.md @@ -0,0 +1,299 @@ +# Proven Periods Bitmap - Technical Reference + +This document explains the bitmap storage system used for tracking proving periods in the FilecoinWarmStorageService contract. + +## Overview + +The contract uses an optimized bitmap storage mechanism to efficiently track which proving periods have been successfully proven. Instead of storing one boolean per period (which would consume one storage slot per period), the contract packs 256 periods into a single `uint256` storage slot. + +## Storage Structure + +```solidity +mapping(uint256 dataSetId => mapping(uint256 bucketId => uint256)) private provenPeriods; +``` + +- **Outer mapping**: Maps each dataset ID to its own proving history +- **Inner mapping**: Maps bucket IDs to uint256 values (each holds 256 bits) +- **Each bit**: Represents whether a specific period was proven (1) or faulted (0) + +## Bit Manipulation + +### Setting a Period as Proven + +```solidity +provenPeriods[dataSetId][periodId >> 8] |= (1 << (periodId & 255)); +``` + +**Breaking this down:** + +1. **`periodId >> 8`** - Right shift by 8 bits (divide by 256) + - This calculates which bucket to use + - Example: Period 1000 → Bucket 3 (1000 ÷ 256 = 3) + +2. **`periodId & 255`** - Bitwise AND with 255 (modulo 256) + - This calculates which bit within the bucket + - Example: Period 1000 → Bit 232 (1000 % 256 = 232) + +3. **`1 << (periodId & 255)`** - Left shift 1 by the bit position + - Creates a bitmask with only that bit set + - Example: For bit 232 → `0x...0100000000000000000000000000000000000000000000000000000000000000` + +4. **`|=`** - Bitwise OR assignment + - Sets the bit to 1 without affecting other bits + - Preserves all existing bits in the bucket + +### Checking if a Period is Proven + +```solidity +function _isPeriodProven(uint256 dataSetId, uint256 periodId) private view returns (bool) { + return provenPeriods[dataSetId][periodId >> 8] & (1 << (periodId & 255)) != 0; +} +``` + +**Breaking this down:** + +1. **Get the bucket**: `provenPeriods[dataSetId][periodId >> 8]` +2. **Create bitmask**: `1 << (periodId & 255)` +3. **Bitwise AND**: Checks if the specific bit is set +4. **Compare to 0**: Returns true if bit is set, false otherwise + +## Examples + +### Example 1: Period 0 + +``` +periodId = 0 + +Bucket calculation: 0 >> 8 = 0 +Bit position: 0 & 255 = 0 +Bitmask: 1 << 0 = 0x0000...0001 + +Storage location: provenPeriods[dataSetId][0] +Bit position: 0 (rightmost bit) +``` + +### Example 2: Period 255 + +``` +periodId = 255 + +Bucket calculation: 255 >> 8 = 0 +Bit position: 255 & 255 = 255 +Bitmask: 1 << 255 = 0x8000...0000 + +Storage location: provenPeriods[dataSetId][0] +Bit position: 255 (leftmost bit) +``` + +### Example 3: Period 256 + +``` +periodId = 256 + +Bucket calculation: 256 >> 8 = 1 +Bit position: 256 & 255 = 0 +Bitmask: 1 << 0 = 0x0000...0001 + +Storage location: provenPeriods[dataSetId][1] +Bit position: 0 (rightmost bit of second bucket) +``` + +### Example 4: Period 1000 + +``` +periodId = 1000 + +Bucket calculation: 1000 >> 8 = 3 +Bit position: 1000 & 255 = 232 +Bitmask: 1 << 232 = 0x0100000000000000000000000000000000000000000000000000000000000000 + +Storage location: provenPeriods[dataSetId][3] +Bit position: 232 +``` + +## Visualization + +### Bucket Layout + +``` +Bucket 0: Periods 0-255 +┌────────────────────────────────────────────────────────┐ +│ [255][254]...[2][1][0] │ +│ ↑ ↑ ↑ ↑ ↑ │ +│ Bit Bit Bit Bit Bit │ +│ 255 254 2 1 0 │ +└────────────────────────────────────────────────────────┘ + +Bucket 1: Periods 256-511 +┌────────────────────────────────────────────────────────┐ +│ [511][510]...[258][257][256] │ +└────────────────────────────────────────────────────────┘ + +Bucket 2: Periods 512-767 +Bucket 3: Periods 768-1023 +... and so on +``` + +### Single uint256 Bucket Example + +``` +uint256 value = provenPeriods[dataSetId][0] + +Bit layout (256 bits total): +Position: 255 254 253 ... 003 002 001 000 +Value: 1 0 1 ... 1 0 1 1 +Meaning: ✓ ✗ ✓ ... ✓ ✗ ✓ ✓ + +✓ = Period proven (bit = 1) +✗ = Period faulted (bit = 0) +``` + +## Gas Efficiency + +### Without Bitmap (naive approach) +```solidity +mapping(uint256 dataSetId => mapping(uint256 periodId => bool)) private provenPeriods; +``` +- **Storage**: 1 slot per period +- **Cost**: ~20,000 gas per period to set from 0 to 1 + +### With Bitmap (optimized) +```solidity +mapping(uint256 dataSetId => mapping(uint256 bucketId => uint256)) private provenPeriods; +``` +- **Storage**: 1 slot per 256 periods +- **Cost**: + - First bit in bucket: ~20,000 gas (cold storage) + - Additional bits in same bucket: ~5,000 gas (warm storage) +- **Space savings**: 256× reduction in storage slots + +## Querying via RPC + +### Direct Storage Query (Advanced) + +To read the raw bitmap directly from storage: + +```bash +# Calculate storage slot for provenPeriods[dataSetId][bucketId] +# This requires knowing the storage layout + +cast storage --rpc-url +``` + +### View Function Query (Recommended) + +```bash +# Check if a specific period is proven +cast call \ + "provenPeriods(uint256,uint256)" \ + \ + --rpc-url +``` + +Returns: +- `0x0000...0001` if period is proven +- `0x0000...0000` if period faulted + +## Decoding Bitmap in Scripts + +### Pseudocode + +```python +def is_period_proven(bitmap_value, period_id): + """Check if a specific period is proven given the bucket bitmap.""" + bit_position = period_id & 255 # period_id % 256 + bitmask = 1 << bit_position + return (bitmap_value & bitmask) != 0 + +def get_all_proven_periods_in_bucket(bitmap_value, bucket_id): + """Extract all proven periods from a single bucket.""" + proven_periods = [] + base_period = bucket_id * 256 + + for bit_pos in range(256): + if bitmap_value & (1 << bit_pos): + proven_periods.append(base_period + bit_pos) + + return proven_periods +``` + +### Shell Script Example + +```bash +# Query bucket 0 (periods 0-255) +bucket_value=$(cast call $CONTRACT "provenPeriods(uint256,uint256)" $DATASET_ID 0 --rpc-url $RPC) + +# Check specific period (e.g., period 100) +period_id=100 +bit_position=$((period_id & 255)) # 100 % 256 = 100 +bitmask=$((1 << bit_position)) + +# Check if period is proven +if (( (bucket_value & bitmask) != 0 )); then + echo "Period $period_id is PROVEN" +else + echo "Period $period_id is FAULTED" +fi +``` + +## Related Contract Code + +### File: `service_contracts/src/FilecoinWarmStorageService.sol` + +**Setting proven period (line 933):** +```solidity +provenPeriods[dataSetId][currentPeriod >> 8] |= (1 << (currentPeriod & 255)); +``` + +**Checking proven period (line 1621):** +```solidity +function _isPeriodProven(uint256 dataSetId, uint256 periodId) private view returns (bool) { + return provenPeriods[dataSetId][periodId >> 8] & (1 << (periodId & 255)) != 0; +} +``` + +**Public view function (StateView.sol line 142):** +```solidity +function provenPeriods(uint256 dataSetId, uint256 periodId) public view returns (bool) { + return FilecoinWarmStorageServiceStateLibrary.provenPeriods(service, dataSetId, periodId); +} +``` + +## Common Pitfalls + +### ❌ Wrong: Direct boolean storage +```solidity +mapping(uint256 => mapping(uint256 => bool)) provenPeriods; +``` +- Uses 1 storage slot per period +- Expensive for long-term storage + +### ❌ Wrong: Incorrect bit calculation +```solidity +// Don't use division and modulo operators +uint256 bucket = periodId / 256; // More expensive than >> +uint256 bitPos = periodId % 256; // More expensive than & +``` + +### ✅ Correct: Bitmap with bitwise operations +```solidity +mapping(uint256 => mapping(uint256 => uint256)) provenPeriods; +provenPeriods[dataSetId][periodId >> 8] |= (1 << (periodId & 255)); +``` +- Uses 1 storage slot per 256 periods +- Efficient bitwise operations +- Scales to thousands of periods + +## Performance Comparison + +| Approach | Storage per 1000 periods | Gas for setting 1000 periods | +|----------|--------------------------|------------------------------| +| Individual bools | 1000 slots | ~20,000,000 gas | +| Bitmap (optimized) | 4 slots | ~100,000 gas | +| **Savings** | **99.6% less** | **99.5% less** | + +## See Also + +- [Solidity Bitwise Operations](https://docs.soliditylang.org/en/latest/types.html#bitwise-operators) +- [EVM Storage Layout](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html) +- [Gas Optimization Techniques](https://github.com/iskdrews/awesome-solidity-gas-optimization) diff --git a/scripts/EXAMPLE_OUTPUT.md b/scripts/EXAMPLE_OUTPUT.md new file mode 100644 index 00000000..ea5daef0 --- /dev/null +++ b/scripts/EXAMPLE_OUTPUT.md @@ -0,0 +1,247 @@ +# Example Output + +This document shows example outputs from the fault status query script. + +## Example 1: Dataset with No Faults + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Filecoin Warm Storage Service - Fault Status Report +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Dataset ID: 123 +Contract: 0x80617b65FD2EEa1D7fDe2B4F85977670690ed348 +RPC: https://api.calibration.node.glif.io/rpc/v1 + +Fetching current block number... +✓ Current block: 3245678 + +Querying proving configuration... +✓ Proving configuration retrieved + Activation Epoch: 3100000 + Current Deadline: 3250000 + Max Proving Period: 25000 epochs + Challenge Window: 5000 epochs + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Proving Period Analysis +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Current Period: 5 +Challenge Window: blocks 3245000 to 3250000 +✓ Currently in challenge window + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Fault History (Periods 0 to 5) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Scanning 6 proving periods... + +Period Deadline Status Result +──────────────────────────────────────────────────────────── +0 3125000 Checked ✓ PROVEN +1 3150000 Checked ✓ PROVEN +2 3175000 Checked ✓ PROVEN +3 3200000 Checked ✓ PROVEN +4 3225000 Checked ✓ PROVEN +5 3250000 Pending ○ Not yet due + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Summary +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Total Periods Checked: 6 +Proven Periods: 5 +Faulted Periods: 0 + +✓ NO FAULTS DETECTED - All checked periods proven successfully + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +## Example 2: Dataset with Faults + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Filecoin Warm Storage Service - Fault Status Report +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Dataset ID: 456 +Contract: 0x80617b65FD2EEa1D7fDe2B4F85977670690ed348 +RPC: https://api.calibration.node.glif.io/rpc/v1 + +Fetching current block number... +✓ Current block: 3345678 + +Querying proving configuration... +✓ Proving configuration retrieved + Activation Epoch: 3100000 + Current Deadline: 3250000 + Max Proving Period: 25000 epochs + Challenge Window: 5000 epochs + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Proving Period Analysis +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Current Period: 9 +Challenge Window: blocks 3245000 to 3250000 +⚠ DEADLINE MISSED by 95678 blocks (3 period(s)) + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Fault History (Periods 0 to 9) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Scanning 10 proving periods... + +Period Deadline Status Result +──────────────────────────────────────────────────────────── +0 3125000 Checked ✓ PROVEN +1 3150000 Checked ✓ PROVEN +2 3175000 Checked ✗ FAULTED +3 3200000 Checked ✓ PROVEN +4 3225000 Checked ✗ FAULTED +5 3250000 Checked ✗ FAULTED +6 3275000 Checked ✓ PROVEN +7 3300000 Checked ✓ PROVEN +8 3325000 Checked ✓ PROVEN +9 3350000 Pending ○ Not yet due + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Summary +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Total Periods Checked: 10 +Proven Periods: 6 +Faulted Periods: 3 + +Faulted Period IDs: 2 4 5 + +⚠ FAULTS DETECTED - Fault rate: 30.00% + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +## Example 3: Dataset with Proving Not Started + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Filecoin Warm Storage Service - Fault Status Report +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Dataset ID: 789 +Contract: 0x80617b65FD2EEa1D7fDe2B4F85977670690ed348 +RPC: https://api.calibration.node.glif.io/rpc/v1 + +Fetching current block number... +✓ Current block: 3245678 + +Querying proving configuration... +✓ Proving configuration retrieved + Activation Epoch: 0 + Current Deadline: 0 + Max Proving Period: 25000 epochs + Challenge Window: 5000 epochs + +⚠ Proving has not been activated for this dataset +``` + +## Example 4: Recent Period Check (--periods 10) + +```bash +./query-fault-status.sh --periods 10 123 +``` + +This limits the scan to only the 10 most recent periods, useful for datasets with many proving periods when you only care about recent history: + +``` +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Filecoin Warm Storage Service - Fault Status Report +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Dataset ID: 123 +Contract: 0x80617b65FD2EEa1D7fDe2B4F85977670690ed348 +RPC: https://api.calibration.node.glif.io/rpc/v1 + +Fetching current block number... +✓ Current block: 3345678 + +Querying proving configuration... +✓ Proving configuration retrieved + Activation Epoch: 3100000 + Current Deadline: 3350000 + Max Proving Period: 25000 epochs + Challenge Window: 5000 epochs + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Proving Period Analysis +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Current Period: 9 +Challenge Window: blocks 3345000 to 3350000 +✓ Currently in challenge window + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Fault History (Periods 0 to 9) +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Scanning 10 proving periods... + +Period Deadline Status Result +──────────────────────────────────────────────────────────── +0 3125000 Checked ✓ PROVEN +1 3150000 Checked ✓ PROVEN +2 3175000 Checked ✓ PROVEN +3 3200000 Checked ✓ PROVEN +4 3225000 Checked ✓ PROVEN +5 3250000 Checked ✓ PROVEN +6 3275000 Checked ✓ PROVEN +7 3300000 Checked ✓ PROVEN +8 3325000 Checked ✓ PROVEN +9 3350000 Pending ○ Not yet due + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Summary +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Total Periods Checked: 10 +Proven Periods: 9 +Faulted Periods: 0 + +✓ NO FAULTS DETECTED - All checked periods proven successfully + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +Note: If the current period is 100 and you use `--periods 10`, it will check periods 91-100 (the 10 most recent), not periods 0-9. + +## Interpreting the Output + +### Status Indicators + +- **✓ PROVEN** (Green) - The provider successfully submitted proof for this period +- **✗ FAULTED** (Red) - The deadline passed without a valid proof being submitted +- **○ Not yet due** (White) - The deadline for this period has not yet arrived + +### Key Metrics + +1. **Current Block** - The latest block on the chain +2. **Activation Epoch** - When proving started for this dataset +3. **Current Deadline** - The block number of the next proving deadline +4. **Max Proving Period** - How many blocks between each proof requirement +5. **Challenge Window** - How many blocks before the deadline proofs can be submitted + +### Understanding Deadlines + +- If `Current Block < Challenge Window Start`: Too early to submit proof +- If `Challenge Window Start <= Current Block <= Deadline`: Can submit proof now +- If `Current Block > Deadline`: Deadline missed - provider is late! + +### Fault Rate + +The fault rate is calculated as: +``` +Fault Rate = (Faulted Periods / Total Checked Periods) × 100% +``` + +A fault rate of 0% means perfect proving performance. Higher fault rates indicate reliability issues. diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..2da0c535 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,223 @@ +# Filecoin Services Scripts + +This directory contains utility scripts for interacting with Filecoin Warm Storage Service contracts. + +## Fault Status Query Scripts + +Two equivalent scripts are provided for querying fault status: +- `query-fault-status.fish` - Fish shell version +- `query-fault-status.sh` - Bash version (more widely compatible) + +Both scripts provide the same functionality to query and analyze the fault history of a dataset in the Filecoin Warm Storage Service. + +### Features + +- Queries the proving period fault bitmap from the contract +- Reconstructs complete fault history for all proving periods +- Calculates whether the provider is currently up to date with proving +- Determines how late the provider is if they missed deadlines +- Generates a detailed, color-coded report + +### Prerequisites + +- [Foundry](https://book.getfoundry.sh/getting-started/installation) (forge/cast) installed +- Either Bash (usually pre-installed) or [Fish shell](https://fishshell.com/) +- Access to a Filecoin RPC endpoint + +### Installation + +```bash +# Install Foundry +curl -L https://foundry.paradigm.xyz | bash +foundryup + +# Install Fish (Ubuntu/Debian) +sudo apt-get install fish + +# Or on macOS +brew install fish +``` + +### Usage + +**Fish version:** +```fish +./query-fault-status.fish [OPTIONS] +``` + +**Bash version:** +```bash +./query-fault-status.sh [OPTIONS] +``` + +#### Arguments + +- `` - The dataset ID to query (required) + +#### Options + +- `-r, --rpc-url URL` - RPC endpoint URL (default: Calibration testnet) +- `-c, --contract ADDR` - Contract address (default: Calibration testnet address) +- `-p, --periods N` - Check only the N most recent periods (default: check all) +- `-v, --verbose` - Enable verbose output +- `-h, --help` - Show help message + +### Examples + +#### Query a dataset on Calibration testnet + +```bash +./query-fault-status.sh 123 +# or +./query-fault-status.fish 123 +``` + +#### Query with custom RPC and contract + +```bash +./query-fault-status.sh \ + --rpc-url https://api.node.glif.io/rpc/v1 \ + --contract 0x80617b65FD2EEa1D7fDe2B4F85977670690ed348 \ + 456 +``` + +#### Check only the most recent 50 periods + +```bash +./query-fault-status.sh --periods 50 789 +``` + +### Output + +The script generates a comprehensive report including: + +1. **Configuration Summary** + - Dataset ID + - Contract address + - Current block number + - Proving configuration (activation epoch, deadline, period length, challenge window) + +2. **Proving Period Analysis** + - Current proving period + - Challenge window status + - Whether deadline was missed and by how many blocks + +3. **Fault History Table** + - Period-by-period breakdown + - Deadline for each period + - Status (Proven/Faulted/Pending) + +4. **Summary Statistics** + - Total periods checked + - Number of proven periods + - Number of faulted periods + - Fault rate percentage + +### How It Works + +#### Bitmap Decoding + +The script queries the `provenPeriods` mapping which uses an optimized bitmap storage system: + +- Each `uint256` stores 256 periods as individual bits +- `periodId >> 8` determines which storage bucket +- `periodId & 255` determines which bit within the bucket +- Bit value 1 = period was proven successfully +- Bit value 0 = period faulted (proof not submitted) + +#### Contract Queries + +The script makes the following RPC calls using `cast call`: + +1. `provingActivationEpoch(uint256)` - When proving started +2. `provingDeadline(uint256)` - Current deadline block +3. `getMaxProvingPeriod()` - Length of each proving period +4. `challengeWindow()` - Size of the challenge window +5. `provenPeriods(uint256,uint256)` - Check if specific period was proven + +#### Fault Detection + +For each proving period: + +1. Calculate the deadline: `activationEpoch + (periodId + 1) * maxProvingPeriod` +2. Query if the period was proven using `provenPeriods(dataSetId, periodId)` +3. If deadline has passed and period not proven → **FAULT** +4. If deadline has passed and period proven → **SUCCESS** +5. If deadline not yet reached → **PENDING** + +### Technical Details + +#### Proving Period Calculation + +``` +currentPeriod = (currentBlock - activationEpoch) / maxProvingPeriod +``` + +#### Challenge Window + +The challenge window is the time period during which a provider can submit their proof: + +``` +challengeWindowStart = deadline - challengeWindowSize +``` + +Proofs can only be submitted when: +``` +challengeWindowStart <= currentBlock <= deadline +``` + +#### Periods Missed + +If the current block is past the deadline: + +``` +blocksOverdue = currentBlock - deadline +periodsMissed = floor(blocksOverdue / maxProvingPeriod) +``` + +### Troubleshooting + +#### "cast: command not found" + +Install Foundry: +```bash +curl -L https://foundry.paradigm.xyz | bash +foundryup +``` + +#### RPC connection errors + +- Ensure the RPC URL is correct and accessible +- Try using a different RPC endpoint +- Check if you need authentication/API keys + +#### "Failed to query contract state" + +- Verify the contract address is correct +- Ensure the dataset ID exists +- Check that the RPC endpoint is synced + +### Contract Addresses + +#### Calibration Testnet (Default) +- **FilecoinWarmStorageService**: `0x80617b65FD2EEa1D7fDe2B4F85977670690ed348` +- **Network**: `filecoin-testnet` +- **RPC**: `https://api.calibration.node.glif.io/rpc/v1` + +#### Mainnet +- Coming soon (see `subgraph/config/network.json` for latest addresses) + +### Additional Documentation + +- **[EXAMPLE_OUTPUT.md](./EXAMPLE_OUTPUT.md)** - Example outputs and how to interpret them +- **[BITMAP_REFERENCE.md](./BITMAP_REFERENCE.md)** - Technical deep-dive into the bitmap storage system + +### Related Files + +- Contract: `service_contracts/src/FilecoinWarmStorageService.sol` +- State View: `service_contracts/src/FilecoinWarmStorageServiceStateView.sol` +- Network Config: `subgraph/config/network.json` + +### License + +See [LICENSE.md](../LICENSE.md) diff --git a/scripts/query-fault-status.fish b/scripts/query-fault-status.fish new file mode 100755 index 00000000..4aafe315 --- /dev/null +++ b/scripts/query-fault-status.fish @@ -0,0 +1,444 @@ +#!/usr/bin/env fish + +# Filecoin Warm Storage Service - Fault Status Query Script +# This script queries the fault history of a dataset by examining the provenPeriods bitmap +# and generating a comprehensive report on proving status. + +set -g SCRIPT_NAME (basename (status filename)) + +# Default configuration +set -g DEFAULT_RPC_URL "https://api.calibration.node.glif.io/rpc/v1" +set -g DEFAULT_CONTRACT_ADDRESS "0xA5D87b04086B1d591026cCE10255351B5AA4689B" # Calibration testnet state view contract + +# Color codes for output +set -g COLOR_RESET "\033[0m" +set -g COLOR_BOLD "\033[1m" +set -g COLOR_RED "\033[31m" +set -g COLOR_GREEN "\033[32m" +set -g COLOR_YELLOW "\033[33m" +set -g COLOR_BLUE "\033[34m" +set -g COLOR_CYAN "\033[36m" + +function print_usage + echo "Usage: $SCRIPT_NAME [OPTIONS] " + echo "" + echo "Query the fault status of a Filecoin Warm Storage Service dataset." + echo "" + echo "Arguments:" + echo " The dataset ID to query (required)" + echo "" + echo "Options:" + echo " -r, --rpc-url URL RPC endpoint URL (default: calibration testnet)" + echo " -c, --contract ADDR Contract address (default: calibration testnet)" + echo " -p, --periods N Check only the N most recent periods (default: check all)" + echo " -v, --verbose Enable verbose output" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $SCRIPT_NAME 123" + echo " $SCRIPT_NAME --rpc-url https://api.node.glif.io/rpc/v1 --contract 0x... 456" + echo " $SCRIPT_NAME -p 100 789" +end + +function error + echo -e "$COLOR_RED""Error: $argv""$COLOR_RESET" >&2 +end + +function info + echo -e "$COLOR_CYAN""$argv""$COLOR_RESET" +end + +function success + echo -e "$COLOR_GREEN""$argv""$COLOR_RESET" +end + +function warning + echo -e "$COLOR_YELLOW""$argv""$COLOR_RESET" +end + +function bold + echo -e "$COLOR_BOLD""$argv""$COLOR_RESET" +end + +# Check if forge/cast is installed +function check_dependencies + if not type -q cast + error "cast (foundry) is not installed or not in PATH" + error "Install from: https://book.getfoundry.sh/getting-started/installation" + return 1 + end + return 0 +end + +# Convert hex to decimal +function hex_to_dec + set -l hex_value $argv[1] + # Remove 0x prefix if present + set hex_value (string replace -r '^0x' '' $hex_value) + printf "%d" 0x$hex_value +end + +# Call contract function +function call_contract + set -l function_sig $argv[1] + set -l args $argv[2..-1] + + if test (count $argv) -eq 1 + cast call $CONTRACT_ADDRESS "$function_sig" --rpc-url $RPC_URL 2>/dev/null + else + cast call $CONTRACT_ADDRESS "$function_sig" $args --rpc-url $RPC_URL 2>/dev/null + end +end + +# Get current block number +function get_current_block + cast block-number --rpc-url $RPC_URL 2>/dev/null +end + +# Query proving activation epoch +function get_proving_activation_epoch + set -l dataset_id $argv[1] + set -l result (call_contract "provingActivationEpoch(uint256)" $dataset_id) + hex_to_dec $result +end + +# Query proving deadline +function get_proving_deadline + set -l dataset_id $argv[1] + set -l result (call_contract "provingDeadline(uint256)" $dataset_id) + hex_to_dec $result +end + +# Query max proving period +function get_max_proving_period + set -l result (call_contract "getMaxProvingPeriod()") + hex_to_dec $result +end + +# Query challenge window size +function get_challenge_window + set -l result (call_contract "challengeWindow()") + hex_to_dec $result +end + +# Query proven periods bucket (internal mapping storage) +# NOTE: This function is for reference only and is not currently used. +# It would be inefficient to reconstruct the bitmap this way, and large exponents +# (2^255) could cause overflow issues. +function get_proven_periods_bucket + set -l dataset_id $argv[1] + set -l bucket_id $argv[2] + + # Calculate storage slot for provenPeriods[dataSetId][bucketId] + # We need to query via the StateView contract if available + # For now, we'll query individual periods using the view function + + # Query using provenPeriods view function for each bit in the bucket + # WARNING: This is inefficient and may overflow for large bit positions + set -l bucket_value 0 + for bit_pos in (seq 0 255) + set -l period_id (math "$bucket_id * 256 + $bit_pos") + set -l result (call_contract "provenPeriods(uint256,uint256)" $dataset_id $period_id) + + # If result is 0x0000...0001 then period is proven + if test "$result" = "0x0000000000000000000000000000000000000000000000000000000000000001" + # Set the corresponding bit (may overflow for large bit_pos) + set bucket_value (math "$bucket_value + 2^$bit_pos") + end + end + + echo $bucket_value +end + +# Check if a specific period is proven +function is_period_proven + set -l dataset_id $argv[1] + set -l period_id $argv[2] + + set -l result (call_contract "provenPeriods(uint256,uint256)" $dataset_id $period_id) + + if test "$result" = "0x0000000000000000000000000000000000000000000000000000000000000001" + return 0 # true + else + return 1 # false + end +end + +# Calculate current proving period +function calculate_current_period + set -l activation_epoch $argv[1] + set -l current_epoch $argv[2] + set -l max_proving_period $argv[3] + + if test $activation_epoch -eq 0 + echo -1 + return + end + + if test $current_epoch -lt $activation_epoch + echo -1 + return + end + + set -l epochs_since_activation (math "$current_epoch - $activation_epoch") + # Fish math does integer division by default (automatic floor) + set -l current_period (math "$epochs_since_activation / $max_proving_period") + echo $current_period +end + +# Calculate deadline for a specific period +function calculate_period_deadline + set -l activation_epoch $argv[1] + set -l period_id $argv[2] + set -l max_proving_period $argv[3] + + set -l deadline (math "$activation_epoch + ($period_id + 1) * $max_proving_period") + echo $deadline +end + +# Main query function +function query_fault_status + set -l dataset_id $argv[1] + + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Filecoin Warm Storage Service - Fault Status Report" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + info "Dataset ID: $dataset_id" + info "Contract: $CONTRACT_ADDRESS" + info "RPC: $RPC_URL" + echo "" + + # Get current block number + info "Fetching current block number..." + set -l current_block (get_current_block) + if test -z "$current_block" + error "Failed to get current block number" + return 1 + end + success "✓ Current block: $current_block" + echo "" + + # Get proving configuration + info "Querying proving configuration..." + set -l activation_epoch (get_proving_activation_epoch $dataset_id) + set -l proving_deadline (get_proving_deadline $dataset_id) + set -l max_proving_period (get_max_proving_period) + set -l challenge_window (get_challenge_window) + + if test -z "$activation_epoch" -o -z "$proving_deadline" -o -z "$max_proving_period" -o -z "$challenge_window" + error "Failed to query contract state" + return 1 + end + + success "✓ Proving configuration retrieved" + echo " Activation Epoch: $activation_epoch" + echo " Current Deadline: $proving_deadline" + echo " Max Proving Period: $max_proving_period epochs" + echo " Challenge Window: $challenge_window epochs" + echo "" + + # Check if proving is active + if test $proving_deadline -eq 0 + warning "⚠ Proving has not been activated for this dataset" + return 0 + end + + if test $activation_epoch -eq 0 -o $activation_epoch -gt $current_block + warning "⚠ Proving not yet started (activation epoch not reached)" + return 0 + end + + # Calculate current period + set -l current_period (calculate_current_period $activation_epoch $current_block $max_proving_period) + + if test $current_period -lt 0 + warning "⚠ Invalid period calculation" + return 1 + end + + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Proving Period Analysis" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + echo "Current Period: $current_period" + + # Calculate challenge window + set -l challenge_window_start (math "$proving_deadline - $challenge_window") + echo "Challenge Window: blocks $challenge_window_start to $proving_deadline" + + # Check if we're in the challenge window + if test $current_block -ge $challenge_window_start -a $current_block -le $proving_deadline + success "✓ Currently in challenge window" + else if test $current_block -gt $proving_deadline + set -l blocks_overdue (math "$current_block - $proving_deadline") + # Fish math does integer division by default (automatic floor) + set -l periods_missed (math "$blocks_overdue / $max_proving_period") + warning "⚠ DEADLINE MISSED by $blocks_overdue blocks ($periods_missed period(s))" + else + info "○ Not yet in challenge window (starts at block $challenge_window_start)" + end + echo "" + + # Check how many periods to scan + set -l start_period 0 + set -l end_period $current_period + + if test $MAX_PERIODS -gt 0 + # Check the N most recent periods + set start_period (math "max(0, $current_period - $MAX_PERIODS + 1)") + end + + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Fault History (Periods $start_period to $end_period)" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + set -l num_periods (math "$end_period - $start_period + 1") + info "Scanning $num_periods proving periods..." + echo "" + + set -l total_periods 0 + set -l proven_count 0 + set -l faulted_count 0 + set -l faulted_periods + + printf "%-10s %-15s %-15s %-10s\n" "Period" "Deadline" "Status" "Result" + echo "────────────────────────────────────────────────────────────" + + for period_id in (seq $start_period $end_period) + set -l period_deadline (calculate_period_deadline $activation_epoch $period_id $max_proving_period) + + # Only check periods whose deadline has passed + if test $period_deadline -le $current_block + set total_periods (math "$total_periods + 1") + if is_period_proven $dataset_id $period_id + set proven_count (math "$proven_count + 1") + printf "%-10s %-15s %-15s " "$period_id" "$period_deadline" "Checked" + success "✓ PROVEN" + else + set faulted_count (math "$faulted_count + 1") + set -a faulted_periods $period_id + printf "%-10s %-15s %-15s " "$period_id" "$period_deadline" "Checked" + error "✗ FAULTED" + end + else + printf "%-10s %-15s %-15s " "$period_id" "$period_deadline" "Pending" + echo "○ Not yet due" + end + end + + echo "" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Summary" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + echo "Total Periods Checked: $total_periods" + success "Proven Periods: $proven_count" + + if test $faulted_count -gt 0 + error "Faulted Periods: $faulted_count" + echo "" + warning "Faulted Period IDs: $faulted_periods" + else + success "Faulted Periods: 0" + end + + echo "" + + if test $faulted_count -eq 0 + success "✓ NO FAULTS DETECTED - All checked periods proven successfully" + else + # Calculate fault rate with 2 decimal places + # Fish math doesn't support floating point directly, so we calculate as integer then format + set -l fault_rate_scaled (math "$faulted_count * 10000 / $total_periods") + set -l fault_rate_whole (math "$fault_rate_scaled / 100") + set -l fault_rate_decimal (math "$fault_rate_scaled % 100") + # Pad decimal part to 2 digits + if test $fault_rate_decimal -lt 10 + set fault_rate_decimal "0$fault_rate_decimal" + end + warning "⚠ FAULTS DETECTED - Fault rate: $fault_rate_whole.$fault_rate_decimal%" + end + + echo "" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + return 0 +end + +# Parse command line arguments +set -g RPC_URL $DEFAULT_RPC_URL +set -g CONTRACT_ADDRESS $DEFAULT_CONTRACT_ADDRESS +set -g MAX_PERIODS 0 +set -g VERBOSE 0 +set -g DATASET_ID "" + +set -l i 1 +while test $i -le (count $argv) + set -l arg $argv[$i] + + switch $arg + case -h --help + print_usage + exit 0 + + case -r --rpc-url + set i (math "$i + 1") + if test $i -le (count $argv) + set RPC_URL $argv[$i] + else + error "Missing value for $arg" + exit 1 + end + + case -c --contract + set i (math "$i + 1") + if test $i -le (count $argv) + set CONTRACT_ADDRESS $argv[$i] + else + error "Missing value for $arg" + exit 1 + end + + case -p --periods + set i (math "$i + 1") + if test $i -le (count $argv) + set MAX_PERIODS $argv[$i] + else + error "Missing value for $arg" + exit 1 + end + + case -v --verbose + set VERBOSE 1 + + case '*' + if test -z "$DATASET_ID" + set DATASET_ID $arg + else + error "Unexpected argument: $arg" + print_usage + exit 1 + end + end + + set i (math "$i + 1") +end + +# Validate required arguments +if test -z "$DATASET_ID" + error "Missing required argument: " + echo "" + print_usage + exit 1 +end + +# Main execution +check_dependencies +or exit 1 + +query_fault_status $DATASET_ID diff --git a/scripts/query-fault-status.sh b/scripts/query-fault-status.sh new file mode 100755 index 00000000..23e4ce70 --- /dev/null +++ b/scripts/query-fault-status.sh @@ -0,0 +1,405 @@ +#!/usr/bin/env bash + +# Filecoin Warm Storage Service - Fault Status Query Script +# This script queries the fault history of a dataset by examining the provenPeriods bitmap +# and generating a comprehensive report on proving status. + +set -euo pipefail + +SCRIPT_NAME=$(basename "$0") + +# Default configuration +DEFAULT_RPC_URL="https://api.calibration.node.glif.io/rpc/v1" +DEFAULT_CONTRACT_ADDRESS="0xA5D87b04086B1d591026cCE10255351B5AA4689B" # Calibration testnet + +# Color codes for output +COLOR_RESET="\033[0m" +COLOR_BOLD="\033[1m" +COLOR_RED="\033[31m" +COLOR_GREEN="\033[32m" +COLOR_YELLOW="\033[33m" +COLOR_BLUE="\033[34m" +COLOR_CYAN="\033[36m" + +function print_usage() { + echo "Usage: $SCRIPT_NAME [OPTIONS] " + echo "" + echo "Query the fault status of a Filecoin Warm Storage Service dataset." + echo "" + echo "Arguments:" + echo " The dataset ID to query (required)" + echo "" + echo "Options:" + echo " -r, --rpc-url URL RPC endpoint URL (default: calibration testnet)" + echo " -c, --contract ADDR Contract address (default: calibration testnet)" + echo " -p, --periods N Check only the N most recent periods (default: check all)" + echo " -v, --verbose Enable verbose output" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $SCRIPT_NAME 123" + echo " $SCRIPT_NAME --rpc-url https://api.node.glif.io/rpc/v1 --contract 0x... 456" + echo " $SCRIPT_NAME -p 100 789" +} + +function error() { + echo -e "${COLOR_RED}Error: $*${COLOR_RESET}" >&2 +} + +function info() { + echo -e "${COLOR_CYAN}$*${COLOR_RESET}" +} + +function success() { + echo -e "${COLOR_GREEN}$*${COLOR_RESET}" +} + +function warning() { + echo -e "${COLOR_YELLOW}$*${COLOR_RESET}" +} + +function bold() { + echo -e "${COLOR_BOLD}$*${COLOR_RESET}" +} + +# Check if forge/cast is installed +function check_dependencies() { + if ! command -v cast &> /dev/null; then + error "cast (foundry) is not installed or not in PATH" + error "Install from: https://book.getfoundry.sh/getting-started/installation" + return 1 + fi + return 0 +} + +# Convert hex to decimal +function hex_to_dec() { + local hex_value=$1 + # Remove 0x prefix if present + hex_value=${hex_value#0x} + printf "%d" $((16#$hex_value)) +} + +# Call contract function +function call_contract() { + local function_sig=$1 + shift + + if [ $# -eq 0 ]; then + cast call "$CONTRACT_ADDRESS" "$function_sig" --rpc-url "$RPC_URL" 2>/dev/null + else + cast call "$CONTRACT_ADDRESS" "$function_sig" "$@" --rpc-url "$RPC_URL" 2>/dev/null + fi +} + +# Get current block number +function get_current_block() { + cast block-number --rpc-url "$RPC_URL" 2>/dev/null +} + +# Query proving activation epoch +function get_proving_activation_epoch() { + local dataset_id=$1 + local result + result=$(call_contract "provingActivationEpoch(uint256)" "$dataset_id") + hex_to_dec "$result" +} + +# Query proving deadline +function get_proving_deadline() { + local dataset_id=$1 + local result + result=$(call_contract "provingDeadline(uint256)" "$dataset_id") + hex_to_dec "$result" +} + +# Query max proving period +function get_max_proving_period() { + local result + result=$(call_contract "getMaxProvingPeriod()") + hex_to_dec "$result" +} + +# Query challenge window size +function get_challenge_window() { + local result + result=$(call_contract "challengeWindow()") + hex_to_dec "$result" +} + +# Check if a specific period is proven +function is_period_proven() { + local dataset_id=$1 + local period_id=$2 + local result + + result=$(call_contract "provenPeriods(uint256,uint256)" "$dataset_id" "$period_id") + + if [ "$result" = "0x0000000000000000000000000000000000000000000000000000000000000001" ]; then + return 0 # true + else + return 1 # false + fi +} + +# Calculate current proving period +function calculate_current_period() { + local activation_epoch=$1 + local current_epoch=$2 + local max_proving_period=$3 + + if [ "$activation_epoch" -eq 0 ]; then + echo -1 + return + fi + + if [ "$current_epoch" -lt "$activation_epoch" ]; then + echo -1 + return + fi + + local epochs_since_activation=$((current_epoch - activation_epoch)) + local current_period=$((epochs_since_activation / max_proving_period)) + echo "$current_period" +} + +# Calculate deadline for a specific period +function calculate_period_deadline() { + local activation_epoch=$1 + local period_id=$2 + local max_proving_period=$3 + + local deadline=$((activation_epoch + (period_id + 1) * max_proving_period)) + echo "$deadline" +} + +# Main query function +function query_fault_status() { + local dataset_id=$1 + + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Filecoin Warm Storage Service - Fault Status Report" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + info "Dataset ID: $dataset_id" + info "Contract: $CONTRACT_ADDRESS" + info "RPC: $RPC_URL" + echo "" + + # Get current block number + info "Fetching current block number..." + local current_block + current_block=$(get_current_block) + if [ -z "$current_block" ]; then + error "Failed to get current block number" + return 1 + fi + success "✓ Current block: $current_block" + echo "" + + # Get proving configuration + info "Querying proving configuration..." + local activation_epoch proving_deadline max_proving_period challenge_window + activation_epoch=$(get_proving_activation_epoch "$dataset_id") + proving_deadline=$(get_proving_deadline "$dataset_id") + max_proving_period=$(get_max_proving_period) + challenge_window=$(get_challenge_window) + + if [ -z "$activation_epoch" ] || [ -z "$proving_deadline" ] || [ -z "$max_proving_period" ] || [ -z "$challenge_window" ]; then + error "Failed to query contract state" + return 1 + fi + + success "✓ Proving configuration retrieved" + echo " Activation Epoch: $activation_epoch" + echo " Current Deadline: $proving_deadline" + echo " Max Proving Period: $max_proving_period epochs" + echo " Challenge Window: $challenge_window epochs" + echo "" + + # Check if proving is active + if [ "$proving_deadline" -eq 0 ]; then + warning "⚠ Proving has not been activated for this dataset" + return 0 + fi + + if [ "$activation_epoch" -eq 0 ] || [ "$activation_epoch" -gt "$current_block" ]; then + warning "⚠ Proving not yet started (activation epoch not reached)" + return 0 + fi + + # Calculate current period + local current_period + current_period=$(calculate_current_period "$activation_epoch" "$current_block" "$max_proving_period") + + if [ "$current_period" -lt 0 ]; then + warning "⚠ Invalid period calculation" + return 1 + fi + + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Proving Period Analysis" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + echo "Current Period: $current_period" + + # Calculate challenge window + local challenge_window_start=$((proving_deadline - challenge_window)) + echo "Challenge Window: blocks $challenge_window_start to $proving_deadline" + + # Check if we're in the challenge window + if [ "$current_block" -ge "$challenge_window_start" ] && [ "$current_block" -le "$proving_deadline" ]; then + success "✓ Currently in challenge window" + elif [ "$current_block" -gt "$proving_deadline" ]; then + local blocks_overdue=$((current_block - proving_deadline)) + local periods_missed=$((blocks_overdue / max_proving_period)) + warning "⚠ DEADLINE MISSED by $blocks_overdue blocks ($periods_missed period(s))" + else + info "○ Not yet in challenge window (starts at block $challenge_window_start)" + fi + echo "" + + # Check how many periods to scan + local start_period=0 + local end_period=$current_period + + if [ "$MAX_PERIODS" -gt 0 ]; then + # Check the N most recent periods + local temp=$((current_period - MAX_PERIODS + 1)) + start_period=$((temp > 0 ? temp : 0)) + fi + + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Fault History (Periods $start_period to $end_period)" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + local num_periods=$((end_period - start_period + 1)) + info "Scanning $num_periods proving periods..." + echo "" + + printf "%-10s %-15s %-15s %-10s\n" "Period" "Deadline" "Status" "Result" + echo "────────────────────────────────────────────────────────────" + + local total_periods=0 + local proven_count=0 + local faulted_count=0 + local faulted_periods=() + + for period_id in $(seq "$start_period" "$end_period"); do + local period_deadline + period_deadline=$(calculate_period_deadline "$activation_epoch" "$period_id" "$max_proving_period") + + # Only check periods whose deadline has passed + if [ "$period_deadline" -le "$current_block" ]; then + total_periods=$((total_periods + 1)) + if is_period_proven "$dataset_id" "$period_id"; then + proven_count=$((proven_count + 1)) + printf "%-10s %-15s %-15s " "$period_id" "$period_deadline" "Checked" + success "✓ PROVEN" + else + faulted_count=$((faulted_count + 1)) + faulted_periods+=("$period_id") + printf "%-10s %-15s %-15s " "$period_id" "$period_deadline" "Checked" + error "✗ FAULTED" + fi + else + printf "%-10s %-15s %-15s " "$period_id" "$period_deadline" "Pending" + echo "○ Not yet due" + fi + done + + echo "" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + bold " Summary" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + echo "Total Periods Checked: $total_periods" + success "Proven Periods: $proven_count" + + if [ "$faulted_count" -gt 0 ]; then + error "Faulted Periods: $faulted_count" + echo "" + warning "Faulted Period IDs: ${faulted_periods[*]}" + else + success "Faulted Periods: 0" + fi + + echo "" + + if [ "$faulted_count" -eq 0 ]; then + success "✓ NO FAULTS DETECTED - All checked periods proven successfully" + else + local fault_rate + fault_rate=$(awk "BEGIN {printf \"%.2f\", $faulted_count * 100 / $total_periods}") + warning "⚠ FAULTS DETECTED - Fault rate: ${fault_rate}%" + fi + + echo "" + bold "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + return 0 +} + +# Parse command line arguments +RPC_URL=$DEFAULT_RPC_URL +CONTRACT_ADDRESS=$DEFAULT_CONTRACT_ADDRESS +MAX_PERIODS=0 +VERBOSE=0 +DATASET_ID="" + +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + print_usage + exit 0 + ;; + -r|--rpc-url) + RPC_URL="$2" + shift 2 + ;; + -c|--contract) + CONTRACT_ADDRESS="$2" + shift 2 + ;; + -p|--periods) + MAX_PERIODS="$2" + shift 2 + ;; + -v|--verbose) + VERBOSE=1 + shift + ;; + -*) + error "Unknown option: $1" + print_usage + exit 1 + ;; + *) + if [ -z "$DATASET_ID" ]; then + DATASET_ID="$1" + else + error "Unexpected argument: $1" + print_usage + exit 1 + fi + shift + ;; + esac +done + +# Validate required arguments +if [ -z "$DATASET_ID" ]; then + error "Missing required argument: " + echo "" + print_usage + exit 1 +fi + +# Main execution +check_dependencies || exit 1 + +query_fault_status "$DATASET_ID"