Skip to content

fix(api): publish the AT address as atAddress everywhere - #186

Merged
QuickMythril merged 1 commit into
mainfrom
fix/standardize-at-address-json
Jul 30, 2026
Merged

fix(api): publish the AT address as atAddress everywhere#186
QuickMythril merged 1 commit into
mainfrom
fix/standardize-at-address-json

Conversation

@QuickMythril

Copy link
Copy Markdown
Member

One value, three published names

response JSON key
DeployAtTransactionData — tx search, unconfirmed, block txs, websockets aTAddress
ATData/at/{addr}, /at/byfunction ATAddress
AT map reads, and every other Core response atAddress

Both offenders bind with @XmlAccessorType(XmlAccessType.FIELD), so the JSON key is the Java field name — neither spelling was deliberate. DeployAtTransactionData declared getAtAddress() / setAtAddress(String AtAddress) around a field named aTAddress: a fourth capitalization of the same word in one file.

Seven serialized classes already use atAddress (ATTransactionData, TradeBotData, TradeBotFillData, TransactionSummary, CrossChainOfferSummary, CrossChainTradeSummary, AtMapValueResponse). These two never got the memo — inherited from Qortal.

This fails silently, which is why it survived. A consumer reading atAddress off a DEPLOY_AT response gets undefined, not an error. It cost a real deployment: the SMPL faucet bootstrap printed accepted DEPLOY_AT response did not include an AT address and aborted after the asset and the AT had both been issued and confirmed on Previewnet.

Change

Rename the fields on DeployAtTransactionData and ATData to atAddress. The word-case Java accessors (getATAddress()) are unchanged. ATStateData's private field is renamed for consistency though it is not serialized.

No consensus surface. The AT address is derived and never enters the DEPLOY_AT byte layout — DeployAtTransactionTransformer doesn't reference it at all — and transformers use accessors. Transaction bytes and the chain-config hash are untouched.

Tests assert the marshalled JSON keys directly for both classes, and that the legacy keys are gone, so a future field rename fails loudly instead of quietly changing the public API.

Two faults found while tracing this

GET /at/{ataddress} validated nothing. Any unmatched path under /at/ arrived as an "AT address", and an unknown address answers 204 — so an invented sub-path was indistinguishable from a genuine "no such AT". /at/search is not an endpoint, yet it answered 204 and was recorded four times in our own deployment runbook as evidence that no ATs existed. It returns 204 with the faucet live, too. Malformed addresses are now rejected with INVALID_ADDRESS; a well-formed address with no AT still answers empty, as before.

GET /at/{ataddress}/data returned a bare HTTP 500 for an unknown AT — it dereferenced a null AT state. It now answers empty and validates the address.

getMapValue, directly below both, already did address validation and null-safe absence, so this matches the existing in-file contract rather than inventing one.

Verification

  • Focused suites green, 56 tests, 0 failures: AtResourceTests (8), TransactionDataJsonTests (4), ApiSerializationContractTests, TransactionDataDiscriminatorTests, SmplFaucetAtTests (11), ATMapActivationTests, DeployAtNoNativeAssetTests, DeployAtUnsigned256GateTests. Run with -DskipTests=false and counts read from the surefire reports, not from a silent exit 0.
  • Both new guards mutation-tested. Reverting ATData's field to ATAddress fails with the marshalled JSON in the message, empirically confirming the field name drives the key. Removing the getByAddress validation fails with ApiException expected: INVALID_ADDRESS. Sources restored byte-identically afterwards (diff -q).
  • Consumer sweep across every Qortium repo: no consumer of either legacy key exists — Home has zero references to any spelling, no QDN app reads it, qortium-explore is clean, and every remaining ATAddress hit is a Java method name. Core's own q-apps.js already uses atAddress as the GET_AT request param, so apps were already sending atAddress and receiving ATAddress.
  • CI runs the full suite here (mvn -B test -DskipJUnitTests=false, unfiltered).

Follow-ups, not in this PR

  • The casino operator script must read atAddress with an aTAddress fallback — not deprecation politeness, but because the seeds and installed nodes run 1.6.1 for a while yet.
  • A real /at/search (filters over the ATs table: codeHash, creator, assetId, executable/finished/frozen/fatal-error, creation range) is worth adding separately; note name/description/aTType/tags live on the DEPLOY_AT transaction, not on the AT, so they can't be filtered there.

One value, three published names. A DEPLOY_AT transaction carried `aTAddress`,
an AT lookup carried `ATAddress`, and AT map reads plus every other Core
response already used `atAddress`. Both classes bind with
XmlAccessType.FIELD, so the JSON key is literally the Java field name and
neither odd spelling was deliberate — `DeployAtTransactionData` even declared
`getAtAddress()`/`setAtAddress(String AtAddress)` around a field named
`aTAddress`, a fourth capitalization of the same word.

This fails silently, which is why it survived from Qortal: a consumer reading
`atAddress` off a DEPLOY_AT response gets undefined rather than an error. It
cost us a real deployment — the SMPL faucet bootstrap reported "accepted
DEPLOY_AT response did not include an AT address" and aborted after the asset
and the AT had both been issued and confirmed.

Rename the fields on DeployAtTransactionData and ATData to `atAddress`; the
word-case Java accessors (getATAddress) are unchanged. ATStateData's private
field is renamed for consistency though it is not serialized. No consensus
surface: the AT address is derived and never enters the DEPLOY_AT byte layout,
and the transformers use accessors, so transaction bytes and the chain-config
hash are untouched.

Tests assert the marshalled JSON keys directly for both classes and that the
legacy keys are gone, so a future field rename fails loudly instead of quietly
changing the public API.

Also fix two faults found while tracing this. GET /at/{ataddress} validated
nothing, so any unmatched path under /at/ arrived as an "AT address" and an
unknown address answered 204 — making an invented sub-path indistinguishable
from a genuine "no such AT". `/at/search` is not an endpoint, yet it answered
204 and was recorded four times as evidence that no ATs existed. Malformed
addresses are now rejected with INVALID_ADDRESS, while a well-formed address
with no AT still answers empty. GET /at/{ataddress}/data dereferenced a null AT
state for an unknown AT and returned a bare HTTP 500; it now answers empty and
validates the address too. getMapValue directly below already did both, so this
matches the existing in-file contract.

Consumers reading the AT address must use `atAddress`, accepting either name
while nodes on older versions remain in use.
@QuickMythril
QuickMythril merged commit a2f87e6 into main Jul 30, 2026
9 checks passed
@QuickMythril
QuickMythril deleted the fix/standardize-at-address-json branch July 30, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant