From a03ae271e32be7e107403aae78baafe726abc227 Mon Sep 17 00:00:00 2001 From: gagliardetto Date: Mon, 18 Sep 2023 14:06:31 +0200 Subject: [PATCH] Add getBlockTime method; closes #50 --- cmd-rpc-server-car-getSignaturesForAddress.go | 18 +++--- multiepoch-getBlockTime.go | 55 +++++++++++++++++++ multiepoch.go | 10 ++-- request-response.go | 28 ++++++++-- 4 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 multiepoch-getBlockTime.go diff --git a/cmd-rpc-server-car-getSignaturesForAddress.go b/cmd-rpc-server-car-getSignaturesForAddress.go index 87f18deb..0d1f2e74 100644 --- a/cmd-rpc-server-car-getSignaturesForAddress.go +++ b/cmd-rpc-server-car-getSignaturesForAddress.go @@ -30,20 +30,20 @@ type GetSignaturesForAddressParams struct { func parseGetSignaturesForAddressParams(raw *json.RawMessage) (*GetSignaturesForAddressParams, error) { var params []any if err := json.Unmarshal(*raw, ¶ms); err != nil { - klog.Errorf("failed to unmarshal params: %v", err) - return nil, err + return nil, fmt.Errorf("failed to unmarshal params: %w", err) + } + if len(params) < 1 { + return nil, fmt.Errorf("expected at least 1 param") } sigRaw, ok := params[0].(string) if !ok { - klog.Errorf("first argument must be a string") - return nil, nil + return nil, fmt.Errorf("first argument must be a string") } out := &GetSignaturesForAddressParams{} pk, err := solana.PublicKeyFromBase58(sigRaw) if err != nil { - klog.Errorf("failed to parse pubkey from base58: %v", err) - return nil, err + return nil, fmt.Errorf("failed to parse pubkey from base58: %w", err) } out.Address = pk @@ -60,8 +60,7 @@ func parseGetSignaturesForAddressParams(raw *json.RawMessage) (*GetSignaturesFor if before, ok := before.(string); ok { sig, err := solana.SignatureFromBase58(before) if err != nil { - klog.Errorf("failed to parse signature from base58: %v", err) - return nil, err + return nil, fmt.Errorf("failed to parse signature from base58: %w", err) } out.Before = &sig } @@ -70,8 +69,7 @@ func parseGetSignaturesForAddressParams(raw *json.RawMessage) (*GetSignaturesFor if after, ok := after.(string); ok { sig, err := solana.SignatureFromBase58(after) if err != nil { - klog.Errorf("failed to parse signature from base58: %v", err) - return nil, err + return nil, fmt.Errorf("failed to parse signature from base58: %w", err) } out.Until = &sig } diff --git a/multiepoch-getBlockTime.go b/multiepoch-getBlockTime.go new file mode 100644 index 00000000..a9b87b71 --- /dev/null +++ b/multiepoch-getBlockTime.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "errors" + "fmt" + + "github.com/rpcpool/yellowstone-faithful/compactindex36" + "github.com/sourcegraph/jsonrpc2" +) + +func (multi *MultiEpoch) handleGetBlockTime(ctx context.Context, conn *requestContext, req *jsonrpc2.Request) (*jsonrpc2.Error, error) { + blockNum, err := parseGetBlockTimeRequest(req.Params) + if err != nil { + return &jsonrpc2.Error{ + Code: jsonrpc2.CodeInvalidParams, + Message: "Invalid params", + }, fmt.Errorf("failed to parse params: %w", err) + } + + // find the epoch that contains the requested slot + epochNumber := CalcEpochForSlot(blockNum) + epochHandler, err := multi.GetEpoch(epochNumber) + if err != nil { + return &jsonrpc2.Error{ + Code: CodeNotFound, + Message: fmt.Sprintf("Epoch %d is not available", epochNumber), + }, fmt.Errorf("failed to get epoch %d: %w", epochNumber, err) + } + + block, err := epochHandler.GetBlock(WithSubrapghPrefetch(ctx, false), blockNum) + if err != nil { + if errors.Is(err, compactindex36.ErrNotFound) { + return &jsonrpc2.Error{ + Code: CodeNotFound, + Message: fmt.Sprintf("Slot %d was skipped, or missing in long-term storage", blockNum), + }, err + } else { + return &jsonrpc2.Error{ + Code: jsonrpc2.CodeInternalError, + Message: "Failed to get block", + }, fmt.Errorf("failed to get block: %w", err) + } + } + blockTime := uint64(block.Meta.Blocktime) + err = conn.ReplyRaw( + ctx, + req.ID, + blockTime, + ) + if err != nil { + return nil, fmt.Errorf("failed to reply: %w", err) + } + return nil, nil +} diff --git a/multiepoch.go b/multiepoch.go index 2844eabe..3b7385b2 100644 --- a/multiepoch.go +++ b/multiepoch.go @@ -277,7 +277,7 @@ func newMultiEpochHandler(handler *MultiEpoch, lsConf *ListenerConfig) func(ctx klog.Infof("[%s] received request: %q", reqID, strings.TrimSpace(string(body))) - if proxy != nil && !isValidMethod(rpcRequest.Method) { + if proxy != nil && !isValidLocalMethod(rpcRequest.Method) { klog.Infof("[%s] Unhandled method %q, proxying to %q", reqID, rpcRequest.Method, proxy.Addr) // proxy the request to the target proxyReq := fasthttp.AcquireRequest() @@ -355,15 +355,15 @@ func newMultiEpochHandler(handler *MultiEpoch, lsConf *ListenerConfig) func(ctx } func sanitizeMethod(method string) string { - if isValidMethod(method) { + if isValidLocalMethod(method) { return method } return "" } -func isValidMethod(method string) bool { +func isValidLocalMethod(method string) bool { switch method { - case "getBlock", "getTransaction", "getSignaturesForAddress": + case "getBlock", "getTransaction", "getSignaturesForAddress", "getBlockTime": return true default: return false @@ -379,6 +379,8 @@ func (ser *MultiEpoch) handleRequest(ctx context.Context, conn *requestContext, return ser.handleGetTransaction(ctx, conn, req) case "getSignaturesForAddress": return ser.handleGetSignaturesForAddress(ctx, conn, req) + case "getBlockTime": + return ser.handleGetBlockTime(ctx, conn, req) default: return &jsonrpc2.Error{ Code: jsonrpc2.CodeMethodNotFound, diff --git a/request-response.go b/request-response.go index 8e864df7..561fc45f 100644 --- a/request-response.go +++ b/request-response.go @@ -16,7 +16,6 @@ import ( "github.com/mr-tron/base58" "github.com/sourcegraph/jsonrpc2" "github.com/valyala/fasthttp" - "k8s.io/klog/v2" ) type requestContext struct { @@ -175,13 +174,14 @@ func (req *GetBlockRequest) Validate() error { func parseGetBlockRequest(raw *json.RawMessage) (*GetBlockRequest, error) { var params []any if err := json.Unmarshal(*raw, ¶ms); err != nil { - klog.Errorf("failed to unmarshal params: %v", err) - return nil, err + return nil, fmt.Errorf("failed to unmarshal params: %w", err) + } + if len(params) < 1 { + return nil, fmt.Errorf("params must have at least one argument") } slotRaw, ok := params[0].(float64) if !ok { - klog.Errorf("first argument must be a number, got %T", params[0]) - return nil, nil + return nil, fmt.Errorf("first argument must be a number, got %T", params[0]) } out := &GetBlockRequest{ @@ -313,6 +313,9 @@ func parseGetTransactionRequest(raw *json.RawMessage) (*GetTransactionRequest, e if err := json.Unmarshal(*raw, ¶ms); err != nil { return nil, fmt.Errorf("failed to unmarshal params: %w", err) } + if len(params) < 1 { + return nil, fmt.Errorf("params must have at least one argument") + } sigRaw, ok := params[0].(string) if !ok { return nil, fmt.Errorf("first argument must be a string, got %T", params[0]) @@ -412,3 +415,18 @@ func encodeBytesResponseBasedOnWantedEncoding( return nil, fmt.Errorf("unsupported encoding %q", encoding) } } + +func parseGetBlockTimeRequest(raw *json.RawMessage) (uint64, error) { + var params []any + if err := json.Unmarshal(*raw, ¶ms); err != nil { + return 0, fmt.Errorf("failed to unmarshal params: %w", err) + } + if len(params) < 1 { + return 0, fmt.Errorf("params must have at least one argument") + } + blockRaw, ok := params[0].(float64) + if !ok { + return 0, fmt.Errorf("first argument must be a number, got %T", params[0]) + } + return uint64(blockRaw), nil +}