Skip to content

Allow more transaction fields for RPC field selection#371

Merged
DZakh merged 12 commits into
mainfrom
dz/rpc-field-selection
Dec 4, 2024
Merged

Allow more transaction fields for RPC field selection#371
DZakh merged 12 commits into
mainfrom
dz/rpc-field-selection

Conversation

@DZakh

@DZakh DZakh commented Nov 29, 2024

Copy link
Copy Markdown
Member

In the next PRs:

  • Delete cache after finishing fetching the batch
  • Add field selection by event, otherwise, it will add too many unnecessary rpc calls for indexers with many event kinds

@DZakh DZakh marked this pull request as draft November 29, 2024 15:44
@DZakh DZakh changed the title Add field selection for RPC Allow more transaction fields for RPC field selection Dec 2, 2024
@DZakh DZakh requested a review from JonoPrest December 2, 2024 13:14
@DZakh DZakh marked this pull request as ready for review December 2, 2024 13:14
Comment on lines +20 to +22
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>,

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.

It was tricky to make PriorityQueue make with a string value, so I thought that a normal queue will work for us similarly good.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

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.

Looks good
image

Comment on lines -102 to -107
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,
})

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.

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,

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.

Changed after discussion

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment on lines 220 to 235
TransactionIndex,
#[subenum(RpcTransactionField)]
Hash,
#[subenum(RpcTransactionField)]
From,
#[subenum(RpcTransactionField)]
To,
Gas,
#[subenum(RpcTransactionField)]
GasPrice,
#[subenum(RpcTransactionField)]
MaxPriorityFeePerGas,
#[subenum(RpcTransactionField)]
MaxFeePerGas,
CumulativeGasUsed,
EffectiveGasPrice,

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.

We can allow more fields after moving to our own rpc client.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why can't we do all fields? Are the others only available in a different rpc method or something?

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.

Ethers simply doesn't expose them, or exposes them already in decoded way, which is difficult to handle with current schema set up.

Comment on lines +18 to +20
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>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Comment on lines +102 to +105
switch am.onError {
| None => ()
| Some(onError) => onError(am, ~exn=err)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

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.

I scoped out all the timeout handling changes for now

Comment on lines +234 to +240
// 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"] = ()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

😅 I will trust you on this, I have no idea what's going on here

Comment on lines +59 to +69
let nativeSchema: S.t<bigint> = S.custom("BigInt", s => {
{
parser: unknown => {
if Js.typeof(unknown) !== "bigint" {
s.fail("Expected bigint")
} else {
unknown->Obj.magic
}
},
}
})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this not built into rescript built into rescript schema out of interest?

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.

It's in v8.4.0

Comment thread codegenerator/cli/src/rescript_types.rs Outdated
}

pub fn to_rescript_schema(&self) -> String {
pub fn to_rescript_schema(&self, with_db_coerce: bool) -> String {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)

Comment on lines +41 to +50
| [{location: "hash"}]
| [{location: "hash"}, {location: "transactionIndex"}]
| [{location: "transactionIndex"}, {location: "hash"}] =>
(log: Ethers.log) =>
{
"hash": log.transactionHash,
"transactionIndex": log.transactionIndex,
}
->parseOrThrowReadableError
->Promise.resolve

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like it will add transactionIndex regardless of whether it was selected when hash is selected

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just realised you are still calling parseOrThrowReadableError! No need to respond to the above

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.

It will be stripped by the parse function

@JonoPrest JonoPrest left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey @DZakh, just a couple of comments/nitpicks but nothing blocking. Thanks so much looks awesome!

@DZakh DZakh left a comment

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.

@JonoPrest Could you review the changes after your previous review?

Comment on lines +23 to +24
// The function used to load the result.
loaderFn: 'key => promise<'value>,

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.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

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.

Makes sense. But I assume even thoug Js.Dict.t more performant than Map, but after adding toString there shouldn't be a difference.

Comment on lines +13 to +17
#[derive(Debug, PartialEq, Clone)]
pub enum RescriptSchemaMode {
ForDb,
ForFieldSelection,
}

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.

Like how it looks like with the explicit mode

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yeah definitely much easier to understand

am.resolvers
->Utils.Map.get(k)
->Belt.Option.forEach(r => {
let _ = am.resolvers->Utils.Map.delete(k)

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.

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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why does it need to be a reference out of interest? Can't the fn just take ownership of the flag?

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.

It would require to do .clone in a few places, so I thought it's simpler to work with a reference.

@JonoPrest

Copy link
Copy Markdown
Collaborator

@JonoPrest Could you review the changes after your previous review?

Nice @DZakh looks good, made some last comments but happy for you to merge whenever

@DZakh DZakh merged commit 415d1fa into main Dec 4, 2024
@DZakh DZakh deleted the dz/rpc-field-selection branch December 4, 2024 12:36
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.

2 participants