Files

5.2 KiB

Context

See proposal.md for motivation. SolanaBlockChainImpl currently serializes every RPC request through a synchronized sendThrottled() method, sleeps until five seconds have elapsed since the preceding RPC response, performs HTTP while holding the monitor, and records completion time in finally. Monitor acquisition is neither interruptible nor deadline-aware, while issue #67 requires both without changing existing RPC behavior.

Goals / Non-Goals

Goals:

  • Preserve one shared five-second gate for all RPC calls while allowing awaiting calls to stop at a monotonic deadline.
  • Keep definitive transaction state, timeout, protocol failure, and interruption as distinct outcomes.
  • Strictly validate all wire data that controls a returned outcome.
  • Keep the API and outcome types generic to Solana rather than any consuming domain.

Non-Goals:

  • Combining submission and awaiting, resubmission, WebSockets, signature subscriptions, or blockhash-expiration inference.
  • Updating any existing wallet, Jupiter, burn, Evelyn, or Discord consumer.
  • Adding automated tests or test-only transport, time, or throttling seams in this implementation.

Decisions

Replace the intrinsic monitor with one interruptible shared lock

A private ReentrantLock will serialize all RPC traffic. Existing calls acquire it interruptibly, perform the same five-second wait and HTTP exchange, update the shared completion timestamp in finally, and release it. Awaiting calls use timed tryLock with their remaining deadline. A separate polling lock or rate limiter was rejected because it would violate the shared throttle and permit polling to interfere unpredictably with other calls.

Measure shared throttling and awaiting deadlines monotonically

The completion timestamp and deadline calculations use System.nanoTime(). A small private deadline value tracks the call's start and saturated timeout nanoseconds and computes remaining time by elapsed duration. Wall-clock time was rejected because clock adjustments could extend or shorten both throttling and the caller's timeout.

Build each status HTTP request only after gate and throttle admission

The deadline-aware send path acquires the gate and waits only up to the remaining deadline. Once admitted, it creates a request whose HttpRequest.timeout equals the then-current remaining duration. A request-timeout exception is translated to TIMED_OUT; other I/O failures remain IOException. Building the request after admission avoids assigning the full original timeout to an HTTP exchange that starts after gate/throttle delay.

Return an internal no-request signal on deadline expiry

The deadline-aware send helper returns no response when the deadline expires before request start. The public loop converts this only to the invariant TIMED_OUT value. This keeps deadline expiry separate from communication/protocol exceptions and ensures a previously seen lower-commitment slot cannot leak into the timeout result.

Parse only authoritative signature-status fields

Each response must contain a successful HTTP result, no JSON-RPC error, and exactly one result.value element. A null element remains inconclusive. A status object requires a Long-representable non-negative slot and recognized confirmationStatus; only after its commitment satisfies the request is err interpreted as success or failure. result.context, confirmations, and legacy status are ignored.

Preserve the complete on-chain error as compact JSON

Jackson's compact serialization of the non-null err node becomes failureDetails. The blockchain layer does not classify program-specific errors. Any issue outside this err node remains an exception or timeout rather than FAILED.

Validate transaction signatures with the existing Base58 implementation

The reference implementation reuses its existing private Base58 decoder and requires exactly 64 decoded bytes before entering the RPC gate. Introducing a second codec dependency or general transaction parser was rejected as unrelated scope.

Make cached awaiting a transparent pass-through

CachedSolanaBlockChain.awaitTransaction calls its delegate directly. Transaction status is deadline-sensitive and mutable, so cache keys, reuse, and in-flight deduplication would violate the contract.

Risks / Trade-offs

  • [An HTTP client can complete at the edge of its timeout after the monotonic deadline] → Use the remaining deadline as the request timeout and re-check the deadline before interpreting an inconclusive response; this is the strongest practical bound offered by Java HttpClient.
  • [Replacing synchronization changes the internal locking primitive] → Keep the same lock scope, completion-based five-second spacing, and exception behavior for every existing RPC operation.
  • [No automated deterministic coverage is added now] → Keep deadline, parsing, and gate concerns in narrow private methods and document that focused tests remain deferred by explicit task instruction.

Migration Plan

This is additive at the public API. Existing implementations in this repository are updated together, while existing consumers remain unchanged. Rollback removes the new method/types and restores the prior private monitor without data migration.