timostamm / protobuf-ts

Protobuf and RPC for TypeScript
Apache License 2.0
1.09k stars 130 forks source link

How to debug INVALID_ARGUMENT? #626

Open banool opened 9 months ago

banool commented 9 months ago

I've got code that looks like this:

let transport = new GrpcWebFetchTransport({
    baseUrl: "http://127.0.0.1:50051",
    format: "text",
});

let client = new RawDataClient(transport);

const request = GetTransactionsRequest.create({
  starting_version: "0",
});

console.log(`request: ${JSON.stringify(request, (_, v) => typeof v === 'bigint' ? v.toString() : v)}`);

const requestOptions = {
    meta: {},
};

let streamingCall = await client.getTransactions(request, requestOptions);
console.log("Data", JSON.stringify(streamingCall, null, 2));

It is generated from this buf file:

version: v1
managed:
  enabled: true
plugins:
  - plugin: buf.build/community/timostamm-protobuf-ts:v2.9.3
    out: tsweb/src
    opt:
      # - long_type_string
      - long_type_bigint
      - server_none
      - optimize_code_size
      - output_typescript
      - generate_dependencies
      - keep_enum_prefix
      # probs not necessary
      - use_proto_field_name

When I run this, I get the following error:

      {
        "name": "RpcError",
        "code": "INVALID_ARGUMENT",
        "meta": {
          "access-control-allow-credentials": "true",
          "access-control-allow-origin": "*",
          "access-control-expose-headers": "*",
          "content-length": "0",
          "date": "Wed, 24 Jan 2024 23:24:35 GMT",
          "vary": "origin, access-control-request-method, access-control-request-headers"
        },
        "methodName": "GetTransactions",
        "serviceName": "aptos.indexer.v1.RawData"
      }

Thing is, when I make the exact same request with grpcurl it works fine:

grpcurl -plaintext -d '{ "starting_version": 0 }' 127.0.0.1:50051 aptos.indexer.v1.RawData/GetTransactions

On the server side I'm using tonic-web, so grpc-web should be supported directly.

When the client connects, I can see that the server builds and sends the response, but then it says the client aborts the connection and disconnects. This leads me to believe something is wrong client side.

I've confirmed that the compression settings match, that it isn't related to auth, etc. I've even wiresharked the request packets and I think at least the core payload looks the same between grpcurl and the TS code. In any case, I feel like the issue is related to how the client builds the request or maybe handles the response, since the server seems to think the request is perfectly fine.

Creating a minimal repro is sort of complex since there are a lot of moving parts but I will do that later. For now though I wonder if there is some way to figure out why this error is happening. I've added server side logging and it doesn't seem like the server is rejecting the request, so I'm not sure why the code is INVALID_ARGUMENT. I've confirmed that the server and client are using code generated from the same protos. I've tried setting grpc.keepalive_time_ms and grpc.max_send_message_length and the like.

Any ideas would be greatly appreciated, I've been sort of losing my marbles for the last 12 hours 😅

timostamm commented 8 months ago

Hey Daniel, apologies for the late response. It's hard to tell what's going on just based on the code INVALID_ARGUMENT. Looking at grpc-web-transport.ts and grpc-web-format.ts, I don't see us raising this error code 🤔

I would add add breakpoints or log statements in @protobuf-ts/grpcweb-transport to verify that the error is indeed part of the server response.

If it is, the server is most likely raising the error because it received headers or a body that it does not accept. It is possible that our client understands a part of the gRPC-Web spec differently than tonic-web. Unfortunately, the spec is quite vague. Our client runs just fine against dotnet (there's an example in this repository), and I've also been using it against envoy without issues. But it's possible that tonic does something differently. So as a next step, I'd add breakpoints or log statements in the server.