Skip to content

Commit cf39baa

Browse files
Copilotjulienrbrt
andcommitted
Complete DA height polling implementation with CLI support and documentation
Co-authored-by: julienrbrt <29894366+julienrbrt@users.noreply.github.com>
1 parent e586987 commit cf39baa

File tree

5 files changed

+298
-0
lines changed

5 files changed

+298
-0
lines changed

docs/DA_HEIGHT_POLLING.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# DA Height Polling for Genesis Initialization
2+
3+
This feature allows non-aggregator nodes to automatically poll the DA (Data Availability) height from an aggregator node during genesis initialization, eliminating the need to manually configure the DA start height.
4+
5+
## How It Works
6+
7+
When a non-aggregator node starts with a genesis configuration where `genesis_da_start_height` is zero (or equivalently, `GenesisDAStartTime` is zero time), it will automatically poll the configured aggregator endpoint to get the current DA height before proceeding with chain initialization.
8+
9+
## Configuration
10+
11+
### For Non-Aggregator Nodes
12+
13+
Add the aggregator endpoint to your configuration:
14+
15+
```yaml
16+
da:
17+
aggregator_endpoint: http://aggregator-node:7331
18+
```
19+
20+
Or via command line:
21+
22+
```bash
23+
evnode start --evnode.da.aggregator_endpoint=http://aggregator-node:7331
24+
```
25+
26+
### For Aggregator Nodes
27+
28+
No additional configuration needed. Aggregator nodes automatically expose the `/da/height` endpoint when DA visualization is enabled.
29+
30+
## Genesis File Setup
31+
32+
For non-aggregator nodes to trigger automatic DA height polling, the genesis file should have a zero DA start time:
33+
34+
```json
35+
{
36+
"chain_id": "my-chain",
37+
"genesis_da_start_height": "1970-01-01T00:00:00Z",
38+
"initial_height": 1,
39+
"proposer_address": "..."
40+
}
41+
```
42+
43+
## Workflow Example
44+
45+
1. **Aggregator Node Setup:**
46+
```bash
47+
# Start an aggregator node
48+
evnode start --evnode.node.aggregator
49+
```
50+
51+
2. **Non-Aggregator Node Setup:**
52+
```bash
53+
# Initialize non-aggregator node with aggregator endpoint
54+
evnode init --evnode.da.aggregator_endpoint=http://aggregator:7331
55+
56+
# Start non-aggregator node (will automatically poll DA height)
57+
evnode start
58+
```
59+
60+
## API Endpoint
61+
62+
Aggregator nodes expose the following endpoint:
63+
64+
**GET `/da/height`**
65+
66+
Returns the current DA layer height:
67+
68+
```json
69+
{
70+
"height": 1234,
71+
"timestamp": "2023-11-15T10:30:15Z"
72+
}
73+
```
74+
75+
## Polling Behavior
76+
77+
- **Timeout:** 5 minutes maximum
78+
- **Interval:** 5 seconds between polls
79+
- **Condition:** Only triggers when `genesis_da_start_height` is zero and node is not an aggregator
80+
- **Fallback:** If no aggregator endpoint is configured, uses current time
81+
82+
## Error Handling
83+
84+
If the polling fails:
85+
- The node will log the error and fail to start
86+
- Check that the aggregator endpoint is reachable
87+
- Verify the aggregator node has the DA height endpoint available
88+
- Ensure the aggregator node is running and has advanced beyond height 0
89+
90+
## Use Cases
91+
92+
- **Simplified Deployment:** No need to manually coordinate DA start heights
93+
- **Dynamic Chain Launching:** Non-aggregator nodes can join automatically
94+
- **Development Testing:** Easy setup for multi-node test networks

pkg/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ func AddFlags(cmd *cobra.Command) {
302302
cmd.Flags().String(FlagDASubmitOptions, def.DA.SubmitOptions, "DA submit options")
303303
cmd.Flags().Uint64(FlagDAMempoolTTL, def.DA.MempoolTTL, "number of DA blocks until transaction is dropped from the mempool")
304304
cmd.Flags().Int(FlagDAMaxSubmitAttempts, def.DA.MaxSubmitAttempts, "maximum number of attempts to submit data to the DA layer before giving up")
305+
cmd.Flags().String(FlagDAAggregatorEndpoint, def.DA.AggregatorEndpoint, "HTTP endpoint of an aggregator node to poll for DA height during genesis initialization (only used by non-aggregator nodes when genesis DA start time is zero)")
305306

306307
// P2P configuration flags
307308
cmd.Flags().String(FlagP2PListenAddress, def.P2P.ListenAddress, "P2P listen address (host:port)")

pkg/rpc/server/templates/da_visualization.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,24 @@ <h4><span class="method method-get">GET</span> /da/health</h4>
153153
<li><code>unknown</code> - Insufficient data to determine health</li>
154154
</ul>
155155
</div>
156+
157+
<div class="api-endpoint">
158+
<h4><span class="method method-get">GET</span> /da/height</h4>
159+
<p>Returns the current DA layer height. Used by non-aggregator nodes for genesis polling.</p>
160+
<p><strong>Note:</strong> Only available on aggregator nodes.</p>
161+
<p><strong>Example:</strong> <code>curl http://localhost:8080/da/height</code></p>
162+
<div class="api-response">
163+
<strong>Response:</strong>
164+
<pre>{
165+
"height": 1234,
166+
"timestamp": "2023-11-15T10:30:15Z"
167+
}</pre>
168+
</div>
169+
<p style="margin-top: 10px; font-size: 13px; color: #666;">
170+
This endpoint is used during genesis initialization when non-aggregator nodes need to poll
171+
the DA height from an aggregator node (when <code>genesis_da_start_height</code> is zero).
172+
</p>
173+
</div>
156174
</div>
157175
{{end}}
158176

scripts/demo_da_height_polling.sh

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/bin/bash
2+
3+
# Demo script for DA height polling functionality
4+
# This script demonstrates how to use the new DA height polling feature
5+
6+
set -e
7+
8+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
10+
TESTAPP_BIN="$PROJECT_ROOT/build/testapp"
11+
TEMP_DIR=$(mktemp -d)
12+
13+
echo "🚀 Demo: DA Height Polling for Genesis Initialization"
14+
echo "=================================================="
15+
16+
# Ensure testapp is built
17+
if [ ! -f "$TESTAPP_BIN" ]; then
18+
echo "❌ testapp binary not found at $TESTAPP_BIN"
19+
echo "Please run 'make build' first"
20+
exit 1
21+
fi
22+
23+
echo "✅ testapp binary found at $TESTAPP_BIN"
24+
25+
# Create directories for two nodes
26+
AGG_HOME="$TEMP_DIR/aggregator"
27+
FULL_HOME="$TEMP_DIR/fullnode"
28+
29+
echo "📁 Setting up temporary directories:"
30+
echo " Aggregator: $AGG_HOME"
31+
echo " Full node: $FULL_HOME"
32+
33+
# Initialize aggregator node
34+
echo ""
35+
echo "🔧 Initializing aggregator node..."
36+
"$TESTAPP_BIN" init \
37+
--home="$AGG_HOME" \
38+
--chain_id=demo-chain \
39+
--evnode.node.aggregator \
40+
--evnode.signer.passphrase=test123
41+
42+
echo "✅ Aggregator initialized"
43+
44+
# Initialize full node with aggregator endpoint
45+
echo ""
46+
echo "🔧 Initializing full node with aggregator endpoint..."
47+
"$TESTAPP_BIN" init \
48+
--home="$FULL_HOME" \
49+
--chain_id=demo-chain \
50+
--evnode.da.aggregator_endpoint=http://localhost:7331
51+
52+
echo "✅ Full node initialized"
53+
54+
# Show the configuration files
55+
echo ""
56+
echo "📄 Full node configuration (showing DA config):"
57+
echo "------------------------------------------------"
58+
grep -A 10 "^da:" "$FULL_HOME/config/evnode.yaml" || echo "❌ Could not find DA config"
59+
60+
echo ""
61+
echo "🎯 Key points demonstrated:"
62+
echo "1. ✅ New --evnode.da.aggregator_endpoint flag is available"
63+
echo "2. ✅ Configuration is properly saved to evnode.yaml"
64+
echo "3. ✅ Non-aggregator nodes can be configured to poll DA height"
65+
66+
echo ""
67+
echo "🧪 To test the actual polling (requires running nodes):"
68+
echo " 1. Start aggregator: $TESTAPP_BIN start --home=$AGG_HOME --evnode.signer.passphrase=test123"
69+
echo " 2. Test DA height endpoint: curl http://localhost:7331/da/height"
70+
echo " 3. Start full node: $TESTAPP_BIN start --home=$FULL_HOME"
71+
echo " (The full node will automatically poll DA height if genesis DA start time is zero)"
72+
73+
# Cleanup function
74+
cleanup() {
75+
echo ""
76+
echo "🧹 Cleaning up temporary files..."
77+
rm -rf "$TEMP_DIR"
78+
echo "✅ Cleanup complete"
79+
}
80+
81+
# Register cleanup function
82+
trap cleanup EXIT
83+
84+
echo ""
85+
echo "✨ Demo completed successfully!"
86+
echo " Temp directory: $TEMP_DIR (will be cleaned up on exit)"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package integration
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"time"
9+
10+
coreda "github.com/evstack/ev-node/core/da"
11+
"github.com/evstack/ev-node/pkg/config"
12+
"github.com/evstack/ev-node/pkg/da_client"
13+
genesispkg "github.com/evstack/ev-node/pkg/genesis"
14+
"github.com/evstack/ev-node/pkg/rpc/server"
15+
"github.com/rs/zerolog"
16+
)
17+
18+
// TestDAHeightPollingIntegration tests the end-to-end DA height polling functionality
19+
func TestDAHeightPollingIntegration(t *testing.T) {
20+
logger := zerolog.Nop()
21+
22+
// Step 1: Create a simulated aggregator with DummyDA
23+
dummyDA := coreda.NewDummyDA(1024, 0, 0, 50*time.Millisecond)
24+
dummyDA.StartHeightTicker()
25+
defer dummyDA.StopHeightTicker()
26+
27+
// Wait for DA height to increment to > 0
28+
time.Sleep(100 * time.Millisecond)
29+
30+
// Create aggregator's DA visualization server
31+
aggregatorServer := server.NewDAVisualizationServer(dummyDA, logger, true)
32+
server.SetDAVisualizationServer(aggregatorServer) // Set the global server instance
33+
34+
// Set up HTTP test server to simulate the aggregator using the public HTTP endpoints
35+
mux := http.NewServeMux()
36+
server.RegisterCustomHTTPEndpoints(mux) // This will register all the endpoints including /da/height
37+
testAggregator := httptest.NewServer(mux)
38+
defer testAggregator.Close()
39+
40+
// Step 2: Test DA client polling functionality
41+
client := da_client.NewClient()
42+
43+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
44+
defer cancel()
45+
46+
height, err := client.GetDAHeight(ctx, testAggregator.URL)
47+
if err != nil {
48+
t.Fatalf("Failed to get DA height from aggregator: %v", err)
49+
}
50+
51+
if height == 0 {
52+
t.Errorf("Expected DA height > 0, got %d", height)
53+
}
54+
55+
t.Logf("Successfully polled DA height: %d", height)
56+
57+
// Step 3: Test polling functionality (simulates genesis initialization)
58+
pollHeight, err := client.PollDAHeight(ctx, testAggregator.URL, 100*time.Millisecond)
59+
if err != nil {
60+
t.Fatalf("Failed to poll DA height: %v", err)
61+
}
62+
63+
if pollHeight == 0 {
64+
t.Errorf("Expected polled DA height > 0, got %d", pollHeight)
65+
}
66+
67+
t.Logf("Successfully polled DA height via polling: %d", pollHeight)
68+
}
69+
70+
// TestGenesisPollingConfig tests the configuration and genesis modification logic
71+
func TestGenesisPollingConfig(t *testing.T) {
72+
// Test configuration with aggregator endpoint
73+
cfg := config.DefaultConfig
74+
cfg.DA.AggregatorEndpoint = "http://localhost:8080"
75+
cfg.Node.Aggregator = false // This is a non-aggregator node
76+
77+
// Verify the configuration is set correctly
78+
if cfg.DA.AggregatorEndpoint == "" {
79+
t.Error("Expected aggregator endpoint to be set")
80+
}
81+
82+
if cfg.Node.Aggregator {
83+
t.Error("Expected node to be non-aggregator for this test")
84+
}
85+
86+
// Test genesis with zero start time (simulating the condition that triggers polling)
87+
genesis := genesispkg.Genesis{
88+
ChainID: "test-chain",
89+
GenesisDAStartTime: time.Time{}, // Zero time
90+
InitialHeight: 1,
91+
ProposerAddress: []byte("test-proposer"),
92+
}
93+
94+
if !genesis.GenesisDAStartTime.IsZero() {
95+
t.Error("Expected genesis DA start time to be zero")
96+
}
97+
98+
t.Log("Configuration test passed - ready for DA height polling")
99+
}

0 commit comments

Comments
 (0)