Skip to content

fix(docgen): derive JSON-RPC docs from the serializer contract - #12868

Merged
dipkakwani merged 2 commits into
masterfrom
fix/docgen-serializer-contract
Aug 18, 2026
Merged

fix(docgen): derive JSON-RPC docs from the serializer contract#12868
dipkakwani merged 2 commits into
masterfrom
fix/docgen-serializer-contract

Conversation

@dipkakwani

@dipkakwani dipkakwani commented Aug 18, 2026

Copy link
Copy Markdown
Member

Partial #12828, #12836, #12838, #12839 and #12841. Closes #12826.

Changes

DocGen re-derived the serializer's rules by inspecting CLR types, and disagreed with them. It now reads the same contract the RPC layer serializes with, via the public EthereumJsonSerializer.JsonOptions.

  • Names and visibility come from the contract. JsonPropertyInfo.Name is the final wire name and Get is null marks a hidden member, so naming policy, [JsonPropertyName], [JsonIgnore] conditions, public fields and inherited members are no longer re-implemented. This surfaces the block fields eth_simulateV1 and trace_simulateV1 omitted.
  • Converter-backed types are documented as what they emit. eth_call was an object with a bytes member instead of a hex string; traceAddress was an object with isUncapped instead of an array. A typeof-keyed table replaces the type.Name switch and covers these. By-ref and Nullable<T> unwrap first, removing the hasValue/value wrappers.
  • The table is consulted before shape inference, and its numeric rows match the registered converters. Previously the enum and collection branches ran first, so the TxType row was unreachable and every transaction type was documented as _integer_ while the wire is "0x2". int, uint and byte have no hex converter and serialize as JSON numbers; BigInteger writes decimal digits, not hex. Enums serialize numerically unless a converter writes the name instead, so admin_prune is a string.
  • Collections use Kind/ElementType/KeyType, deleting ~55 lines of hand-rolled detection.
  • Types with no contract fall back to CLR shape, and are reported once per run. Hand-rolled converters expose no members. JsonTypeInfoKind selects the fallback rather than an empty property count, which would conflate "no contract" with "every member ignored"; public fields are included to match the serializer's IncludeFields; and a type the serializer refuses to model degrades to its CLR shape instead of aborting the run. Listing the fallbacks makes a guessed page visible.

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Notes on testing

Each changed shape was checked against geth's implementation and golden testdata/*.json, plus Nethermind's existing CI-asserted JSON.

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

Remarks

Fixed beyond the filed issues

Method(s) Was Now
eth_call _object_ with a bytes member _string_ (hex data)
debug_getRawBlock/RawHeader/RawTransaction, eth_getRawTransactionBy* array of _string_ (hex data) _string_ (hex data)
eth_feeHistoryrewardPercentiles array of _object_ array of _number_
eth_simulateV1 / trace_simulateV1 block fields absent inherited block fields documented
type on every transaction and receipt (31 places) _integer_ _string_ (transaction type)
admin_prune _integer_ _string_
debug_trace* struct logs → depth _string_ (hex integer) _integer_
admin_nodeInfoports.*; parity_netPeersactive/connected/max; clique_getSnapshotvotes; eth_simulateV1code _string_ (hex integer) _integer_
admin_peersversion _string_ (hex data) _integer_

@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dipkakwani's task in 10m 19s —— View job


Review: fix(docgen): derive JSON-RPC docs from the serializer contract

  • Read the diff and current file state
  • Verify serializer-contract logic (TryGetTypeInfo, ignore handling, fallback)
  • Verify collection / type-table handling and edge cases
  • Check repo rules adherence
  • Post findings (5 inline comments)

The direction is right and I verified the mechanics against the actual serializer configuration: EthereumJsonSerializer.CreateOptions terminates its resolver chain with DefaultJsonTypeInfoResolver, so TryGetTypeInfo yields reflection-derived metadata for every type — no source-gen resolver is registered in the DocGen process — and IncludeFields = true genuinely makes public fields show up. I also confirmed the claims in the Remarks table: eth_call returns ResultWrapper<HexBytes>, and debug_getRawBlock/RawHeader/RawTransaction plus eth_getRawTransactionBy* return ResultWrapper<ArrayPoolList<byte>>, which ArrayPoolListByteHexConverter renders as hex — so _string_ (hex data) is correct and master's array of _string_ (hex data) was wrong. Deleting TryGetEnumerableItemType's hand-rolled reflection in favour of Kind/ElementType is a net removal of failure points, and it strictly widens coverage (interface IReadOnlyList<T>, any IDictionary shape) over master's IEnumerable<>/IDictionary<,>-only special cases.

I could not build or run the tool (no dotnet permission in this job), so everything below is from reading the code and the serializer it now depends on — a before/after diff of the generated markdown would be the real check.

Medium

1. The type.IsEnum short-circuit runs before the new table, so two enum shapes stay wrongJsonRpcGenerator.cs:385

GetJsonTypeName returns _integer_ at line 366 before ever reaching _knownTypeNames, which makes [typeof(TxType)] = "_string_ (transaction type)" (line 62) dead code — TxType is enum TxType : byte, and TxTypeConverter.Write emits "0x0"/"0x2", so type on every transaction and receipt is still documented as _integer_. The same branch mis-documents enums with [JsonConverter(typeof(LowerCaseJsonStringEnumConverter<>))]: admin_prune returns ResultWrapper<PruningStatus>, which serializes as "starting". Both are pre-existing (master's switch had the same unreachable "TxType" arm), but the new table reads as authoritative, and one lookup reorder plus a JsonConverterAttribute check fixes both.

2. int and byte are documented as hex strings but serialize as JSON numbersJsonRpcGenerator.cs:44-55

IntConverter.Write is writer.WriteNumberValue(value), unlike LongConverter/ULongConverter which route through NumericConverterHelper.Write and do emit hex (ForcedNumberConversion.Value defaults to NumberConversion.Hex). There is no byte converter at all. Concretely: parity_netPeersactive/connected/max, admin_nodeInfoports.*, trace_*subtraces, debug_* GC stats → gen0/gen1/gen2, and EthProtocolInfo.version. Also carried over from master, but the PR already corrects the analogous double row to _number_, so these look like the same one-line fix.

Low

3. Properties.Count != 0 conflates "no contract" with "object contract that serializes nothing"JsonRpcGenerator.cs:417-431. contract.Kind is JsonTypeInfoKind.Object is the precise signal; with the count test, a DTO whose properties are all [JsonIgnore] would fall through and get documented with exactly the members the serializer omits. Latent — no such type exists in the current surface. Same comment covers two sub-notes: the fallback reads properties only, though IncludeFields = true means the contract path also yields fields; and dropping DeclaredOnly would double-list a new-shadowed instance property (nothing documented does that today).

4. catch (ArgumentException) is too narrow for contract resolutionJsonRpcGenerator.cs:402-413. CreateTypeInfo can also throw InvalidOperationException (colliding JSON property names) or NotSupportedException; either aborts the whole --jsonrpc run, where the old pure-reflection path could not fail at all. The graceful degradation path already exists — let those reach it.

5. _guessedTypeNames.Add(type.Name)JsonRpcGenerator.cs:426. Collides for same-named types across namespaces (two BlockHeaderForRpc exist) and prints generics as ArrayPoolList`1, which is the common case since buffer wrappers are exactly the converter-backed types. Same comment notes that TransactionForRpc will always land in this list, so the headline transaction shapes remain guessed (seven base fields only) — unchanged from master, but it caps what this PR can fix.

6. Two smaller things, no comment posted. The PR description says collections come from Kind/ElementType/KeyType, but KeyType is never read — map of X still drops the key type, same as master. And Nullable<T> is now unwrapped in WriteExpandedType (line 292) and by-ref only inside GetJsonTypeName; unwrapping by-ref in WriteExpandedType too would keep the member-expansion path consistent, though I found no out/ref object parameter in the RPC interfaces, so nothing is broken today.

Notes on tests and style

No test was added, and the PR body ticks "requires testing: yes / wrote tests: no" with manual verification against geth and golden testdata/*.json as the rationale. AGENTS.md asks for a regression test on every bug fix; there is no DocGen test project, and neither build-tools.yml nor update-docs.yml asserts the generated markdown, so nothing in CI will catch it if the next serializer change silently shifts a documented shape. A golden-file check over one representative namespace would lock in the four fixes claimed in the Remarks table. Style-wise the diff reads cleanly and matches the file's existing idiom — no var, explicit types, LINQ consistent with the surrounding tool code (the "no LINQ" rule targets hot paths, not a docs generator). One nit: DateTimeOffset sits after double[] in the otherwise alphabetical table.

Verdict: nothing here can affect a running node — this is a build-time docs tool, so no consensus, security, or hot-path exposure. Findings 1 and 2 are the ones I'd want addressed before merge, since both leave factually wrong types in the published docs that this PR is otherwise well positioned to fix; everything else is fine to defer.
• branch fix/docgen-serializer-contract

Comment thread tools/DocGen/JsonRpcGenerator.cs Outdated
Comment thread tools/DocGen/JsonRpcGenerator.cs Outdated
Comment thread tools/DocGen/JsonRpcGenerator.cs
Comment thread tools/DocGen/JsonRpcGenerator.cs
Comment thread tools/DocGen/JsonRpcGenerator.cs Outdated
}
}
// A hand-rolled converter exposes no contract members, leaving the CLR shape as the only guess
_guessedTypeNames.Add(type.Name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low — type.Name makes the new warning ambiguous exactly where it matters.

The diagnostic is the only signal that a type fell back to guessing, so it should identify the type unambiguously. type.Name collapses same-named types in different namespaces (there are two BlockHeaderForRpc in the tree — Nethermind.Facade.Eth and Nethermind.Taiko.Tdx) and prints generics mangled as ArrayPoolList\1, which is the common case here since the buffer wrappers are precisely the converter-backed types. type.FullName ?? type.Name` costs nothing and keeps the set genuinely deduplicated.

Also worth noting for anyone reading the output: TransactionForRpc will always appear in this list — it carries [JsonConverter(typeof(TransactionJsonConverter))] and is abstract, so the CLR-shape guess only yields the seven base fields (type, hash, transactionIndex, blockHash, blockNumber, blockTimestamp, gas) and none of the per-type fields (nonce, to, from, value, input, gasPrice, …). That matches master, so it's not a regression, but it does mean the headline transaction shapes are still the guessed ones.

@dipkakwani
dipkakwani marked this pull request as ready for review August 18, 2026 08:22
@dipkakwani
dipkakwani requested a review from rubo as a code owner August 18, 2026 08:22
@claude

claude Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dipkakwani's task in 7m 51s —— View job


Review: fix(docgen): derive JSON-RPC docs from the serializer contract

  • Read the diff and current file state
  • Re-check the five findings from the prior review against 013594d address comments
  • Verify every row of the new type table against the registered converters
  • Check collection handling for regressions from dropping reflection-based enumerable detection
  • Check repo rules adherence
  • Post findings (3 inline comments)

013594d addresses all five findings from my earlier pass, and I re-verified each fix against the serializer rather than taking it on trust:

Earlier finding State in 013594d
Enum short-circuit ran before the table Table is now consulted first (line 365), so [typeof(TxType)] is live. TxTypeConverter.Write emits "0x0" / ByteArrayConverter.Convert(…, skipLeadingZeros: true), so _string_ (transaction type) is right and the 31 type fields are fixed.
int/byte documented as hex Now _integer_. Confirmed IntConverter.Write is writer.WriteNumberValue(value) and no byte/uint converter is registered. long/ulong correctly stay hex — they route through NumericConverterHelper.Write and ForcedNumberConversion.Value defaults to NumberConversion.Hex = 0. BigInteger_string_ (decimal integer) matches WriteStringValue(value.ToString(InvariantCulture)); double_number_ matches WriteRawValue.
Properties.Count != 0 conflated "no contract" with "everything ignored" Now contract?.Kind is JsonTypeInfoKind.Object, and the fallback reads fields as well as properties to match IncludeFields = true.
catch (ArgumentException) too narrow Widened to ArgumentException or InvalidOperationException or NotSupportedException.
type.Name collided across namespaces Now namespace-qualified.

I also checked the two things most at risk from deleting TryGetEnumerableItemType's reflection, and both hold: ArrayPoolList<T> carries no [JsonConverter], so a non-byte instance still resolves to Kind == Enumerable and its items still expand; and every array row in the table (byte[], byte[][], Hash256[], double[], Bloom) is converter-backed, so Kind == None keeps the enumerable branch from overriding it. CappedArray<int>array of _integer_ is right, because CappedArrayJsonConverter<T> delegates each element to IntConverter. By-ref parameters are handled too — GetContract throws for them, the catch swallows it, and the unwrapped scalar name wins, which is why retiring master's "Int64&" row is safe.

No dotnet permission in this job, so as before this is a read of the code and the serializer it depends on, not a run of the tool. A before/after diff of the generated markdown remains the real check, and build-tools.yml will catch any compile error in the new usings.

Medium

1. Member-level [JsonConverter]s are still invisible, so trace_* block numbers are documented as hex strings while the node writes bare numbersJsonRpcGenerator.cs:427

GetSerializedMembers returns (p.Name, p.PropertyType) and drops p.CustomConverter, so the type table decides alone. Two converters deliberately opt out of hex: ParityTxTraceFromStore.BlockNumber uses ULongRawJsonConverter (writer.WriteNumberValue) and shows up in trace_block, trace_filter, trace_get and trace_transaction; BlockForRpc.Step / BlockHeaderForRpc.Step use NullableRawULongConverter, same thing. Both are documented as _string_ (hex integer). Nonce via BlockNonceConverter is a milder variant — a zero-padded 16-digit hex string documented as a hex integer.

Carried over from master's "UInt64" => "_string_ (hex integer)", so not a regression — I'm raising it because it is the same defect class the PR fixes at type level, and the contract already carries the answer one field away. Fixing it or explicitly scoping it out both work for me; what I'd avoid is leaving it unstated now that the table reads as authoritative. Fix this →

Low

2. The enum branch reads the attribute, but the counter-example doesn't use oneJsonRpcGenerator.cs:370. All three attributed enums in the surface (PruningStatus, AbiDescriptionType, StateMutability) use LowerCaseJsonStringEnumConverter<T>, so the output is correct today and admin_prune is genuinely fixed. But TxTypeConverter is registered in the options list, not as an attribute — invisible to this check and only correct because of its explicit table row — and it writes hex, showing that attribute-present ⇒ _string_ holds by coincidence of the current three.

3. Three notes on the fallback pathJsonRpcGenerator.cs:431-440. type.FullName would keep generics from printing as ArrayPoolList`1; GetProperties includes indexers, which the serializer never writes (prophylactic, nothing hits it today); and the new warning list is worth pasting somewhere durable, because at least two entries are load-bearing guesses — AccessListForRpc has no public members at all, so accessList documents as a bare _object_ instead of an array of {address, storageKeys}, and SyncingResult documents isSyncing/syncMode, neither of which SyncingResultJsonConverter ever writes.

Notes

Still no test, and AGENTS.md asks for a regression test on every bug fix. The PR body states the rationale (manual verification against geth and the golden testdata/*.json), and there is no DocGen test project, so I'm treating that as acknowledged rather than re-raising it — though a golden-file check over one namespace is what would keep the next serializer change from silently shifting a documented shape, since neither build-tools.yml nor update-docs.yml asserts the generated markdown. Style-wise the diff matches the file's idiom: explicit types, no var, LINQ consistent with the surrounding tool code, and the table is now alphabetical. KeyType is still unread despite the PR description mentioning it, so map of X continues to drop the key type — same as master, cosmetic.

Verdict: the direction and the mechanics are sound, and this fixes a real set of factually wrong published shapes. Nothing here can touch a running node — build-time docs tool, no consensus, security or hot-path exposure. Finding 1 is the only one I'd want a decision on before merge (a fix or a stated scope boundary); findings 2 and 3 are fine to defer.
• branch fix/docgen-serializer-contract

Comment thread tools/DocGen/JsonRpcGenerator.cs
// An enum serializes as its numeric value unless a converter writes the member name instead
if (type.IsEnum)
return "_integer_";
return type.GetCustomAttribute<JsonConverterAttribute>() is null ? "_integer_" : "_string_";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low — correct for every enum in the surface today, but the signal it reads is the one the counter-example doesn't use.

I checked all three enums that carry the attribute — PruningStatus, AbiDescriptionType, StateMutability — and each uses LowerCaseJsonStringEnumConverter<T>, so _string_ is right and admin_prune is now correct. Two caveats worth a comment or a tweak rather than a change of behaviour:

  1. The counter-example is TxType, whose converter is registered in EthereumJsonSerializer.CreateOptions (new TxTypeConverter(), line 120) rather than as an attribute — so a converter presence check via the attribute cannot see it, and it only comes out right because _knownTypeNames has an explicit row above. Any future enum converter added to that list the same way silently regresses to _integer_.
  2. TxTypeConverter also shows the inference direction isn't sound in general: it writes "0x0", i.e. a converter on an enum can emit hex rather than the member name, so attribute-present ⇒ _string_ holds by coincidence of the current three.

Both are covered by keying off the resolved contract instead — e.g. treating GetContract(type)?.Kind is JsonTypeInfoKind.None on an enum as "a converter decides" — or, if you'd rather keep the check as-is, by tightening the comment to say what is actually being detected (a member-name-writing converter attribute) so the next reader doesn't assume options-registered converters are covered.

Comment on lines +431 to +440
_guessedTypeNames.Add($"{type.Namespace}.{type.Name}");

if (type.IsInterface && type.IsGenericType)
{
if (type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
itemType = type.GetGenericArguments().Last();
isDictionary = false;
const BindingFlags memberFlags = BindingFlags.Public | BindingFlags.Instance;

return true;
}

if (type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
itemType = type.GetGenericArguments().Last();
isDictionary = true;
// The serializer sets IncludeFields, so public fields reach the wire alongside properties
return type.GetProperties(memberFlags).Select(p => (Member: (MemberInfo)p, Type: p.PropertyType))
.Concat(type.GetFields(memberFlags).Select(f => (Member: (MemberInfo)f, Type: f.FieldType)))
.Where(m => m.Member.GetCustomAttribute<JsonIgnoreAttribute>()?.Condition is not JsonIgnoreCondition.Always)
.Select(m => (Name: GetFallbackName(m.Member), Type: m.Type))
.OrderBy(m => m.Name, StringComparer.Ordinal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low — three notes on the fallback path, none blocking.

  1. $"{type.Namespace}.{type.Name}" still mangles generics. It does disambiguate the two BlockHeaderForRpc (Nethermind.Facade.Eth vs Nethermind.Taiko.Tdx), which was the point — but a constructed generic still prints as Nethermind.Core.Collections.ArrayPoolList`1. type.FullName ?? $"{type.Namespace}.{type.Name}" gives the full construction for free.

  2. GetProperties includes indexers, which the serializer never writes. A converter-backed type with public T this[int i] would gain a phantom item member here. Nothing in the documented surface hits it today (ArrayPoolList<T>/CappedArray<T> are intercepted by the buffer-wrapper branch before they reach expansion), so this is prophylactic: .Where(p => p.GetIndexParameters().Length == 0).

  3. Worth capturing what the new warning line prints. The value of the diagnostic is that it names pages that are guesses, and at least two of them are guesses in a load-bearing way:

    • AccessListForRpc has no public properties or fields at all (its IEnumerable<Item> _items is private, Nethermind.Facade/Eth/RpcTransaction/AccessListForRpc.cs:20), so accessList documents as a bare _object_ while the wire is an array of {address, storageKeys}.
    • SyncingResult documents isSyncing and syncMode, neither of which SyncingResultJsonConverter ever writes — it emits either false or {startingBlock, currentBlock, highestBlock} (Nethermind.Facade/Eth/SyncingResultJsonConverter.cs:22-33).

    Both predate this PR. Since the run now lists them, pasting that list into the PR (or a follow-up issue) would turn it into a work queue instead of console output nobody reads.

if (contract?.Kind is JsonTypeInfoKind.Object)
return contract.Properties
.Where(p => p.Get is not null)
.Select(p => (Name: p.Name, Type: p.PropertyType))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contract path maps each member by its CLR PropertyType, so a long or ulong field written by a raw-number converter such as LongRawJsonConverter would still be documented as a hex string. Is that per-property converter case meant to be covered here, or left to the type-level mapping?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not coverable by the type-level mapping e.g. struct-log gas and transaction gas are both ulong but need different encodings. Will fix it in a follow-up PR, since this is an existing issue in master.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed here #12869

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One same-family gap to note: the enum-with-converter branch (GetJsonTypeName) hardcodes string, so an enum whose converter emits a number would be mislabeled, another member-converter case. Could later share the same probe as in #12869 (safe for enums, though not for value-dependent unions like eth_syncing).

hudem1 added a commit that referenced this pull request Aug 18, 2026
Building on the serializer-contract refactor (#12868): the member's CLR type
alone does not determine its wire form. A `long`/`ulong` field carrying
`[LongRawJsonConverter]`/`[ULongRawJsonConverter]` serializes as a raw JSON
number, overriding the hex-quantity string its type would otherwise imply.

Read the converter from the member (the contract's AttributeProvider, or the
CLR member on the fallback path) and document such fields as JSON integers.
Fixes the trace_* result blockNumber and the debug_* Geth struct-log entries,
completing the number-vs-hex mismatches reported in #12838.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hudem1 added a commit that referenced this pull request Aug 18, 2026
Building on the serializer-contract refactor (#12868): a member's CLR type does
not determine its wire form. A field carrying an explicit [JsonConverter] can
emit something other than the hex-quantity string its type implies - e.g. two
`ulong` fields where one has a raw-number converter (struct-log gas vs tx gas,
raised in review).

Instead of hard-coding the raw-number converter types, serialize a sample value
through the member's converter and read back the JSON token: a Number/Boolean
overrides the type-based label, while a String keeps its editorial flavour
(hex data, hash, ...). Probing is scoped to explicit member converters only;
probing a type's default serialization is unsound for value-dependent unions
(e.g. eth_syncing returns `false` or an object).

Completes #12838: trace_* result blockNumber and debug_* struct-log entries
(gas/gasCost/pc/refund/step) now render as JSON integers, matching the wire
format and Geth/OpenEthereum/Erigon. Verified by diffing generated docs against
the base branch: the only changes are these fields; eth_syncing and byte-buffer
fields are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dipkakwani
dipkakwani merged commit e9c470f into master Aug 18, 2026
508 of 509 checks passed
@dipkakwani
dipkakwani deleted the fix/docgen-serializer-contract branch August 18, 2026 10:44
hudem1 added a commit that referenced this pull request Aug 18, 2026
A member's CLR type does not determine its wire form. A field carrying an
explicit [JsonConverter] can emit something other than the hex-quantity string
its type implies - e.g. two `ulong` fields where one has a raw-number converter
(struct-log gas vs tx gas).

Instead of hard-coding the raw-number converter types, serialize a sample value
through the member's converter and read back the JSON token: a Number/Boolean
overrides the type-based label, while a String keeps its editorial flavour
(hex data, hash, ...). Probing is scoped to explicit member converters; probing
a type's default serialization is unsound for value-dependent unions (e.g.
eth_syncing returns `false` or an object).

Completes #12838 on top of #12868: trace_* result blockNumber and debug_*
struct-log entries (gas/gasCost/pc/refund/step) now render as JSON integers,
matching the wire format and Geth/OpenEthereum/Erigon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

eth_getLogs documentation describes address array elements as {value: …} objects, but only plain address strings are accepted

3 participants