Skip to content

Commit c561529

Browse files
smrz2001claude
andcommitted
fix(anchor-evm): address PR review comments and add full integration test
Fixes from PR #742 review: - Fix tx_hash_to_cid: wrap hash with Keccak256 code + ETH_TX codec (0x93) instead of re-hashing with SHA2-256 - Fix AnchorProof parameter order: (chain_id, root, tx_hash, tx_type) - Add chain ID validation after connecting to provider - Implement retry logic with exponential backoff - Add wallet balance logging for gas cost tracking - Update default confirmations from 1 to 4 (matching JS implementation) Code cleanup: - Remove unused GasConfig (alloy handles gas estimation automatically) - Remove unused get_root_block function and getRootBlock interface - Consolidate gnosis_test.rs into integration_test.rs Testing: - Add test_cid_to_bytes32_matches_js to verify JS compatibility - Add test_tx_hash_to_cid_matches_js to verify JS compatibility - Add test_anchor_service_with_evm for full AnchorService flow testing (merkle tree building, EVM anchoring, time event creation) Documentation: - Update README with comprehensive test documentation - Document all environment variables for integration tests - Update contract interface to match actual implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent cfd3136 commit c561529

9 files changed

Lines changed: 717 additions & 619 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

anchor-evm/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ ceramic-core.workspace = true
1717
ceramic-event.workspace = true
1818
hex.workspace = true
1919
multihash-codetable.workspace = true
20-
serde.workspace = true
2120
tokio.workspace = true
2221
tracing.workspace = true
2322
url.workspace = true
2423

2524
[dev-dependencies]
25+
ceramic-sql.workspace = true
2626
cid.workspace = true
2727
expect-test.workspace = true
2828
test-log.workspace = true
29-
tracing-subscriber.workspace = true
29+
tracing-subscriber.workspace = true

anchor-evm/README.md

Lines changed: 97 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,85 +4,124 @@ A complete self-anchoring solution for Ceramic that can submit root CIDs to any
44

55
## Features
66

7-
- **Multi-chain Support**: Works with any EVM blockchain (Ethereum, Gnosis, Polygon, Arbitrum, Base, etc.)
8-
- **Production Ready**: Comprehensive error handling, retry logic, and gas management
9-
- **Compatible**: Maintains compatibility with existing Ceramic anchor service patterns
10-
- **Efficient**: Uses modern alloy library with optimal gas usage
11-
- **Self-Sovereign**: No reliance on centralized anchor services
7+
- **Multi-chain Support**: Works with any EVM blockchain (Ethereum, Gnosis, Polygon, Arbitrum, Base, etc.)
8+
- **Production Ready**: Comprehensive error handling, retry logic with exponential backoff
9+
- **Compatible**: Maintains compatibility with existing Ceramic anchor service patterns
10+
- **Efficient**: Uses modern alloy library with automatic gas estimation
11+
- **Self-Sovereign**: No reliance on centralized anchor services
1212

13-
## Quick Test on Gnosis Chain
13+
## Testing
1414

15-
The implementation includes a ready-to-test setup using Gnosis Chain with a deployed test contract.
15+
The crate includes comprehensive tests at multiple levels:
1616

17-
### Prerequisites
17+
### Unit Tests (no blockchain required)
1818

19-
1. **Test Account**: You'll need a test account with some xDAI (~0.01 xDAI for testing)
20-
2. **Private Key**: Export your test account's private key (hex format, no 0x prefix)
19+
```bash
20+
cargo test -p ceramic-anchor-evm
21+
```
22+
23+
Runs 15 tests including:
24+
- CID to bytes32 conversion (verified against JS implementation)
25+
- Transaction hash to CID (uses Keccak256 multihash + ETH_TX codec)
26+
- Proof building with correct parameter order
27+
- Configuration validation
2128

22-
### Running the Test
29+
### Integration Test: TransactionManager
30+
31+
Tests the EVM transaction submission flow in isolation.
2332

2433
```bash
25-
# Set your test account private key
26-
export TEST_PRIVATE_KEY="your_private_key_hex_no_0x_prefix"
34+
# Required: Private key (hex, no 0x prefix)
35+
export TEST_PRIVATE_KEY="your_private_key_hex"
36+
37+
# Optional: Override defaults
38+
export TEST_RPC_URL="https://gnosis-mainnet.g.alchemy.com/v2/YOUR_KEY"
39+
export TEST_CONTRACT_ADDRESS="0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC"
40+
export TEST_CHAIN_ID="100"
2741

28-
# Run the Gnosis anchoring test
29-
cd /Users/mz/Documents/3Box/GitHub/3box/rust-ceramic
30-
cargo test -p ceramic-anchor-evm test_gnosis_anchoring -- --ignored --nocapture
42+
# Run the test
43+
cargo test -p ceramic-anchor-evm test_evm_anchoring -- --ignored --nocapture
3144
```
3245

33-
### What the Test Does
46+
**What it tests:**
47+
1. Connects to EVM chain and validates chain ID
48+
2. Submits anchor transaction to contract
49+
3. Waits for confirmations
50+
4. Returns `RootTimeEvent` with correct proof structure
3451

35-
1. **Configuration**: Sets up connection to Gnosis Chain via Alchemy RPC
36-
2. **Contract**: Uses deployed test contract at `0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC`
37-
3. **CID Processing**: Converts a test Ceramic CID to the contract format
38-
4. **Transaction**: Submits anchoring transaction to the blockchain
39-
5. **Confirmation**: Waits for confirmations and validates the result
40-
6. **Verification**: Ensures the proof structure is correct for Ceramic
52+
### Integration Test: Full AnchorService Flow
4153

42-
### Expected Output
54+
Tests the complete anchor pipeline including merkle tree building and time event creation.
4355

56+
```bash
57+
export TEST_PRIVATE_KEY="your_private_key_hex"
58+
59+
cargo test -p ceramic-anchor-evm test_anchor_service_with_evm -- --ignored --nocapture
4460
```
45-
🧪 Testing EVM anchoring on Gnosis Chain
46-
📡 RPC: https://gnosis-mainnet.public.blastapi.io
47-
🔗 Chain ID: 100
48-
📄 Contract: 0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC
49-
⏰ Confirmations: 2
50-
🔍 Validating configuration...
51-
✅ Configuration valid
52-
🏗️ Creating EVM transaction manager...
53-
✅ Transaction manager created
54-
📝 Test root CID: bafyreia776z4jdg5zgycivcpr3q6lcu6llfowkrljkmq3bex2k5hkzat54
55-
🔢 CID as bytes32: 0x1fffb3c48cddc9b024544f8ee1e58a9e5acaeb2a2b4a990d8497d2ba756413ef
56-
⚓ Starting anchoring process...
57-
🎉 Anchoring successful in 15.2s!
58-
📊 Results:
59-
├─ Chain ID: eip155:100
60-
├─ Transaction Type: f(bytes32)
61-
├─ Transaction Hash: bafkreifx4bqkmc5xvaxvg2ttyf554n5bw3hvo2pojkbslp7xnrk2sw3kuq
62-
├─ Proof CID: bafkreia...
63-
└─ Path: ''
64-
✅ All verification checks passed!
65-
🔗 View transaction on Gnosis block explorer:
66-
https://gnosisscan.io/tx/0x...
61+
62+
**What it tests:**
63+
1. Creates mock anchor requests via `MockAnchorEventService`
64+
2. `AnchorService` builds merkle tree from requests
65+
3. Root CID is anchored on EVM chain
66+
4. Time events are created with correct merkle paths
67+
68+
**Expected output:**
69+
```
70+
=== Full AnchorService Integration Test ===
71+
RPC: https://gnosis-mainnet.public.blastapi.io
72+
Chain ID: 100
73+
Contract: 0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC
74+
Anchor requests: 3
75+
Request 0: id=..., prev=...
76+
Request 1: id=..., prev=...
77+
Request 2: id=..., prev=...
78+
79+
Anchoring batch...
80+
Anchoring completed in 18.64s
81+
82+
=== Results ===
83+
Time events created: 3
84+
Proof chain ID: eip155:100
85+
Proof tx_type: f(bytes32)
86+
Proof tx_hash: bagjqcgza...
87+
Proof root: bafyreien...
88+
89+
Time Event 0:
90+
prev: baeabeig...
91+
proof: bafyreic...
92+
path: 0/0
93+
94+
Time Event 1:
95+
prev: baeabeig...
96+
proof: bafyreic...
97+
path: 0/1
98+
99+
Time Event 2:
100+
prev: baeabeig...
101+
proof: bafyreic...
102+
path: 1
103+
104+
All assertions passed!
67105
```
68106

107+
### Test Prerequisites
108+
109+
1. **Test Account**: You'll need a test account with some xDAI (~0.01 xDAI for testing)
110+
2. **Private Key**: Export your test account's private key (hex format, no 0x prefix)
111+
3. **Contract**: Default uses deployed test contract at `0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC`
112+
69113
## Configuration
70114

71115
The implementation supports comprehensive configuration for different networks:
72116

73117
```rust
74-
use ceramic_anchor_evm::{EvmConfig, GasConfig, RetryConfig};
118+
use ceramic_anchor_evm::{EvmConfig, RetryConfig};
75119

76120
let config = EvmConfig {
77121
rpc_url: "https://gnosis-mainnet.g.alchemy.com/v2/YOUR_KEY".to_string(),
78122
private_key: "your_private_key_hex".to_string(),
79123
chain_id: 100, // Gnosis Chain
80124
contract_address: "0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC".to_string(),
81-
gas_config: GasConfig {
82-
gas_limit: Some(300_000),
83-
gas_increase_percent: 25, // 25% increase per retry
84-
..GasConfig::default()
85-
},
86125
retry_config: RetryConfig {
87126
max_retries: 5,
88127
base_delay: Duration::from_secs(3),
@@ -137,16 +176,17 @@ Extremely cost-effective on Gnosis Chain:
137176
The implementation uses a simple, efficient contract interface:
138177

139178
```solidity
140-
contract SimpleAnchor {
141-
mapping(bytes32 => uint256) public rootBlocks;
142-
179+
interface IAnchorContract {
180+
/// Anchor a root CID on the blockchain
143181
function anchorDagCbor(bytes32 root) external;
144-
function getRootBlock(bytes32 root) external view returns (uint256);
145-
182+
183+
/// Event emitted when a root is anchored
146184
event RootAnchored(bytes32 indexed root, uint256 blockNumber, address indexed anchor);
147185
}
148186
```
149187

188+
The contract only needs a single function - the Rust implementation handles all proof construction from the transaction receipt.
189+
150190
## Performance
151191

152192
- **CID Processing**: 2M+ CIDs/second

anchor-evm/src/contract.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
use alloy::{
2-
primitives::{Address, FixedBytes, U256},
2+
primitives::{Address, FixedBytes},
33
providers::Provider,
44
sol,
55
transports::Transport,
66
};
77
use anyhow::Result;
88

99
// Solidity contract interface for anchoring Ceramic roots
10-
// Based on the existing CeramicAnchorServiceV2 pattern
1110
sol! {
1211
#[derive(Debug)]
1312
#[sol(rpc)]
1413
interface IAnchorContract {
15-
/// Anchor a root CID on the blockchain (matching existing pattern)
14+
/// Anchor a root CID on the blockchain
1615
function anchorDagCbor(bytes32 root) external;
17-
18-
/// Get the block number when a root was anchored
19-
function getRootBlock(bytes32 root) external view returns (uint256 blockNumber);
20-
21-
/// Event emitted when a root is successfully anchored
22-
event RootAnchored(bytes32 indexed root, uint256 blockNumber, address indexed anchor);
2316
}
2417
}
2518

@@ -36,15 +29,12 @@ impl<T: Transport + Clone, P: Provider<T> + Clone> AnchorContract<T, P> {
3629
}
3730

3831
/// Anchor a root CID on the blockchain
39-
pub async fn anchor_root(&self, root: FixedBytes<32>) -> Result<alloy::rpc::types::TransactionReceipt> {
32+
pub async fn anchor_root(
33+
&self,
34+
root: FixedBytes<32>,
35+
) -> Result<alloy::rpc::types::TransactionReceipt> {
4036
let call = self.contract.anchorDagCbor(root);
4137
let receipt = call.send().await?.get_receipt().await?;
4238
Ok(receipt)
4339
}
44-
45-
/// Check if a root has been anchored and return the block number
46-
pub async fn get_root_block(&self, root: FixedBytes<32>) -> Result<U256> {
47-
let result = self.contract.getRootBlock(root).call().await?;
48-
Ok(result.blockNumber)
49-
}
50-
}
40+
}

0 commit comments

Comments
 (0)