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:
core_service.rs:529-546 — handler clamps blocks to 1..1000, calls estimate_smart_fee_btc_per_kb
core_client.rs:410-413 — issues the estimatesmartfee Core RPC
- Core denies it:
estimatesmartfee is absent from the dapi user's whitelist (getBaseConfigFactory.js:80-89), and dash.conf.dot:36 sets rpcwhitelistdefault=0 → HTTP 403
- The
jsonrpc crate surfaces it as Error::Transport(HttpErrorCode(403))
error.rs:833 maps jsonrpc::Error::Transport → DapiError::Unavailable → grpc-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
- Add
estimatesmartfee to the dapi user's whitelist in getBaseConfigFactory.js.
- 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.
- 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.
- Add coverage —
getEstimatedTransactionFee 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.
Core
getEstimatedTransactionFeefails with 403 on testnet seeds:estimatesmartfeemissing from thedapiCore RPC whitelistSummary
Core/getEstimatedTransactionFeefails on public testnet seed nodes. The handler calls the Dash Core RPCestimatesmartfee, which is not in thedapiRPC user's whitelist; withrpcwhitelistdefault=0, Core rejects it with HTTP 403. rs-dapi relays that as gRPCUnavailable.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-1andseed-2(testnet), 2026-08-10. Fails identically with{"blocks": 10},{"blocks": 1}, and{}.getBlockchainStatusandgetBestBlockHeightsucceed 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
Envoy is ruled out as the source: the gateway template routes the entire
/org.dash.platform.dapi.v0.Coreprefix 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 injsonrpc-0.18.0/src/http/simple_http.rs:565— the JSON-RPC HTTP client used bydashcore_rpc. Dash Core returnsrpcwhitelistdenials at the HTTP layer as a 403, so the failure path is:core_service.rs:529-546— handler clampsblocksto1..1000, callsestimate_smart_fee_btc_per_kbcore_client.rs:410-413— issues theestimatesmartfeeCore RPCestimatesmartfeeis absent from thedapiuser's whitelist (getBaseConfigFactory.js:80-89), anddash.conf.dot:36setsrpcwhitelistdefault=0→ HTTP 403jsonrpccrate surfaces it asError::Transport(HttpErrorCode(403))error.rs:833mapsjsonrpc::Error::Transport→DapiError::Unavailable→grpc-status: 14Every other Core gRPC method works because each one's underlying RPC is whitelisted. This is the only one that is not.
Proposed fix
estimatesmartfeeto thedapiuser's whitelist ingetBaseConfigFactory.js.getConfigFileMigrationsFactory.js:791-792and:805, both of which reassign a user's whitelist frombasefor exactly this kind of change. Without this, upgraded nodes stay broken.jsonrpcerror mapping aterror.rs:833. An auth/whitelist denial is a permanent server-side misconfiguration and should not present as retryableUnavailable;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.getEstimatedTransactionFeeis currently exercised by no test inplatform-test-suite,rs-dapi/tests, ordapi/test, which is why this reached production.Related observation
core_service.rs:544appliesunwrap_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.