Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tm2): Fix the endpoint /tx?hash= to make it work #2518

Open
wants to merge 46 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
58d6307
handle base64 argument
linhpn99 Jul 6, 2024
de3e620
format
linhpn99 Jul 6, 2024
70b5bf6
format
linhpn99 Jul 6, 2024
16c628a
format
linhpn99 Jul 6, 2024
9e2c995
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 6, 2024
970eed4
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 6, 2024
b389ae9
refactor code
linhpn99 Jul 7, 2024
7372b65
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 7, 2024
84db553
remove test command
linhpn99 Jul 7, 2024
00d600e
Merge branch 'fix-query-tx-by-hash' of https://github.com/linhpn99/gn…
linhpn99 Jul 7, 2024
3a226c8
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 8, 2024
e469623
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 8, 2024
58ff8fc
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 8, 2024
55269b8
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 9, 2024
87f5aca
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 9, 2024
d4caab3
use base64.URLEncoding instead
linhpn99 Jul 9, 2024
2ef7f72
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 9, 2024
5be999d
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 10, 2024
4e109bc
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 12, 2024
dc63599
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 13, 2024
f70a866
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 14, 2024
6d44919
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 14, 2024
cb1431b
revert to StdEncoding
linhpn99 Jul 15, 2024
d47583b
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 16, 2024
007cab5
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 17, 2024
a44c24d
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 20, 2024
15520d7
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 22, 2024
ce92dd7
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 22, 2024
4722db2
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 23, 2024
36bec24
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 24, 2024
2c2c0e0
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 29, 2024
d43345a
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 30, 2024
e206f8e
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Jul 30, 2024
5a98f79
fixup
linhpn99 Aug 1, 2024
b4b9b2d
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Aug 1, 2024
e4b3b62
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Aug 2, 2024
b04aa22
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Aug 10, 2024
fdbb5d6
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Aug 20, 2024
459f283
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Aug 23, 2024
dd274ab
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Aug 26, 2024
73707e0
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Sep 4, 2024
25fa669
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Sep 7, 2024
b6f7471
Merge branch 'master' into fix-query-tx-by-hash
linhpn99 Sep 11, 2024
40c7474
fixup some tests
thehowl Oct 23, 2024
19f6faa
oops
thehowl Oct 23, 2024
9f13812
fixup
thehowl Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 32 additions & 102 deletions tm2/pkg/bft/rpc/lib/server/handlers.go
thehowl marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -332,130 +333,59 @@
}
}

var reInt = regexp.MustCompile(`^-?[0-9]+$`)

// Convert an http query to a list of properly typed values.
// To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
// skip types.Context
const argsOffset = 1

values := make([]reflect.Value, len(rpcFunc.argNames))

for i, name := range rpcFunc.argNames {
argType := rpcFunc.args[i+argsOffset]

values[i] = reflect.Zero(argType) // set default for that type

arg := GetParam(r, name)
// log.Notice("param to arg", "argType", argType, "name", name, "arg", arg)

paramsMap := make(map[string]json.RawMessage)
for _, argName := range rpcFunc.argNames {
arg := GetParam(r, argName)
if arg == "" {
continue
}

v, err, ok := nonJSONStringToArg(argType, arg)
if err != nil {
return nil, err
}
if ok {
values[i] = v
continue
}
// Handle hex string
if strings.HasPrefix(arg, "0x") {
decoded, err := hex.DecodeString(arg[2:])
if err != nil {
return nil, errors.Wrap(err, "error decoding hex string")

Check warning on line 354 in tm2/pkg/bft/rpc/lib/server/handlers.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/bft/rpc/lib/server/handlers.go#L354

Added line #L354 was not covered by tests
}

values[i], err = jsonStringToArg(argType, arg)
if err != nil {
return nil, err
}
}
data, err := amino.MarshalJSON(decoded)
if err != nil {
return nil, errors.Wrap(err, "error marshaling argument to JSON")

Check warning on line 359 in tm2/pkg/bft/rpc/lib/server/handlers.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/bft/rpc/lib/server/handlers.go#L359

Added line #L359 was not covered by tests
}

return values, nil
}
paramsMap[argName] = data

func jsonStringToArg(rt reflect.Type, arg string) (reflect.Value, error) {
rv := reflect.New(rt)
err := amino.UnmarshalJSON([]byte(arg), rv.Interface())
if err != nil {
return rv, err
}
rv = rv.Elem()
return rv, nil
}

func nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, error, bool) {
if rt.Kind() == reflect.Ptr {
rv_, err, ok := nonJSONStringToArg(rt.Elem(), arg)
switch {
case err != nil:
return reflect.Value{}, err, false
case ok:
rv := reflect.New(rt.Elem())
rv.Elem().Set(rv_)
return rv, nil, true
default:
return reflect.Value{}, nil, false
continue
}
} else {
return _nonJSONStringToArg(rt, arg)
}
}

var reInt = regexp.MustCompile(`^-?[0-9]+$`)

// NOTE: rt.Kind() isn't a pointer.
func _nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, error, bool) {
isIntString := reInt.Match([]byte(arg))
isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`)
isHexString := strings.HasPrefix(strings.ToLower(arg), "0x")

var expectingString, expectingByteSlice, expectingInt bool
switch rt.Kind() {
case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
expectingInt = true
case reflect.String:
expectingString = true
case reflect.Slice:
expectingByteSlice = rt.Elem().Kind() == reflect.Uint8
}

if isIntString && expectingInt {
qarg := `"` + arg + `"`
// jsonStringToArg
rv, err := jsonStringToArg(rt, qarg)
if err != nil {
return rv, err, false
}
// Handle base64 string
if decoded, err := base64.StdEncoding.DecodeString(arg); err == nil {
data, err := amino.MarshalJSON(decoded)
if err != nil {
return nil, errors.Wrap(err, "error marshaling argument to JSON")

Check warning on line 371 in tm2/pkg/bft/rpc/lib/server/handlers.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/bft/rpc/lib/server/handlers.go#L371

Added line #L371 was not covered by tests
}
thehowl marked this conversation as resolved.
Show resolved Hide resolved

return rv, nil, true
}
paramsMap[argName] = data

if isHexString {
if !expectingString && !expectingByteSlice {
err := errors.New("got a hex string arg, but expected '%s'",
rt.Kind().String())
return reflect.ValueOf(nil), err, false
continue
}

var value []byte
value, err := hex.DecodeString(arg[2:])
if err != nil {
return reflect.ValueOf(nil), err, false
}
if rt.Kind() == reflect.String {
return reflect.ValueOf(string(value)), nil, true
// Handle int string
if reInt.Match([]byte(arg)) {
arg = fmt.Sprintf("%q", arg)
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}
return reflect.ValueOf(value), nil, true
}

if isQuotedString && expectingByteSlice {
v := reflect.New(reflect.TypeOf(""))
err := amino.UnmarshalJSON([]byte(arg), v.Interface())
if err != nil {
return reflect.ValueOf(nil), err, false
}
v = v.Elem()
return reflect.ValueOf([]byte(v.String())), nil, true
// Default: treat the argument as a JSON raw message
paramsMap[argName] = json.RawMessage([]byte(arg))
}

return reflect.ValueOf(nil), nil, false
return mapParamsToArgs(rpcFunc, paramsMap, argsOffset)
}

// rpc.http
Expand Down
112 changes: 95 additions & 17 deletions tm2/pkg/bft/rpc/lib/server/parse_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package rpcserver

import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"testing"

Expand Down Expand Up @@ -175,44 +179,118 @@ func TestParseJSONRPC(t *testing.T) {
}
}

func TestParseURI(t *testing.T) {
func TestParseURINonJSON(t *testing.T) {
t.Parallel()

demo := func(ctx *types.Context, height int, name string) {}
call := NewRPCFunc(demo, "height,name")
// Define a demo RPC function
demo := func(ctx *types.Context, height int, name string, hash []byte) {}
call := NewRPCFunc(demo, "height,name,hash")

cases := []struct {
// Helper function to decode input base64 string to []byte
decodeBase64 := func(input string) []byte {
decoded, _ := base64.StdEncoding.DecodeString(input)
return decoded
}

// Helper function to decode input hex string to []byte
decodeHex := func(input string) []byte {
decoded, _ := hex.DecodeString(input[2:])
return decoded
}

// Test cases for non-JSON encoded parameters
nonJSONCases := []struct {
raw []string
height int64
name string
hash []byte
fail bool
}{
// can parse numbers unquoted and strings quoted
{[]string{"7", `"flew"`}, 7, "flew", false},
{[]string{"22", `"john"`}, 22, "john", false},
{[]string{"-10", `"bob"`}, -10, "bob", false},
{[]string{"7", `"flew"`, "rnpVPFlGJlauMNiL43Dmcl1U9loOBlib4L9OQAQ29tI="}, 7, "flew", decodeBase64("rnpVPFlGJlauMNiL43Dmcl1U9loOBlib4L9OQAQ29tI="), false},
{[]string{"22", `"john"`, "E+oc1Imd8g5W62tYntw6DjEI/5Lygsx9wJEwc4/oNWI="}, 22, "john", decodeBase64("E+oc1Imd8g5W62tYntw6DjEI/5Lygsx9wJEwc4/oNWI="), false},
{[]string{"-10", `"bob"`, "VAmm+RUvpjL3SAGG5gHNzo9ZWo2w3E15iyQB7DN0uF8="}, -10, "bob", decodeBase64("VAmm+RUvpjL3SAGG5gHNzo9ZWo2w3E15iyQB7DN0uF8="), false},
// can parse numbers quoted, too
{[]string{`"7"`, `"flew"`}, 7, "flew", false},
{[]string{`"-10"`, `"bob"`}, -10, "bob", false},
// cant parse strings uquoted
{[]string{`"-10"`, `bob`}, -10, "bob", true},
{[]string{`"7"`, `"flew"`, "0x486173682076616c7565"}, 7, "flew", decodeHex("0x486173682076616c7565"), false}, // Testing hex encoded data
{[]string{`"-10"`, `"bob"`, "0x6578616d706c65"}, -10, "bob", decodeHex("0x6578616d706c65"), false}, // Testing hex encoded data
// can't parse strings unquoted
{[]string{`"-10"`, `bob`, "invalid_encoded_data"}, -10, "bob", []byte("invalid_encoded_data"), true}, // Invalid encoded data format
}
for idx, tc := range cases {

// Iterate over test cases for non-JSON encoded parameters
for idx, tc := range nonJSONCases {
i := strconv.Itoa(idx)
// data := []byte(tc.raw)
url := fmt.Sprintf(
"test.com/method?height=%v&name=%v",
tc.raw[0], tc.raw[1])
url := fmt.Sprintf("test.com/method?height=%v&name=%v&hash=%v", tc.raw[0], tc.raw[1], url.QueryEscape(tc.raw[2]))
req, err := http.NewRequest("GET", url, nil)

assert.NoError(t, err)

// Invoke httpParamsToArgs to parse the request and convert to reflect.Values
vals, err := httpParamsToArgs(call, req)

// Check for expected errors or successful parsing
if tc.fail {
assert.NotNil(t, err, i)
} else {
assert.Nil(t, err, "%s: %+v", i, err)
if assert.Equal(t, 2, len(vals), i) {
// Assert the parsed values match the expected height, name, and data

if assert.Equal(t, 3, len(vals), i) {
assert.Equal(t, tc.height, vals[0].Int(), i)
assert.Equal(t, tc.name, vals[1].String(), i)
assert.Equal(t, len(tc.hash), len(vals[2].Bytes()), i)
assert.True(t, bytes.Equal(tc.hash, vals[2].Bytes()), i)
}
}
}
}

func TestParseURIJSON(t *testing.T) {
t.Parallel()

type Data struct {
Key string `json:"key"`
}

// Define a demo RPC function
demo := func(ctx *types.Context, data Data) {}
call := NewRPCFunc(demo, "data")

// Test cases for JSON encoded parameters
jsonCases := []struct {
raw string
data Data
fail bool
}{
// Valid JSON encoded values
{`{"key": "value"}`, Data{Key: "value"}, false},
{`{"id": 123}`, Data{}, false}, // Invalid field "id" (not in struct)
{`{"list": [1, 2, 3]}`, Data{}, false}, // Invalid field "list" (not in struct)
// Invalid JSON encoded values
{`"string_data"`, Data{}, true}, // Invalid JSON format (not an object)
{`12345`, Data{}, true}, // Invalid JSON format (not an object)
{`{"key": true}`, Data{}, true}, // Invalid field "key" type (expected string)
{`{"key": {"nested": "value"}}`, Data{}, true}, // Invalid field "key" type (nested object)
}

// Iterate over test cases for JSON encoded parameters
for idx, tc := range jsonCases {
i := strconv.Itoa(idx)
url := fmt.Sprintf("test.com/method?data=%v", url.PathEscape(tc.raw))
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)

// Invoke httpParamsToArgs to parse the request and convert to reflect.Values
vals, err := httpParamsToArgs(call, req)

// Check for expected errors or successful parsing
if tc.fail {
assert.NotNil(t, err, i)
} else {
assert.Nil(t, err, "%s: %+v", i, err)
// Assert the parsed values match the expected data
if assert.Equal(t, 1, len(vals), i) {
assert.Equal(t, tc.data, vals[0].Interface(), i)
}
}
}
Expand Down
Loading