Restore the chain prefix the running nodes answer

The rename to /v1/chain is real in luxd source, but the deployed fleet has not
taken it yet: probed just now, api.lux.network, api.lux-test.network,
api.zoo.network and api.hanzo.network each answer 200 on /v1/bc/C/rpc and 404 on
/v1/chain/C/rpc. Composing /v1/chain against those hosts reaches nothing, which
is the failure the rename set out to prevent.

The move belongs with the node redeploy, not ahead of it.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-30 21:21:32 -07:00
co-authored by Hanzo Dev
parent 6c2b79d070
commit a5edffeba2
13 changed files with 23 additions and 23 deletions
+3 -3
View File
@@ -74,15 +74,15 @@ jobs:
include:
- net: mainnet
manifest: dexvm/registry/manifests/assets.mainnet.json
evm_rpc: https://api.lux.network/v1/chain/C/rpc
evm_rpc: https://api.lux.network/v1/bc/C/rpc
api_base: https://api.lux.network
- net: testnet
manifest: dexvm/registry/manifests/assets.testnet.json
evm_rpc: https://api.lux-test.network/v1/chain/C/rpc
evm_rpc: https://api.lux-test.network/v1/bc/C/rpc
api_base: https://api.lux-test.network
- net: devnet
manifest: dexvm/registry/manifests/assets.devnet.json
evm_rpc: https://api.lux-dev.network/v1/chain/C/rpc
evm_rpc: https://api.lux-dev.network/v1/bc/C/rpc
api_base: https://api.lux-dev.network
steps:
- uses: actions/checkout@v4
+2 -2
View File
@@ -10,14 +10,14 @@ import (
"testing"
)
// The node mounts each CreateHandlers key at /v1/chain/<chainID>+key and matches
// The node mounts each CreateHandlers key at /v1/bc/<chainID>+key and matches
// that path EXACTLY, then hands the handler the request on the path it arrived
// on. So a handler that dispatches on r.URL.Path is unreachable: no request can
// ever carry the bare path it looks for, and no path below the mount is routed
// at all. This asserts the only property that makes an endpoint reachable —
// the handler answers at the path the node delivers.
func TestHandlersAnswerAtTheMountedPath(t *testing.T) {
const base = "/v1/chain/2NXHomv4gbu8i6JTqALHvcbdeb8gb8r7jzbxqXvqZRd1UzorhF"
const base = "/v1/bc/2NXHomv4gbu8i6JTqALHvcbdeb8gb8r7jzbxqXvqZRd1UzorhF"
handlers, err := (&VM{}).CreateHandlers(context.Background())
if err != nil {
+1 -1
View File
@@ -18,7 +18,7 @@ type Service struct {
// Routes returns one handler per endpoint.
//
// The node mounts each key under /v1/chain/<chainID> and matches that full path
// The node mounts each key under /v1/bc/<chainID> and matches that full path
// EXACTLY, then hands the handler the request with the path it arrived on. A
// handler therefore never sees a path it can dispatch on, and no path below a
// mount is routable at all. The key IS the route — the same shape bridgevm and
+1 -1
View File
@@ -447,7 +447,7 @@ func (vm *VM) Shutdown(ctx context.Context) error {
//
// The node mounts each KEY as a route and delivers the request on the path it
// arrived on, so a handler that dispatches on r.URL.Path never matches: it is
// asked for /v1/chain/<chainID>/rpc and looks for /stats. A flat map of endpoints to
// asked for /v1/bc/<chainID>/rpc and looks for /stats. A flat map of endpoints to
// handlers that answer AT their mount is the simplest shape that works, and it is
// what this VM uses.
//
+1 -1
View File
@@ -276,7 +276,7 @@ func TestVMCreateHandlers(t *testing.T) {
handlers, err := vm.CreateHandlers(context.Background())
require.NoError(err)
// Each key is a route the node mounts under /v1/chain/<chainID>, so the set of
// Each key is a route the node mounts under /v1/bc/<chainID>, so the set of
// keys IS the chain's public surface. Collapsing them behind one key hides
// every endpoint but the one named.
keys := make([]string, 0, len(handlers))
+1 -1
View File
@@ -20,7 +20,7 @@ import (
// - Permissionless bridge settlement
// (EstimateFee, SubmitRequest, GetStatus, CancelRequest, …)
//
// Any client that can reach /v1/chain/B/rpc has equal authority — the
// Any client that can reach /v1/bc/B/rpc has equal authority — the
// daemon at cmd/bridge is one such client. There are no privileged
// methods on this surface; rate-limiting and auth (when desired) are
// applied at the ingress layer.
+3 -3
View File
@@ -22,7 +22,7 @@
//
// dex-assets-validate \
// -manifest dexvm/registry/manifests/assets.mainnet.json \
// -evm-rpc https://api.lux.network/v1/chain/C/rpc \
// -evm-rpc https://api.lux.network/v1/bc/C/rpc \
// -api-base https://api.lux.network \
// [-native-decimals 18]
package main
@@ -42,8 +42,8 @@ import (
func main() {
var (
manifestPath = flag.String("manifest", "", "path to the per-network asset manifest JSON")
evmRPC = flag.String("evm-rpc", "", "C-Chain EVM RPC URL (…/v1/chain/C/rpc)")
apiBase = flag.String("api-base", "", "node API base (… root; used for /ext/P and /v1/chain/X)")
evmRPC = flag.String("evm-rpc", "", "C-Chain EVM RPC URL (…/v1/bc/C/rpc)")
apiBase = flag.String("api-base", "", "node API base (… root; used for /ext/P and /v1/bc/X)")
nativeDec = flag.Uint("native-decimals", 18, "C-Chain native coin decimals")
timeout = flag.Duration("timeout", 2*time.Minute, "overall validation timeout")
)
+3 -3
View File
@@ -42,8 +42,8 @@ type Verifier struct {
}
// New dials the C-Chain EVM RPC and records the node API base for P/X queries.
// evmRPC is the full C-Chain RPC URL (…/v1/chain/C/rpc); apiBase is the node root
// (…) used to reach /ext/P and /v1/chain/X. nativeDecimals is the C-Chain native
// evmRPC is the full C-Chain RPC URL (…/v1/bc/C/rpc); apiBase is the node root
// (…) used to reach /ext/P and /v1/bc/X. nativeDecimals is the C-Chain native
// coin's decimals (18 for LUX).
func New(ctx context.Context, evmRPC, apiBase string, nativeDecimals uint8) (*Verifier, error) {
ec, err := ethclient.DialContext(ctx, evmRPC)
@@ -128,7 +128,7 @@ func (v *Verifier) VerifyUTXOAsset(_ uint32, _ ids.ID, assetID ids.ID) (uint8, e
} `json:"result"`
Error *rpcError `json:"error"`
}
if err := v.jsonRPC(ctx, "/v1/chain/X", "avm.getAssetDescription",
if err := v.jsonRPC(ctx, "/v1/bc/X", "avm.getAssetDescription",
map[string]any{"assetID": assetID.String()}, &out); err != nil {
return 0, err
}
+2 -2
View File
@@ -10,11 +10,11 @@ import (
"testing"
)
// The node mounts each CreateHandlers key at /v1/chain/<chainID>+key and matches
// The node mounts each CreateHandlers key at /v1/bc/<chainID>+key and matches
// that path EXACTLY, then hands the handler the request on the path it arrived
// on. A handler that dispatches on r.URL.Path is therefore unreachable.
func TestHandlersAnswerAtTheMountedPath(t *testing.T) {
const base = "/v1/chain/24C9zm36x43T7LqcaKF1ikHxSeuQeTXnstzi5Gwh2apo18rXNE"
const base = "/v1/bc/24C9zm36x43T7LqcaKF1ikHxSeuQeTXnstzi5Gwh2apo18rXNE"
handlers, err := (&VM{}).CreateHandlers(context.Background())
if err != nil {
+2 -2
View File
@@ -170,11 +170,11 @@ func TestInitializeRefusesBadConfig(t *testing.T) {
}
// post drives a handler at the path the node mounts it on: the node matches
// /v1/chain/<chainID>+key EXACTLY and then hands the handler the full path, so a
// /v1/bc/<chainID>+key EXACTLY and then hands the handler the full path, so a
// handler that dispatches on r.URL.Path answers nothing.
func post(t *testing.T, h http.Handler, auth, body string) *httptest.ResponseRecorder {
t.Helper()
r := httptest.NewRequest(http.MethodPost, "/v1/chain/chain/graphql", strings.NewReader(body))
r := httptest.NewRequest(http.MethodPost, "/v1/bc/chain/graphql", strings.NewReader(body))
if auth != "" {
r.Header.Set("Authorization", auth)
}
+1 -1
View File
@@ -156,7 +156,7 @@ func (vm *VM) Version(context.Context) (string, error) {
// CreateHandlers implements the common.VM interface.
//
// The node mounts each key under /v1/chain/<chainID> and matches that full path
// The node mounts each key under /v1/bc/<chainID> and matches that full path
// EXACTLY, then hands the handler the request with the path it arrived on. A
// handler that dispatches on r.URL.Path therefore never recognizes anything.
// The key IS the route; one handler per key.
+2 -2
View File
@@ -93,7 +93,7 @@ func TestZKVM_HTTP_SendTransaction_FeePolicy(t *testing.T) {
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/v1/chain/z/sendTransaction", bytes.NewReader(body))
req := httptest.NewRequest(http.MethodPost, "/v1/bc/z/sendTransaction", bytes.NewReader(body))
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
return w
@@ -142,7 +142,7 @@ func TestZKVM_HTTP_SendTransaction_FeePolicy(t *testing.T) {
}
// A method the endpoint does not serve is refused, not silently accepted.
req := httptest.NewRequest(http.MethodGet, "/v1/chain/z/sendTransaction", nil)
req := httptest.NewRequest(http.MethodGet, "/v1/bc/z/sendTransaction", nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusMethodNotAllowed {
+1 -1
View File
@@ -17,7 +17,7 @@ var (
errNoNullifier = errors.New("nullifier required")
)
// The node mounts each CreateHandlers key under /v1/chain/<chainID> and matches
// The node mounts each CreateHandlers key under /v1/bc/<chainID> and matches
// that full path EXACTLY, then hands the handler the request with the path it
// arrived on. A mux behind one key therefore serves nothing: it dispatches on
// r.URL.Path, which is the mounted path and never the route it registered. The