Skip to content

Core getEstimatedTransactionFee fails with 403 on testnet seeds: estimatesmartfee missing from the dapi Core RPC whitelist #4358

Description

@thephez

Core getEstimatedTransactionFee fails with 403 on testnet seeds: estimatesmartfee missing from the dapi Core RPC whitelist

Summary

Core/getEstimatedTransactionFee fails on public testnet seed nodes. The handler calls the Dash Core RPC estimatesmartfee, which is not in the dapi RPC user's whitelist; with rpcwhitelistdefault=0, Core rejects it with HTTP 403. rs-dapi relays that as gRPC Unavailable.

A second, compounding problem: because the denial arrives as an HTTP-layer 403 rather than a JSON-RPC error object, it is mapped to Unavailable — a retryable code that tells clients to try another node — when it is actually a permanent server misconfiguration. This makes the root cause hard to identify from the client side.

Reproduction

Confirmed on seed-1 and seed-2 (testnet), 2026-08-10. Fails identically with {"blocks": 10}, {"blocks": 1}, and {}.

$ grpcurl -insecure -import-path . -proto core/v0/core.proto \
    -d '{"blocks": 10}' seed-1.testnet.networks.dash.org:1443 \
    org.dash.platform.dapi.v0.Core/getEstimatedTransactionFee

ERROR:
  Code: Unavailable
  Message: transport error: unexpected HTTP code: 403

getBlockchainStatus and getBestBlockHeight succeed on the same node, so this is method-specific rather than a node or gateway problem.

Plain gRPC masks the origin of the 403 as a transport error. Issuing the same call over grpc-web shows it is an upstream error relayed inside a normal HTTP 200 envelope, not an Envoy rejection.

grpc-web response headers
$ curl -k -D - -X POST --http2 \
    --url https://seed-2.testnet.networks.dash.org:1443/org.dash.platform.dapi.v0.Core/getEstimatedTransactionFee \
    -H 'content-type: application/grpc-web+proto' --data-binary @frame.bin

HTTP/1.1 200 OK
content-type: application/grpc-web+proto
grpc-status: 14
grpc-message: transport%20error:%20unexpected%20HTTP%20code:%20403
x-envoy-upstream-service-time: 1
ratelimit-limit: 150
ratelimit-remaining: 149
server: envoy

Envoy is ruled out as the source: the gateway template routes the entire /org.dash.platform.dapi.v0.Core prefix uniformly with no per-method filtering (envoy.yaml.dot:183-188), and contains no 403 response anywhere.

Root cause

The error string unexpected HTTP code: {} originates in jsonrpc-0.18.0/src/http/simple_http.rs:565 — the JSON-RPC HTTP client used by dashcore_rpc. Dash Core returns rpcwhitelist denials at the HTTP layer as a 403, so the failure path is:

  1. core_service.rs:529-546 — handler clamps blocks to 1..1000, calls estimate_smart_fee_btc_per_kb
  2. core_client.rs:410-413 — issues the estimatesmartfee Core RPC
  3. Core denies it: estimatesmartfee is absent from the dapi user's whitelist (getBaseConfigFactory.js:80-89), and dash.conf.dot:36 sets rpcwhitelistdefault=0HTTP 403
  4. The jsonrpc crate surfaces it as Error::Transport(HttpErrorCode(403))
  5. error.rs:833 maps jsonrpc::Error::TransportDapiError::Unavailablegrpc-status: 14

Every other Core gRPC method works because each one's underlying RPC is whitelisted. This is the only one that is not.

Proposed fix

  1. Add estimatesmartfee to the dapi user's whitelist in getBaseConfigFactory.js.
  2. Add a config-file migration so existing installs pick up the new whitelist. There is direct precedent at getConfigFileMigrationsFactory.js:791-792 and :805, both of which reassign a user's whitelist from base for exactly this kind of change. Without this, upgraded nodes stay broken.
  3. Distinguish HTTP 403/401 from genuine transport failures in the jsonrpc error mapping at error.rs:833. An auth/whitelist denial is a permanent server-side misconfiguration and should not present as retryable Unavailable; Internal (or a distinct code with a clear message) would surface it as an operator problem instead of sending clients to retry other nodes. This is what made the underlying cause hard to see.
  4. Add coveragegetEstimatedTransactionFee is currently exercised by no test in platform-test-suite, rs-dapi/tests, or dapi/test, which is why this reached production.

Related observation

core_service.rs:544 applies unwrap_or(0.0) when Core returns no estimate, making "no estimate available" indistinguishable from a successful zero-fee estimate. Worth deciding whether that should be an explicit error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions