Allow more transaction fields for RPC field selection#371
Conversation
| loaderQueue: SDSL.Queue.t<'key>, | ||
| // Keys for items that have been loaded already. Used to evict the oldest keys from cache. | ||
| loadedKeys: SDSL.PriorityQueue.t<int>, | ||
| loadedKeys: SDSL.Queue.t<'key>, |
There was a problem hiding this comment.
It was tricky to make PriorityQueue make with a string value, so I thought that a normal queue will work for us similarly good.
There was a problem hiding this comment.
If I recall correctly the reason that priority queue was used is becaues somehow the block promises were resolving in reverse order. (ie later blocks first, earlier blocks last)
Maybe it's worth checking that we haven't broken that behaviour.
| Logging.error({ | ||
| "err": err, | ||
| "msg": `EE1100: Top level promise timeout reached. Please review other errors or warnings in the code. This function will retry in ${(am._retryDelayMillis / 1000) | ||
| ->Belt.Int.toString} seconds. It is highly likely that your indexer isn't syncing on one or more chains currently. Also take a look at the "suggestedFix" in the metadata of this command`, | ||
| "metadata": am.metadata, | ||
| }) |
There was a problem hiding this comment.
Had to move the dependency on Logging, to move the file to npm package and be able normally edit it with ReScript watch mode enabled.
| initial_block_interval: 10_000, | ||
| backoff_multiplicative: 0.8, | ||
| acceleration_additive: 2_000, | ||
| acceleration_additive: 500, |
There was a problem hiding this comment.
We must still monitor if this is low enough I think. If queries are maxing out below 2500 here then backoff will be less than the acceleration. Obviously it's configurable so it can be dealt with case by case.
| TransactionIndex, | ||
| #[subenum(RpcTransactionField)] | ||
| Hash, | ||
| #[subenum(RpcTransactionField)] | ||
| From, | ||
| #[subenum(RpcTransactionField)] | ||
| To, | ||
| Gas, | ||
| #[subenum(RpcTransactionField)] | ||
| GasPrice, | ||
| #[subenum(RpcTransactionField)] | ||
| MaxPriorityFeePerGas, | ||
| #[subenum(RpcTransactionField)] | ||
| MaxFeePerGas, | ||
| CumulativeGasUsed, | ||
| EffectiveGasPrice, |
There was a problem hiding this comment.
We can allow more fields after moving to our own rpc client.
There was a problem hiding this comment.
Why can't we do all fields? Are the others only available in a different rpc method or something?
There was a problem hiding this comment.
Ethers simply doesn't expose them, or exposes them already in decoded way, which is difficult to handle with current schema set up.
| inProgress: Utils.Set.t<'key>, | ||
| // Keys for items that we have not started loading yet. | ||
| loaderQueue: SDSL.PriorityQueue.t<int>, | ||
| loaderQueue: SDSL.Queue.t<'key>, |
There was a problem hiding this comment.
I guess what makes me nervous of this polymorphic 'key param, is that really it's constrained to a number or a string else there could be unexpected behaviours. If it were an object for instance then there is more nuance to using it.
My type OCD is making me want to see this 'key type either statically typed as Int(int) | Str(string) or to pass a toString function rather than casting with Utils.magic
| switch am.onError { | ||
| | None => () | ||
| | Some(onError) => onError(am, ~exn=err) | ||
| } |
There was a problem hiding this comment.
A thought I had with this side effect logging, is that we may want to be able clean up/cancel this onError (or even the actual task) from where the lazy loader is called. In case you want to implemented a higher level timeout etc. Then you don't have to worry about stale logs and retries after you've already handled it etc.
There was a problem hiding this comment.
I scoped out all the timeout handling changes for now
| // A hot fix after we use the version where it's supported | ||
| // https://github.com/DZakh/rescript-schema/blob/v8.4.0/docs/rescript-usage.md#removetypevalidation | ||
| let removeTypeValidationInPlace = schema => { | ||
| // The variables input is guaranteed to be an object, so we reset the rescript-schema type filter here | ||
| (schema->Obj.magic)["f"] = () | ||
| } | ||
|
|
There was a problem hiding this comment.
😅 I will trust you on this, I have no idea what's going on here
| let nativeSchema: S.t<bigint> = S.custom("BigInt", s => { | ||
| { | ||
| parser: unknown => { | ||
| if Js.typeof(unknown) !== "bigint" { | ||
| s.fail("Expected bigint") | ||
| } else { | ||
| unknown->Obj.magic | ||
| } | ||
| }, | ||
| } | ||
| }) |
There was a problem hiding this comment.
Is this not built into rescript built into rescript schema out of interest?
| } | ||
|
|
||
| pub fn to_rescript_schema(&self) -> String { | ||
| pub fn to_rescript_schema(&self, with_db_coerce: bool) -> String { |
There was a problem hiding this comment.
It seems like horrible param tunnelling to just handle the one bigint case...
Can't we find a way to improve this? Maybe having explicit functions that don't take the boolean flag. Or just have a dedicated rescript type for the native version?
Also I don't like seeing boolean flags called in functions because you have to go to the definition to understand what they are for. Better to use an enum for the flag or wrap the bool in a new type like struct WithDbCoerce(bool)
| | [{location: "hash"}] | ||
| | [{location: "hash"}, {location: "transactionIndex"}] | ||
| | [{location: "transactionIndex"}, {location: "hash"}] => | ||
| (log: Ethers.log) => | ||
| { | ||
| "hash": log.transactionHash, | ||
| "transactionIndex": log.transactionIndex, | ||
| } | ||
| ->parseOrThrowReadableError | ||
| ->Promise.resolve |
There was a problem hiding this comment.
Looks like it will add transactionIndex regardless of whether it was selected when hash is selected
There was a problem hiding this comment.
Just realised you are still calling parseOrThrowReadableError! No need to respond to the above
There was a problem hiding this comment.
It will be stripped by the parse function
DZakh
left a comment
There was a problem hiding this comment.
@JonoPrest Could you review the changes after your previous review?
| // The function used to load the result. | ||
| loaderFn: 'key => promise<'value>, |
There was a problem hiding this comment.
Having key as an unboxed variant would make the loaderFn annoying for usage, so I've decided to use Map to support literally any 'key type.
There was a problem hiding this comment.
Cool, that works 🙂
I still think just passing toString dependency is the best because it can avoid any confusion about passing reference vs a new object etc. And I'm sure Js.Dict is more performant than Map etc
There was a problem hiding this comment.
Makes sense. But I assume even thoug Js.Dict.t more performant than Map, but after adding toString there shouldn't be a difference.
| #[derive(Debug, PartialEq, Clone)] | ||
| pub enum RescriptSchemaMode { | ||
| ForDb, | ||
| ForFieldSelection, | ||
| } |
There was a problem hiding this comment.
Like how it looks like with the explicit mode
There was a problem hiding this comment.
Yeah definitely much easier to understand
| am.resolvers | ||
| ->Utils.Map.get(k) | ||
| ->Belt.Option.forEach(r => { | ||
| let _ = am.resolvers->Utils.Map.delete(k) |
There was a problem hiding this comment.
Added the line here. Previously there was a memory leak, since we never cleaned up the promise resolvers.
| } | ||
|
|
||
| pub fn to_rescript_schema(&self, with_db_coerce: bool) -> String { | ||
| pub fn to_rescript_schema(&self, mode: &RescriptSchemaMode) -> String { |
There was a problem hiding this comment.
Why does it need to be a reference out of interest? Can't the fn just take ownership of the flag?
There was a problem hiding this comment.
It would require to do .clone in a few places, so I thought it's simpler to work with a reference.
Nice @DZakh looks good, made some last comments but happy for you to merge whenever |

In the next PRs: