Skip to content

Commit 81407c9

Browse files
authored
feat: subnet management script and node init config fixes (#1464)
1 parent 00b3d7f commit 81407c9

118 files changed

Lines changed: 31924 additions & 165 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ target/
33
node_modules/
44
.DS_Store
55

6+
# Environment files with credentials
7+
.env
8+
.env.local
69

710
# we migrated from npm to pnpm.
811
package-lock.json

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [Unreleased]
6+
7+
### 🚀 Features
8+
9+
- *(cli)* Add configurable `listen-ip` option to P2P configuration - Allows advanced users to specify a specific IP address for binding services. Defaults to `0.0.0.0` (all interfaces) for maximum compatibility with cloud environments.
10+
11+
### 🐛 Bug Fixes
12+
13+
- *(cli)* Fix libp2p binding issue on cloud VMs (GCP, AWS, Azure) - `ipc-cli node init` now correctly uses `0.0.0.0` (or configurable `listen-ip`) for `listen_addr` and the public IP for `external_addresses`. This fixes parent finality voting and top-down message execution on cloud-deployed subnets where public IPs are not directly bound to network interfaces. Existing deployments can reinitialize or manually update `~/.ipc-node/fendermint/config/default.toml` to set `listen_addr = "/ip4/0.0.0.0/tcp/26655"` and add `external_addresses = ["/ip4/<PUBLIC_IP>/tcp/26655"]`.
14+
515
## [axon-r08] - 2024-12-31
616

717
### 🚀 Features

calculate_chain_id.py

Whitespace-only changes.

check_supply_source.sh

Whitespace-only changes.

docs/ipc/node-init.md

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,26 +126,84 @@ key:
126126

127127
P2P networking configuration for peer discovery and communication.
128128

129-
| Field | Type | Required? | Description |
130-
| ------------- | -------- | --------- | --------------------------------------------- |
131-
| `external-ip` | `string` | No | External IP address for peer connections |
132-
| `ports` | `object` | No | Port configuration for different P2P services |
133-
| `peers` | `object` | No | Peer configuration sources |
129+
| Field | Type | Required? | Description |
130+
| ------------- | -------- | --------- | ------------------------------------------------------------------------ |
131+
| `external-ip` | `string` | No | External IP address for peer connections (defaults to `127.0.0.1`) |
132+
| `listen-ip` | `string` | No | IP address to bind services to (defaults to `0.0.0.0`) |
133+
| `ports` | `object` | No | Port configuration for different P2P services |
134+
| `peers` | `object` | No | Peer configuration sources |
134135

135-
**Example:**
136+
#### Understanding Network Configuration
137+
138+
The `external-ip` and `listen-ip` fields serve distinct purposes in P2P networking:
139+
140+
- **External IP** (`external-ip`): The public IP address that OTHER nodes use to connect to you. This is what you advertise to peers.
141+
- **Listen IP** (`listen-ip`): Where YOUR node binds/listens for incoming connections. Defaults to `0.0.0.0` (all interfaces) for maximum compatibility.
142+
143+
**Cloud Deployment (GCP, AWS, Azure) - Default Configuration:**
144+
145+
When deploying on cloud providers, you only need to specify your VM's **public IP** for `external-ip`:
146+
147+
```yaml
148+
p2p:
149+
external-ip: "34.73.187.192" # Your VM's public IP
150+
# listen-ip defaults to "0.0.0.0" - no need to specify
151+
ports:
152+
cometbft: 26656
153+
resolver: 26655
154+
```
155+
156+
This configuration will:
157+
- Bind services to `0.0.0.0` (listens on all network interfaces) by default
158+
- Advertise your public IP to peers for incoming connections
159+
- Work correctly with cloud networking where public IPs are not directly bound to interfaces
160+
161+
**Cloud Deployment with Specific Private IP (Advanced):**
162+
163+
If you need to bind to a specific private IP instead of all interfaces:
164+
165+
```yaml
166+
p2p:
167+
external-ip: "34.73.187.192" # Your VM's public IP
168+
listen-ip: "10.128.0.5" # Your VM's private IP (optional)
169+
ports:
170+
cometbft: 26656
171+
resolver: 26655
172+
```
173+
174+
This is useful for:
175+
- Multi-network VMs where you want to control which interface listens
176+
- Security policies requiring binding to specific IPs
177+
- Advanced network configurations with multiple interfaces
178+
179+
**Local Development:**
180+
181+
For local testing, use localhost:
182+
183+
```yaml
184+
p2p:
185+
external-ip: "127.0.0.1" # Localhost (default)
186+
ports:
187+
cometbft: 26656
188+
resolver: 26655
189+
```
190+
191+
**With Peer Discovery:**
136192

137193
```yaml
138194
p2p:
139195
external-ip: "192.168.1.100"
140196
ports:
141197
cometbft: 26656
142-
resolver: 26657
198+
resolver: 26655
143199
peers:
144200
peer-files:
145201
- "/path/to/peer1.json"
146202
- "/path/to/peer2.json"
147203
```
148204

205+
> **Note:** The node automatically handles the distinction between listen addresses (what to bind to) and external addresses (what to advertise). By default, services bind to `0.0.0.0` (all interfaces) and advertise the `external-ip` to peers. For most use cases, you only need to specify `external-ip`. The `listen-ip` option is available for advanced configurations where you need to control the specific interface for binding.
206+
149207
---
150208

151209
### cometbft-overrides

faucet/.dockerignore

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Dependencies
2+
node_modules/
3+
frontend/node_modules/
4+
backend/node_modules/
5+
6+
# Build output (frontend will be built in Docker)
7+
frontend/dist/
8+
9+
# Development files
10+
.env
11+
.env.local
12+
.env.*.local
13+
14+
# Git
15+
.git/
16+
.gitignore
17+
18+
# IDE
19+
.vscode/
20+
.idea/
21+
*.swp
22+
*.swo
23+
24+
# OS
25+
.DS_Store
26+
Thumbs.db
27+
28+
# Logs
29+
logs/
30+
*.log
31+
npm-debug.log*
32+
33+
# Documentation
34+
README.md
35+
docs/
36+
37+
# Testing
38+
*.test.js
39+
*.spec.js
40+
test/
41+
coverage/
42+

faucet/.env.example

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# IPC Faucet Configuration
2+
# Copy this file to .env and fill in your actual values
3+
# NEVER commit .env to version control
4+
5+
# Private key for the faucet wallet (without 0x prefix or with it)
6+
# This account will distribute funds to requesters
7+
# Example: PRIVATE_KEY=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
8+
PRIVATE_KEY=0xYOUR_PRIVATE_KEY_HERE
9+
10+
# RPC URL for the IPC subnet
11+
# Example: http://localhost:8545 for local development
12+
# Example: http://node-1.test.ipc.space:8545 for test network
13+
RPC_URL=http://localhost:8545
14+
15+
# Amount to send per faucet request (in native token units)
16+
FAUCET_AMOUNT=10
17+
18+
# Rate limiting window in milliseconds (86400000 = 24 hours)
19+
RATE_LIMIT_WINDOW=86400000
20+
21+
# Maximum number of requests per address within the rate limit window
22+
RATE_LIMIT_MAX=3
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Check Pending Transactions for IPC Faucet
5+
*
6+
* Helps diagnose stuck transactions
7+
*/
8+
9+
import { ethers } from 'ethers'
10+
import dotenv from 'dotenv'
11+
import { fileURLToPath } from 'url'
12+
import { dirname, join } from 'path'
13+
14+
const __filename = fileURLToPath(import.meta.url)
15+
const __dirname = dirname(__filename)
16+
17+
// Load environment variables from parent directory
18+
dotenv.config({ path: join(__dirname, '..', '.env') })
19+
20+
const RPC_URL = process.env.RPC_URL || 'http://node-1.test.ipc.space:8545'
21+
const PRIVATE_KEY = process.env.PRIVATE_KEY
22+
23+
async function checkPendingTransactions() {
24+
try {
25+
if (!PRIVATE_KEY) {
26+
console.error('❌ Error: PRIVATE_KEY not found in .env file')
27+
process.exit(1)
28+
}
29+
30+
console.log('\n🔍 Checking for pending transactions...\n')
31+
console.log(`RPC: ${RPC_URL}\n`)
32+
33+
const provider = new ethers.JsonRpcProvider(RPC_URL)
34+
const wallet = new ethers.Wallet(PRIVATE_KEY, provider)
35+
36+
console.log(`Wallet Address: ${wallet.address}\n`)
37+
38+
// Get current nonce from network (includes pending)
39+
const pendingNonce = await provider.getTransactionCount(wallet.address, 'pending')
40+
41+
// Get confirmed nonce
42+
const confirmedNonce = await provider.getTransactionCount(wallet.address, 'latest')
43+
44+
// Get balance
45+
const balance = await provider.getBalance(wallet.address)
46+
const balanceFIL = ethers.formatEther(balance)
47+
48+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
49+
console.log('📊 Wallet Status')
50+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
51+
console.log(`Balance: ${balanceFIL} tFIL`)
52+
console.log(`Confirmed Nonce: ${confirmedNonce}`)
53+
console.log(`Pending Nonce: ${pendingNonce}`)
54+
console.log(`Stuck Transactions: ${pendingNonce - confirmedNonce}`)
55+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
56+
57+
if (pendingNonce === confirmedNonce) {
58+
console.log('✅ No pending transactions!\n')
59+
return
60+
}
61+
62+
console.log('⚠️ Pending transactions detected!\n')
63+
console.log('Checking transaction details...\n')
64+
65+
// Try to get pending transactions
66+
try {
67+
// Note: Not all RPC endpoints support this method
68+
const pendingBlock = await provider.send('eth_getBlockByNumber', ['pending', true])
69+
70+
if (pendingBlock && pendingBlock.transactions) {
71+
const myPendingTxs = pendingBlock.transactions.filter(
72+
tx => tx.from && tx.from.toLowerCase() === wallet.address.toLowerCase()
73+
)
74+
75+
if (myPendingTxs.length > 0) {
76+
console.log(`Found ${myPendingTxs.length} pending transaction(s):\n`)
77+
78+
myPendingTxs.forEach((tx, index) => {
79+
console.log(`Transaction ${index + 1}:`)
80+
console.log(` Hash: ${tx.hash}`)
81+
console.log(` To: ${tx.to}`)
82+
console.log(` Value: ${ethers.formatEther(tx.value)} tFIL`)
83+
console.log(` Nonce: ${parseInt(tx.nonce)}`)
84+
console.log(` Gas Price: ${tx.gasPrice ? ethers.formatUnits(tx.gasPrice, 'gwei') : 'N/A'} Gwei`)
85+
console.log('')
86+
})
87+
}
88+
}
89+
} catch (error) {
90+
console.log('ℹ️ Could not fetch pending transaction details (RPC may not support this)\n')
91+
}
92+
93+
// Check recent confirmed transactions
94+
console.log('📜 Recent confirmed transactions:\n')
95+
96+
try {
97+
const latestBlock = await provider.getBlockNumber()
98+
const fromBlock = Math.max(0, latestBlock - 20) // Check last 20 blocks
99+
100+
let foundTxs = 0
101+
for (let i = latestBlock; i >= fromBlock && foundTxs < 5; i--) {
102+
const block = await provider.getBlock(i, true)
103+
if (block && block.transactions) {
104+
for (const tx of block.transactions) {
105+
if (tx.from && tx.from.toLowerCase() === wallet.address.toLowerCase()) {
106+
const receipt = await provider.getTransactionReceipt(tx.hash)
107+
console.log(`Block ${i}:`)
108+
console.log(` Hash: ${tx.hash}`)
109+
console.log(` To: ${tx.to}`)
110+
console.log(` Value: ${ethers.formatEther(tx.value || 0)} tFIL`)
111+
console.log(` Nonce: ${parseInt(tx.nonce)}`)
112+
console.log(` Status: ${receipt.status === 1 ? '✅ Success' : '❌ Failed'}`)
113+
console.log('')
114+
foundTxs++
115+
if (foundTxs >= 5) break
116+
}
117+
}
118+
}
119+
}
120+
121+
if (foundTxs === 0) {
122+
console.log(' No recent transactions found\n')
123+
}
124+
} catch (error) {
125+
console.log(' Could not fetch recent transactions\n')
126+
}
127+
128+
// Provide solutions
129+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
130+
console.log('💡 Solutions to Clear Stuck Transactions')
131+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')
132+
133+
console.log('Option 1: Wait for transactions to be mined')
134+
console.log(' - Transactions may just need more time\n')
135+
136+
console.log('Option 2: Speed up with higher gas (if RPC supports)')
137+
console.log(' - Use node scripts/speed-up-tx.js\n')
138+
139+
console.log('Option 3: Cancel stuck transactions')
140+
console.log(' - Send 0 value tx to yourself with same nonce')
141+
console.log(' - Use node scripts/cancel-tx.js <nonce>\n')
142+
143+
console.log('Option 4: Check gas price settings')
144+
console.log(' - Ensure faucet is using adequate gas price')
145+
console.log(' - Check network congestion\n')
146+
147+
// Get network gas info
148+
try {
149+
const feeData = await provider.getFeeData()
150+
console.log('Current Network Gas Prices:')
151+
if (feeData.gasPrice) {
152+
console.log(` Gas Price: ${ethers.formatUnits(feeData.gasPrice, 'gwei')} Gwei`)
153+
}
154+
if (feeData.maxFeePerGas) {
155+
console.log(` Max Fee: ${ethers.formatUnits(feeData.maxFeePerGas, 'gwei')} Gwei`)
156+
}
157+
if (feeData.maxPriorityFeePerGas) {
158+
console.log(` Max Priority Fee: ${ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei')} Gwei`)
159+
}
160+
console.log('')
161+
} catch (error) {
162+
console.log(' Could not fetch gas prices\n')
163+
}
164+
165+
} catch (error) {
166+
console.error('❌ Error:', error.message)
167+
process.exit(1)
168+
}
169+
}
170+
171+
checkPendingTransactions()
172+
173+
174+

0 commit comments

Comments
 (0)