chore: migrate luxd HTTP routes /ext -> /v1

Drop the Avalanche-heritage /ext prefix; /v1 is the single canonical route
surface (one way, no backward compat). The node's baseURL is the source of
truth; clients, SDKs, CLI, indexer, maker, genesis, netrunner, and the
k8s/compose/gateway/explorer configs are updated to match.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
zeekay
2026-07-01 11:40:27 -07:00
co-authored by Hanzo Dev
parent 21f2c64a8f
commit 26e21d0a8e
19 changed files with 56 additions and 56 deletions
+5 -5
View File
@@ -561,7 +561,7 @@ When deploying manually (without netrunner):
4. **Get Bootstrap Node ID**:
```bash
curl http://localhost:9630/ext/info | jq .result.nodeID
curl http://localhost:9630/v1/info | jq .result.nodeID
```
5. **Start Remaining Nodes**:
@@ -576,7 +576,7 @@ When deploying manually (without netrunner):
6. **Verify All Chains Bootstrap**:
```bash
# Should show isBootstrapped: true for P, X, C, Q
curl http://localhost:9630/ext/info | jq
curl http://localhost:9630/v1/info | jq
```
### Error Messages and Solutions
@@ -1022,12 +1022,12 @@ User runs: lux network snapshot restore checkpoint1
```bash
# Import Zoo mainnet blocks (~1.3M blocks)
curl -X POST http://127.0.0.1:9650/ext/bc/Zoo/rpc \
curl -X POST http://127.0.0.1:9650/v1/bc/Zoo/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"debug_importRLPBlocks","params":["'$(base64 -i ~/work/lux/state/rlp/zoo-mainnet/zoo-mainnet-200200.rlp)'"],"id":1}'
# Import C-chain mainnet blocks (~1.2G)
curl -X POST http://127.0.0.1:9650/ext/bc/C/rpc \
curl -X POST http://127.0.0.1:9650/v1/bc/C/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"debug_importRLPBlocks","params":["'$(base64 -i ~/work/lux/state/rlp/lux-mainnet/lux-mainnet-96369.rlp)'"],"id":1}'
```
@@ -1087,7 +1087,7 @@ After fixes, successfully deployed Zoo chain on mainnet:
- **Zoo Chain ID**: 200200 (0x30e08)
- **Genesis Hash**: `0x7c548af47de27560779ccc67dda32a540944accc71dac3343da3b9cd18f14933`
- **Treasury**: ~500M ZOO at `0x9011E888251AB053B7bD1cdB598Db4f9DEd94714`
- **RPC Endpoint**: `http://localhost:9630/ext/bc/zoo/rpc`
- **RPC Endpoint**: `http://localhost:9630/v1/bc/zoo/rpc`
### RLP Block Import Limitation
+8 -8
View File
@@ -384,10 +384,10 @@ lux network start & # local
sleep 60
# Verify each network is accessible
curl http://localhost:9630/ext/health # mainnet
curl http://localhost:9640/ext/health # testnet
curl http://localhost:9650/ext/health # devnet
curl http://localhost:9660/ext/health # local
curl http://localhost:9630/v1/health # mainnet
curl http://localhost:9640/v1/health # testnet
curl http://localhost:9650/v1/health # devnet
curl http://localhost:9660/v1/health # local
```
**Test Scenario 2: Deploy Chain to Each Network**
@@ -415,15 +415,15 @@ lux chain list --network local
lux network stop --testnet
# Verify other networks still running
curl http://localhost:9630/ext/health # mainnet - should succeed
curl http://localhost:9640/ext/health # testnet - should fail
curl http://localhost:9650/ext/health # devnet - should succeed
curl http://localhost:9630/v1/health # mainnet - should succeed
curl http://localhost:9640/v1/health # testnet - should fail
curl http://localhost:9650/v1/health # devnet - should succeed
# Restart testnet
lux network start --testnet
# Verify testnet back up
curl http://localhost:9640/ext/health # should succeed
curl http://localhost:9640/v1/health # should succeed
```
### 7.3 Stress Test
+4 -4
View File
@@ -853,12 +853,12 @@ curl -X POST --data '{
"data": "mynewblock",
"length": 32
}
}' -H 'content-type:application/json;' 127.0.0.1:9630/ext/vm/tGas3T58KzdjcJ2iKSyiYsWiqYctRXaPTqBCA11BqEkNg8kPc
}' -H 'content-type:application/json;' 127.0.0.1:9630/v1/vm/tGas3T58KzdjcJ2iKSyiYsWiqYctRXaPTqBCA11BqEkNg8kPc
# in this example,
# "E8isHenre76NMxbJ3munSQatV8GoQ4XKWQg9vD34xMBqEFJGf" is the blockchain Id
# "timestamp" is the blockchain alias
# You can use 127.0.0.1:9630/ext/bc/timestamp or 127.0.0.1:9630/ext/bc/E8isHenre76NMxbJ3munSQatV8GoQ4XKWQg9vD34xMBqEFJGf
# You can use 127.0.0.1:9630/v1/bc/timestamp or 127.0.0.1:9630/v1/bc/E8isHenre76NMxbJ3munSQatV8GoQ4XKWQg9vD34xMBqEFJGf
curl -X POST --data '{
"jsonrpc": "2.0",
"method": "timestampvm.proposeBlock",
@@ -866,7 +866,7 @@ curl -X POST --data '{
"data":"0x6d796e6577626c6f636b0000000000000000000000000000000000000000000014228326"
},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9630/ext/bc/timestamp
}' -H 'content-type:application/json;' 127.0.0.1:9630/v1/bc/timestamp
```
```bash
@@ -875,7 +875,7 @@ curl -X POST --data '{
"method": "timestampvm.getBlock",
"params":{},
"id": 1
}' -H 'content-type:application/json;' 127.0.0.1:9630/ext/bc/timestamp
}' -H 'content-type:application/json;' 127.0.0.1:9630/v1/bc/timestamp
```
## Configuration
+2 -2
View File
@@ -47,8 +47,8 @@ func NewAPIClient(ipAddr string, port uint16) Client {
infoClient := sdkinfo.NewClient(uri)
healthClient := health.NewClient(uri)
adminClient := admin.NewClient(uri)
pindexClient := indexer.NewClient(uri + "/ext/index/P/block")
cindexClient := indexer.NewClient(uri + "/ext/index/C/block")
pindexClient := indexer.NewClient(uri + "/v1/index/P/block")
cindexClient := indexer.NewClient(uri + "/v1/index/C/block")
return &APIClient{
platform: platformClient,
+4 -4
View File
@@ -167,7 +167,7 @@ START_HEIGHT=$(curl -s -X POST --data '{
"params": {},
"id": 1
}' -H 'content-type:application/json' \
http://localhost:9630/ext/bc/P | jq -r '.result.height')
http://localhost:9630/v1/bc/P | jq -r '.result.height')
sleep 60
@@ -177,7 +177,7 @@ END_HEIGHT=$(curl -s -X POST --data '{
"params": {},
"id": 1
}' -H 'content-type:application/json' \
http://localhost:9630/ext/bc/P | jq -r '.result.height')
http://localhost:9630/v1/bc/P | jq -r '.result.height')
BLOCKS_PRODUCED=$((END_HEIGHT - START_HEIGHT))
echo "Blocks produced in 60 seconds: ${BLOCKS_PRODUCED}"
@@ -356,7 +356,7 @@ submit_batch() {
},
\"id\": ${i}
}" -H 'content-type:application/json' \
http://localhost:9630/ext/bc/P > /dev/null
http://localhost:9630/v1/bc/P > /dev/null
} &
done
@@ -567,7 +567,7 @@ export_metrics() {
for i in {1..5}; do
# Get metrics from node
METRICS=$(curl -s http://localhost:$((9630 + (i-1)*2))/ext/metrics)
METRICS=$(curl -s http://localhost:$((9630 + (i-1)*2))/v1/metrics)
TPS=$(echo ${METRICS} | jq -r '.tps')
LATENCY=$(echo ${METRICS} | jq -r '.avgLatency')
+2 -2
View File
@@ -302,7 +302,7 @@ curl -X POST --data '{
"method": "platform.getHeight",
"params": {},
"id": 1
}' -H 'content-type:application/json' ${NODE1_URL}/ext/bc/P
}' -H 'content-type:application/json' ${NODE1_URL}/v1/bc/P
# Clean up
netrunner control stop
@@ -580,5 +580,5 @@ curl -X POST --data '{
"method": "info.isBootstrapped",
"params": {"chain": "P"},
"id": 1
}' -H 'content-type:application/json' http://localhost:9630/ext/info
}' -H 'content-type:application/json' http://localhost:9630/v1/info
```
+6 -6
View File
@@ -84,7 +84,7 @@ HEIGHT=$(curl -s -X POST --data '{
"params": {},
"id": 1
}' -H 'content-type:application/json' \
http://localhost:9630/ext/bc/P | jq -r '.result.height')
http://localhost:9630/v1/bc/P | jq -r '.result.height')
if [ -z "$HEIGHT" ]; then
echo "Failed to get height"
@@ -157,8 +157,8 @@ netrunner control start \
netrunner control health
# Export from X-Chain to C-Chain
X_CHAIN_URL="http://localhost:9630/ext/bc/X"
C_CHAIN_URL="http://localhost:9630/ext/bc/C/lux"
X_CHAIN_URL="http://localhost:9630/v1/bc/X"
C_CHAIN_URL="http://localhost:9630/v1/bc/C/lux"
# Create export transaction
EXPORT_TX=$(curl -X POST --data '{
@@ -266,7 +266,7 @@ curl -X POST --data '{
"params": {},
"id": 1
}' -H 'content-type:application/json' \
http://localhost:9630/ext/bc/${BLOCKCHAIN_ID}
http://localhost:9630/v1/bc/${BLOCKCHAIN_ID}
```
## Performance Testing
@@ -329,7 +329,7 @@ for i in {1..100}; do
"params": {},
"id": 1
}' -H 'content-type:application/json' \
http://localhost:9630/ext/info > /dev/null
http://localhost:9630/v1/info > /dev/null
END=$(date +%s%N)
LATENCY=$((($END - $START) / 1000000))
@@ -426,7 +426,7 @@ for i in {1..100}; do
"params": {},
"id": 1
}' -H 'content-type:application/json' \
http://localhost:9630/ext/bc/P
http://localhost:9630/v1/bc/P
sleep 1
done
+6 -6
View File
@@ -222,7 +222,7 @@ func (e *LuxEngine) WSEndpoint() string {
return ""
}
// Lux uses same port for HTTP and WS
return fmt.Sprintf("ws://localhost:%d/ext/bc/C/ws", e.config.HTTPPort)
return fmt.Sprintf("ws://localhost:%d/v1/bc/C/ws", e.config.HTTPPort)
}
func (e *LuxEngine) P2PEndpoint() string {
@@ -309,7 +309,7 @@ func (e *LuxEngine) callRPC(ctx context.Context, endpoint string, method string,
}
func (e *LuxEngine) getNodeID(ctx context.Context) (string, error) {
result, err := e.callRPC(ctx, "/ext/info", "info.getNodeID", struct{}{})
result, err := e.callRPC(ctx, "/v1/info", "info.getNodeID", struct{}{})
if err != nil {
return "", err
}
@@ -325,7 +325,7 @@ func (e *LuxEngine) getNodeID(ctx context.Context) (string, error) {
}
func (e *LuxEngine) getBlockchainID(ctx context.Context, alias string) (ids.ID, error) {
result, err := e.callRPC(ctx, "/ext/info", "info.getBlockchainID", map[string]string{"alias": alias})
result, err := e.callRPC(ctx, "/v1/info", "info.getBlockchainID", map[string]string{"alias": alias})
if err != nil {
return ids.Empty, err
}
@@ -341,7 +341,7 @@ func (e *LuxEngine) getBlockchainID(ctx context.Context, alias string) (ids.ID,
}
func (e *LuxEngine) getHealth(ctx context.Context) (bool, error) {
result, err := e.callRPC(ctx, "/ext/health", "health.health", struct{}{})
result, err := e.callRPC(ctx, "/v1/health", "health.health", struct{}{})
if err != nil {
return false, err
}
@@ -357,7 +357,7 @@ func (e *LuxEngine) getHealth(ctx context.Context) (bool, error) {
}
func (e *LuxEngine) getPeerCount(ctx context.Context) (int, error) {
result, err := e.callRPC(ctx, "/ext/info", "info.peers", struct{}{})
result, err := e.callRPC(ctx, "/v1/info", "info.peers", struct{}{})
if err != nil {
return 0, err
}
@@ -382,7 +382,7 @@ func (e *LuxEngine) getPeerCount(ctx context.Context) (int, error) {
}
func (e *LuxEngine) getNodeVersion(ctx context.Context) (string, error) {
result, err := e.callRPC(ctx, "/ext/info", "info.getNodeVersion", struct{}{})
result, err := e.callRPC(ctx, "/v1/info", "info.getNodeVersion", struct{}{})
if err != nil {
return "", err
}
+4 -4
View File
@@ -269,12 +269,12 @@ func (e *Engine) ChainID() ids.ID {
// RPCEndpoint returns the external RPC endpoint
func (e *Engine) RPCEndpoint() string {
return fmt.Sprintf("http://luxd.%s.svc.cluster.local:%d/ext/bc/C/rpc", e.namespace, e.httpPort)
return fmt.Sprintf("http://luxd.%s.svc.cluster.local:%d/v1/bc/C/rpc", e.namespace, e.httpPort)
}
// WSEndpoint returns the external WebSocket endpoint
func (e *Engine) WSEndpoint() string {
return fmt.Sprintf("ws://luxd.%s.svc.cluster.local:%d/ext/bc/C/ws", e.namespace, e.httpPort)
return fmt.Sprintf("ws://luxd.%s.svc.cluster.local:%d/v1/bc/C/ws", e.namespace, e.httpPort)
}
// P2PEndpoint returns the staking/P2P endpoint
@@ -483,7 +483,7 @@ func (e *Engine) createStatefulSet(ctx context.Context, config *engines.NodeConf
LivenessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/ext/health",
Path: "/v1/health",
Port: intstr.FromInt(int(e.httpPort)),
},
},
@@ -493,7 +493,7 @@ func (e *Engine) createStatefulSet(ctx context.Context, config *engines.NodeConf
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/ext/info",
Path: "/v1/info",
Port: intstr.FromInt(int(e.httpPort)),
},
},
+1 -1
View File
@@ -230,7 +230,7 @@ func (e *LuxEngine) WSEndpoint() string {
}
if e.config.WSPort == 0 {
// Lux uses same port for HTTP and WS
return fmt.Sprintf("ws://localhost:%d/ext/bc/C/ws", e.config.HTTPPort)
return fmt.Sprintf("ws://localhost:%d/v1/bc/C/ws", e.config.HTTPPort)
}
return fmt.Sprintf("ws://localhost:%d", e.config.WSPort)
}
+1 -1
View File
@@ -149,7 +149,7 @@ func run(logger log.Logger, binaryPath string) error {
for name, node := range nodes {
logger.Info(" Node available",
"name", name,
"url", fmt.Sprintf("%s/ext/bc/dex", node.GetURL()),
"url", fmt.Sprintf("%s/v1/bc/dex", node.GetURL()),
)
}
+1 -1
View File
@@ -248,7 +248,7 @@ func run(logger log.Logger, binaryPath string) error {
logger.Info("Zoo TESTNET blockchain created!", "chainIDs", chainIDStrs)
fmt.Printf("\n✅ Zoo TESTNET blockchain created successfully!\n")
fmt.Printf(" Chain ID: %s\n", chainIDs[0])
fmt.Printf(" RPC: http://localhost:9640/ext/bc/zootest/rpc\n\n")
fmt.Printf(" RPC: http://localhost:9640/v1/bc/zootest/rpc\n\n")
logger.Info("TESTNET will run until you CTRL + C to exit...")
<-closedOnShutdownCh
+2 -2
View File
@@ -287,7 +287,7 @@ func (ln *localNetwork) waitForChainsDiscoveredOnAllNodes(
for time.Now().Before(deadline) {
attemptCount++
// Try to query the chain RPC endpoint with eth_chainId - if it responds, the chain is discovered
chainRPCURL := fmt.Sprintf("%s/ext/bc/%s/rpc", node.GetURL(), blockchainID)
chainRPCURL := fmt.Sprintf("%s/v1/bc/%s/rpc", node.GetURL(), blockchainID)
// Use a proper JSON-RPC request body to ensure the EVM endpoint responds
jsonRPCBody := strings.NewReader(`{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}`)
req, err := http.NewRequestWithContext(ctx, "POST", chainRPCURL, jsonRPCBody)
@@ -366,7 +366,7 @@ func (ln *localNetwork) waitForChainsDiscoveredOnAllNodes(
// Provide more diagnostic information
var lastErr string
// Try one more request to get the exact error
chainRPCURL := fmt.Sprintf("%s/ext/bc/%s/rpc", node.GetURL(), blockchainID)
chainRPCURL := fmt.Sprintf("%s/v1/bc/%s/rpc", node.GetURL(), blockchainID)
jsonRPCBody := strings.NewReader(`{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}`)
req, _ := http.NewRequestWithContext(ctx, "POST", chainRPCURL, jsonRPCBody)
if req != nil {
+3 -3
View File
@@ -21,7 +21,7 @@ $LUXD --network-id=1337 --genesis-file=$GENESIS --http-port=9630 --staking-port=
--http-host=0.0.0.0 --public-ip=127.0.0.1 > $BASE/n1.log 2>&1 &
sleep 15
N1ID=$(curl -s http://localhost:9630/ext/info -d '{"jsonrpc":"2.0","id":1,"method":"info.getNodeID"}' -H 'content-type:application/json;' | jq -r '.result.nodeID')
N1ID=$(curl -s http://localhost:9630/v1/info -d '{"jsonrpc":"2.0","id":1,"method":"info.getNodeID"}' -H 'content-type:application/json;' | jq -r '.result.nodeID')
[ -z "$N1ID" ] && echo "ERROR: No Node ID" && tail -10 $BASE/n1.log && exit 1
echo "Node 1 ID: $N1ID"
@@ -39,8 +39,8 @@ done
echo "Waiting 50s for bootstrap..."
sleep 50
PEERS=$(curl -s http://localhost:9630/ext/info -d '{"jsonrpc":"2.0","id":1,"method":"info.peers"}' -H 'content-type:application/json;' | jq -r '.result.numPeers')
CBS=$(curl -s http://localhost:9630/ext/info -d '{"jsonrpc":"2.0","id":1,"method":"info.isBootstrapped","params":{"chain":"C"}}' -H 'content-type:application/json;' | jq -r '.result.isBootstrapped')
PEERS=$(curl -s http://localhost:9630/v1/info -d '{"jsonrpc":"2.0","id":1,"method":"info.peers"}' -H 'content-type:application/json;' | jq -r '.result.numPeers')
CBS=$(curl -s http://localhost:9630/v1/info -d '{"jsonrpc":"2.0","id":1,"method":"info.isBootstrapped","params":{"chain":"C"}}' -H 'content-type:application/json;' | jq -r '.result.isBootstrapped')
echo "Peers: $PEERS/4, C-Chain: $CBS"
[ "$CBS" = "true" ] && echo "✅ Network ready for transactions!"
+2 -2
View File
@@ -47,7 +47,7 @@ scrape_configs:
labels:
alias: machine
- job_name: node
metrics_path: /ext/metrics
metrics_path: /v1/metrics
static_configs:
- targets:
`
@@ -826,7 +826,7 @@ func (lc *localNetwork) updateChainInfo(ctx context.Context) error {
if nodeInfo.Paused {
continue
}
lc.log.Info(fmt.Sprintf(log.LightBlue.Wrap("[blockchain RPC for %q] \"%s/ext/bc/%s\""), chainInfo.info.VmId, nodeInfo.GetUri(), chainID))
lc.log.Info(fmt.Sprintf(log.LightBlue.Wrap("[blockchain RPC for %q] \"%s/v1/bc/%s\""), chainInfo.info.VmId, nodeInfo.GetUri(), chainID))
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ engines:
depends_on:
- lux-l1
extra:
l1-rpc: http://localhost:9630/ext/bc/C/rpc
l1-rpc: http://localhost:9630/v1/bc/C/rpc
sequencer: true
sequencer-l1-confs: 0
+1 -1
View File
@@ -62,7 +62,7 @@ func newConsensusTestEnv(t *testing.T) *consensusTestEnv {
for _, n := range allNodes {
nodes = append(nodes, n)
names = append(names, n.GetName())
rpcURL := fmt.Sprintf("http://%s:%d/ext/bc/C/rpc", n.GetHost(), n.GetAPIPort())
rpcURL := fmt.Sprintf("http://%s:%d/v1/bc/C/rpc", n.GetHost(), n.GetAPIPort())
c, err := ethclient.Dial(rpcURL)
require.NoError(err, "failed to dial ethclient for %s", n.GetName())
clients = append(clients, c)
+1 -1
View File
@@ -92,7 +92,7 @@ func newDEXTestEnv(t *testing.T) *dexTestEnv {
var clients []*ethclient.Client
for _, n := range allNodes {
nodes = append(nodes, n)
rpcURL := fmt.Sprintf("http://%s:%d/ext/bc/C/rpc", n.GetHost(), n.GetAPIPort())
rpcURL := fmt.Sprintf("http://%s:%d/v1/bc/C/rpc", n.GetHost(), n.GetAPIPort())
c, err := ethclient.Dial(rpcURL)
require.NoError(err, "failed to dial ethclient for %s", n.GetName())
clients = append(clients, c)
+2 -2
View File
@@ -13,7 +13,7 @@
// - A running luxd node with GPU EVM enabled (GPU_EVM=1)
// - Or a local anvil instance on the default RPC endpoint
//
// Set RPC_URL to override the default endpoint (http://127.0.0.1:9650/ext/bc/C/rpc).
// Set RPC_URL to override the default endpoint (http://127.0.0.1:9650/v1/bc/C/rpc).
package tests
import (
@@ -41,7 +41,7 @@ import (
// --- Configuration ---
const (
defaultRPCURL = "http://127.0.0.1:9650/ext/bc/C/rpc"
defaultRPCURL = "http://127.0.0.1:9650/v1/bc/C/rpc"
txTimeout = 30 * time.Second
blockTimeout = 60 * time.Second
)