-
Notifications
You must be signed in to change notification settings - Fork 918
refactor(experimental): add RPC methods returning just a number #1271
Conversation
Changing back to draft while I figure out the build error, sorry about that |
Ok all good, just had to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do indeed prefer the ‘repetitive but separate’ approach to tests, because:
- reading through all the interpolation and metaprogramming in this PR's test makes me think too hard
- if (when) someone breaks the RPC implementation for one of these suckers it's going to show up in a test called ‘getBigInt’ which will again require thinking
- some of these methods are actually different (some return context, and others a raw value) so you have logic in this file that doesn't actually apply to all.
- You could imagine adding more special cases here which would make these tests even more dissimilar. Could you actually start the validator with a custom minimum stake delegation and then add a test for that? Might you write a test that actually lands a transaction and asserts that the value of
getTransactionCount()
increased? - when we add a new RPC method that only returns an integer, it will be really obvious that the author didn't add a test if there's literally no file for it, but less obvious if it's just missed here. A 1:1 correspondence between a method definition and a test file makes it easy to spot.
I was going to say ‘despite all that I don't feel super strongly about this,’ but the more bullet points I added the more I think… yeah maybe I do :)
@@ -0,0 +1,10 @@ | |||
import { RpcResponse, Slot } from './common'; | |||
|
|||
type GetFirstAvailableBlockApiResponse = RpcResponse<Slot>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't return an RpcResponse
in fact. It's just the raw integer!
// Not an `RpcResponse<TValue>`...
{context: {...}, value: 1234}
// Actually just `TValue`
1234
type GetFirstAvailableBlockApiResponse = RpcResponse<Slot>; | |
type GetFirstAvailableBlockApiResponse = Slot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well at least I got it right in my PR description 😓 any idea about why this didn't get picked up by the compiler or the tests?
Edit: never mind, it makes sense that Reflect.get
wouldn't be able to notice anything wrong since we're going around the compiler. Another point in favor of the separated tests!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah! I mean we could write Typescript tests, but that’s pretty next level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case it wasn't clear what I meant by this, some people actually have written tests that assert whether source code raises type errors or not. Pretty extreme.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha don't worry, it was very clear. I didn't want to go there under any circumstances 😅
@@ -0,0 +1,14 @@ | |||
import { RpcResponse, U64UnsafeBeyond2Pow53Minus1, Commitment } from './common'; | |||
|
|||
type GetStakeMinimumDelegationApiResponse = RpcResponse<U64UnsafeBeyond2Pow53Minus1>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think at this point it's worth creating a branded Lamports
type. See Base58EncodedAddress
for how to create a branded type.
This will imply that you can create a function that only accepts Lamports
as a type and if you try to pass it a non-branded BigInt
you'll get a Typescript error. Should save a couple million in mistake fees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah sure, where do you want it go? There's no clear place for it, so I'd probably just default to the top-level of rpc-core or in rpc-methods/common.ts
but I'm not sure if it's an RPC-specific type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a @solana/keys
package which is a nice place to put the address types, but for everything else I’ve just been hucking ‘primitive’ types into global typedefs.
Throw it anywhere for now, just try to avoid circular references between modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, put it under common.ts
Ok, this should be ready for another look! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!!! I'll fix up my own nits and land this.
}); | ||
}); | ||
describe('when called with no parameters', () => { | ||
it('returns a bigint', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess there are probably reasons why it's not safe to presume that the test validator will always return 0n
here? I seem to remember that being a problem before: solana-labs/solana#23853
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the stake delegation test, I didn't want this test to be in the business of validating the correctness of the response.
it('returns the result as a bigint wrapped in an RpcResponse', async () => { | ||
expect.assertions(1); | ||
const result = await rpc.getStakeMinimumDelegation({ commitment }).send(); | ||
expect(result.value).toEqual(expect.any(BigInt)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we drop a TODO
here to actually fetch the minimum delegation value from the stake program?
Probably not, now that I think about it. This test shouldn't really be in the business of testing the underlying method. It should stop short of testing the type of the responses, as you've done here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah exactly, that was my intention
@@ -19,3 +19,5 @@ export type RpcResponse<TValue> = Readonly<{ | |||
}>; | |||
value: TValue; | |||
}>; | |||
|
|||
export type Lamports = U64UnsafeBeyond2Pow53Minus1 & { readonly __lamports: unique symbol }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmmm… I think this is more like…
export type Lamports = U64UnsafeBeyond2Pow53Minus1 & { readonly __lamports: unique symbol }; | |
export type LamportsUnsafeBeyond2Pow53Minus1 = bigint & { readonly __lamports: unique symbol }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you wish! In that case, do you also want to do SlotUnsafeBeyond2Pow53Minus1
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked how long it would take to get there and @willhickey told me ~114M years, so I think I decided against it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not opposed to it, for completeness, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah gotcha, that definitely makes sense
* Returns the lowest slot that the node has information about in its ledger. | ||
* This value may increase over time if the node is configured to purge older ledger data. | ||
*/ | ||
minimumLedgerSlot(): MinimumLedgerSlotApiResponse; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOL @ no get
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same 😄
🎉 This PR is included in version 1.76.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Because there has been no activity on this PR for 14 days since it was merged, it has been automatically locked. Please open a new issue if it requires a follow up. |
Summary
While doing one of the simple RPC calls, I noticed there's a bunch of simple RPC calls that take just a commitment with optional minContextSlot, or nothing at all, and return either a number, or a number wrapped in an rpc response. The calls covered here are:
Since so many of the tests just ended up doing the same as the
getBlockHeight
test, I moved them all into the same file. Then I noticed that some of them take parameters and some don't, and still kept them in the same file. Then I noticed some return just a number, and others wrap it, and still I kept them in the same file.So if you want these broken up differently, I'm totally game! Also, I used the
each
construction, which I'm not sure if that's best or if you prefer something else.Test Plan
Also, I ran prettier by hand with
pnpm prettier --write '{,{src,test}/**/}*.{j,t}s'
, I didn't find any other scripts or configuration, and it seemed to pass the prettier test.