Page MenuHomePhabricator

[chronik-client] Support websockets in ChronikClientNode
ClosedPublic

Authored by bytesofman on Feb 8 2024, 18:14.

Details

Summary

Support for websocket connections to in-node versions of chronik

Test Plan

See D14915 for instructions or wait for CI

npm test for new subscription validation function

Diff Detail

Repository
rABC Bitcoin ABC
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
modules/chronik-client/test/vectors.ts
45 ↗(On Diff #45304)

TypeScript prevents you now from doing this

80 ↗(On Diff #45304)

This test can now be removed, TypeScript will catch it

bytesofman added inline comments.
modules/chronik-client/src/ChronikClientNode.ts
846 ↗(On Diff #45304)

?

seems this matches the proto,

/** Error message returned from our APIs. */
export interface Error {
    /** 2, as legacy chronik uses this for the message so we're still compatible. */
    msg: string;
}

what should it be?

modules/chronik-client/test/vectors.ts
45 ↗(On Diff #45304)

JS users of the lib could still do this though right?

80 ↗(On Diff #45304)

JS users of lib?

modules/chronik-client/test/vectors.ts
80 ↗(On Diff #45304)

Touché. Then you have to coerce the type "madeitup" as any as "other" or so, or change isValidWsSubscription to allow for string inputs

modules/chronik-client/src/ChronikClientNode.ts
846 ↗(On Diff #45304)
bytesofman marked 7 inline comments as done.

get npm test to work with types and coerced types, clean up other types, org suggestions from inline comments

modules/chronik-client/src/ChronikClientNode.ts
419 ↗(On Diff #45317)
if (typeof msg.error !== 'undefined') {
            this.onMessage({ type: 'Error', ...msg.error });
        }

@tobias_ruck is this right? I'm not sure how to get this from a test. modeling the implementation from nng chronik-client.

modules/chronik-client/src/ChronikClientNode.ts
419 ↗(On Diff #45317)

It's ok; probably better to just do this. NNG had more fields here, but no need to make it complicated if they aren't there.

You can test it by sending anything that's currently caught by your validation, e.g. send a p2pkh subscription with anything other than 20 bytes of payload.

At least that's what's supposed to happen.

separate blocks subunsub function

modules/chronik-client/src/ChronikClientNode.ts
419 ↗(On Diff #45317)

If I back out the client-side validation and run ws.sub('p2pkh', '1111111111') -- this gets added to ws.subs and I get no error msg from the websocket.

Would be a tough API to handle as an app dev either way, since the error in this case would not be thrown by the subscription, but arrive as a msg that needs to be listend for, specifically parsed, then the dev needs to know to unsub from the bad one. Prob the dev has some msg handler setup that is only looking for type: 'Tx' msgs, could be missed or misunderstood.

So imo catching it client side before we even send the msg is preferable.

Still, potential issue if expected behavior is to get the error as a msg from the ws.

If I back out the client-side validation and run ws.sub('p2pkh', '1111111111') -- this gets added to ws.subs and I get no error msg from the websocket.
Still, potential issue if expected behavior is to get the error as a msg from the ws.

This is fixed in D15472, there was an error sent from Chronik but it was encoded in the wrong msg.

So imo catching it client side before we even send the msg is preferable.

Yes, agreed. Ideally it would remove the sub from ws.subs, but I don't think we have that info available.

If we handle the error for the user (let'd do that), we should remove the Error from WsMsgClient sent via onMessage. Also, we might want to change onError to receive either the JS WebSocket error or the protobuf error from the Error message. This is because we might want to add more failure modes other than failed parsing in the future and apps being prepared to receive and handle those errors is an advantage. Also, our validation might not catch all cases so having some way to receive those errors is still an advantage.

If I back out the client-side validation and run ws.sub('p2pkh', '1111111111') -- this gets added to ws.subs and I get no error msg from the websocket.
Still, potential issue if expected behavior is to get the error as a msg from the ws.

This is fixed in D15472, there was an error sent from Chronik but it was encoded in the wrong msg.

So imo catching it client side before we even send the msg is preferable.

Yes, agreed. Ideally it would remove the sub from ws.subs, but I don't think we have that info available.

If we handle the error for the user (let'd do that), we should remove the Error from WsMsgClient sent via onMessage. Also, we might want to change onError to receive either the JS WebSocket error or the protobuf error from the Error message. This is because we might want to add more failure modes other than failed parsing in the future and apps being prepared to receive and handle those errors is an advantage. Also, our validation might not catch all cases so having some way to receive those errors is still an advantage.

I can confirm we now get the expected error msgs from chronik, e.g.

{
      "msg": "400: Invalid payload for P2PKH: Invalid length, expected 20 bytes but got 5 bytes"
      "type": "Error"
}

For now, the only instances that throw this error -- attempting to subscribe to invalid scripts -- are handled in front-end validation.

Also, we might want to change onError to receive either the JS WebSocket error or the protobuf error from the Error message.

I'm not sure how we could do this. Right now, a JS error would simply throw an error -- like what the client-side validation is already doing. Any server-side error being communicated to the client as a chronik msg seems fine -- and is handled already. I guess we could throw an error if we get this kind of msg from the server? But hard to say if that's really what the dev wants.

Leaving it as is -- i.e., the app dev gets a msg that is clearly an error msg and can figure out how to handle it, even if they are at the moment caught client-side -- probably the best approach for now.

The build failed due to an unexpected infrastructure outage. The administrators have been notified to investigate. Sorry for the inconvenience.
tobias_ruck added inline comments.
modules/chronik-client/src/ChronikClientNode.ts
320 ↗(On Diff #45346)

there will be subscribeToTokenId too and probably subscribeToPlugin, I think it's best to not make the script one special by being "the subscribe"

390 ↗(On Diff #45346)
417 ↗(On Diff #45346)

what do you think about moving this to this.onError(msg.error)? Either way works for me

768 ↗(On Diff #45346)

note: if you do move the error to onError, you can remove the error here; up to you

820 ↗(On Diff #45346)

this comment needs to be fixed

833 ↗(On Diff #45346)

this comment needs to be fixed

modules/chronik-client/test/integration/mempoolconflicts.ts
1 ↗(On Diff #45346)

no copyright notice? they seem to be missing in a lot of files in this diff; calling my lawyer right now

This revision now requires changes to proceed.Feb 18 2024, 17:01
bytesofman marked 7 inline comments as done.

separate functions for subunsub by what is being subscribed/unsubsrcibed to, improve comment, remove now-unused type

modules/chronik-client/src/validation.ts
7 ↗(On Diff #45367)

maybe move this out of the function; dunno if JS is super fast with creating regex objects

modules/chronik-client/test/integration/websocket.ts
309 ↗(On Diff #45367)

add a newline

Fabien requested changes to this revision.Feb 19 2024, 09:06
Fabien added inline comments.
modules/chronik-client/src/ChronikClientNode.ts
302 ↗(On Diff #45367)

The subUnsub* API is using a confusing double negation (to subscribe, you use isunsub = false). This is also inconsistent with the current NNG API, is there a rational behind this change ?

377 ↗(On Diff #45367)

Any reason this is public (and exported) ? Apart from the above comment about the bool param, this also looks like something that should not be used directly by the calling app

377 ↗(On Diff #45367)

Why is this public (and exported) ? This doesn't look like a method that should be called directly by the calling app

390 ↗(On Diff #45367)

same

modules/chronik-client/src/validation.ts
7 ↗(On Diff #45367)

+1

modules/chronik-client/test/integration/websocket.ts
13 ↗(On Diff #45367)

Why is that needed ?

This revision now requires changes to proceed.Feb 19 2024, 09:06
modules/chronik-client/src/ChronikClientNode.ts
302 ↗(On Diff #45367)

It was changed in the in-node Chronik; the rationale was that protobuf has defaults if omitted and the default for a bool is false, and it seemed more natural for a subscription message to default to subscribe as opposed to unsubscribe.

modules/chronik-client/src/ChronikClientNode.ts
302 ↗(On Diff #45367)

OK

bytesofman marked 10 inline comments as done.

cleaning up based on feedback, move subunsub functions to private, move regexp generation out of validation function, line break

modules/chronik-client/src/ChronikClientNode.ts
417 ↗(On Diff #45346)

It does seem like a better design. Lots of reasons onError method should be different from onMessage.

However, without any reachable error events, it's purely hypothetical that this would be preferable in the future. imo should be its own diff, and should have clear impact to warrant a diff.

Being untestable, I'm ok holding off.

833 ↗(On Diff #45346)

now that all the subunsub functions are distinct, we don't need this combined type. not used anywhere else in the file but here. removed.

377 ↗(On Diff #45367)

oversight, fixed

this was called by failoverProxy, so had to be public. But, no reason failoverProxy can't use the now-less-ambiguous subscribeToScript and subscribeToBlocks methods

390 ↗(On Diff #45367)

oversight, fixed

modules/chronik-client/test/integration/mempoolconflicts.ts
1 ↗(On Diff #45346)

been a TODO for awhile now...need to handle this with a linter.

modules/chronik-client/test/integration/websocket.ts
13 ↗(On Diff #45367)

using @ts-ignore will throw a compilation error without this.

@ts-ignore is used two times in this file to test validation errors that would only be possible to experience for JS users of this lib.

rebase, add headers to files created for this diff

modules/chronik-client/src/ChronikClientNode.ts
417 ↗(On Diff #45346)

I’m ok with the current design, it means we have a way for users to handle errors and if there’s testable errors in the future it won’t be a breaking change to start to emit those.

modules/chronik-client/test/integration/websocket.ts
226 ↗(On Diff #45367)

If you want to avoid the ts-ignore comment you can do this

Fabien requested changes to this revision.Feb 20 2024, 08:11

one last thing, otherwise lgtm

modules/chronik-client/src/validation.ts
7 ↗(On Diff #45402)

Another option: change the payout to lowercase before testing

This revision now requires changes to proceed.Feb 20 2024, 08:11

rebase, version bump, clarify that hex payload strings must be lowercase and add tests

bytesofman added inline comments.
modules/chronik-client/src/validation.ts
7 ↗(On Diff #45402)

Existing hex conversion functions in src/hex.ts only support lowercase hex input.

I looked at applying toLowerCase() before passing to the server -- this would support uppercase and mixed case user input. However, I think it is better to restrict everything to lowercase. In this way everything in this.subs is standard and equal to what the user subscribed to.

I added unit tests confirming that uppercase and mixed case inputs are rejected and updated the error msg to reflect this.

bytesofman marked an inline comment as done.

remove unrelated change from testing toLowerCase on payload inputs

This revision is now accepted and ready to land.Feb 20 2024, 14:11