From c420014cb5d19fd7a08c4232948c0bc8cf8f5583 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 22 Jan 2021 17:32:29 +0300 Subject: [PATCH] oracle: implement filters --- go.mod | 1 + go.sum | 5 +++ pkg/core/oracle_test.go | 25 +++++++++++++++ pkg/services/oracle/filter.go | 24 ++++++++++++++ pkg/services/oracle/filter_test.go | 50 ++++++++++++++++++++++++++++++ pkg/services/oracle/request.go | 8 +++++ 6 files changed, 113 insertions(+) create mode 100644 pkg/services/oracle/filter.go create mode 100644 pkg/services/oracle/filter_test.go diff --git a/go.mod b/go.mod index c598572c3..e67da8419 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/nspcc-dev/neo-go require ( + github.com/PaesslerAG/jsonpath v0.1.1 github.com/Workiva/go-datastructures v1.0.50 github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db github.com/alicebob/miniredis v2.5.0+incompatible diff --git a/go.sum b/go.sum index 4a700eba3..6194145fe 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,11 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8= +github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I= +github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= +github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk= +github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY= github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo= github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw= diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index 7ae711a13..45f55ca46 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -140,6 +140,10 @@ func TestOracle(t *testing.T) { putOracleRequest(t, cs.Hash, bc, "http://get.maxallowed", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cs.Hash, bc, "http://get.maxallowed", nil, "handle", []byte{}, 100_000_000) + flt := "Values[1]" + putOracleRequest(t, cs.Hash, bc, "http://get.filter", &flt, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cs.Hash, bc, "http://get.filterinv", &flt, "handle", []byte{}, 10_000_000) + checkResp := func(t *testing.T, id uint64, resp *transaction.OracleResponse) *state.OracleRequest { req, err := oracleCtr.GetRequestInternal(bc.dao, id) require.NoError(t, err) @@ -242,6 +246,19 @@ func TestOracle(t *testing.T) { Result: make([]byte, transaction.MaxOracleResultSize), }) }) + t.Run("WithFilter", func(t *testing.T) { + checkResp(t, 10, &transaction.OracleResponse{ + ID: 10, + Code: transaction.Success, + Result: []byte(`[2]`), + }) + t.Run("invalid response", func(t *testing.T) { + checkResp(t, 11, &transaction.OracleResponse{ + ID: 11, + Code: transaction.Error, + }) + }) + }) } func TestOracleFull(t *testing.T) { @@ -354,6 +371,14 @@ func newDefaultHTTPClient() oracle.HTTPClient { code: http.StatusOK, body: make([]byte, transaction.MaxOracleResultSize), }, + "http://get.filter": { + code: http.StatusOK, + body: []byte(`{"Values":["one", 2, 3],"Another":null}`), + }, + "http://get.filterinv": { + code: http.StatusOK, + body: []byte{0xFF}, + }, }, } } diff --git a/pkg/services/oracle/filter.go b/pkg/services/oracle/filter.go new file mode 100644 index 000000000..560320e0c --- /dev/null +++ b/pkg/services/oracle/filter.go @@ -0,0 +1,24 @@ +package oracle + +import ( + "encoding/json" + "errors" + "unicode/utf8" + + "github.com/PaesslerAG/jsonpath" +) + +func filter(value []byte, path string) ([]byte, error) { + if !utf8.Valid(value) { + return nil, errors.New("not an UTF-8") + } + var v interface{} + if err := json.Unmarshal(value, &v); err != nil { + return nil, err + } + result, err := jsonpath.Get(path, v) + if err != nil { + return nil, err + } + return json.Marshal([]interface{}{result}) +} diff --git a/pkg/services/oracle/filter_test.go b/pkg/services/oracle/filter_test.go new file mode 100644 index 000000000..d42573067 --- /dev/null +++ b/pkg/services/oracle/filter_test.go @@ -0,0 +1,50 @@ +package oracle + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilter(t *testing.T) { + js := `{ + "Stores": [ "Lambton Quay", "Willis Street" ], + "Manufacturers": [ + { + "Name": "Acme Co", + "Products": [ + { "Name": "Anvil", "Price": 50 } + ] + }, + { + "Name": "Contoso", + "Products": [ + { "Name": "Elbow Grease", "Price": 99.95 }, + { "Name": "Headlight Fluid", "Price": 4 } + ] + } + ] +}` + + testCases := []struct { + result, path string + }{ + {`["Acme Co"]`, "Manufacturers[0].Name"}, + {`[50]`, "Manufacturers[0].Products[0].Price"}, + {`["Elbow Grease"]`, "Manufacturers[1].Products[0].Name"}, + {`[{"Name":"Elbow Grease","Price":99.95}]`, "Manufacturers[1].Products[0]"}, + } + + for _, tc := range testCases { + t.Run(tc.path, func(t *testing.T) { + actual, err := filter([]byte(js), tc.path) + require.NoError(t, err) + require.Equal(t, tc.result, string(actual)) + }) + } + + t.Run("not an UTF-8", func(t *testing.T) { + _, err := filter([]byte{0xFF}, "Manufacturers[0].Name") + require.Error(t, err) + }) +} diff --git a/pkg/services/oracle/request.go b/pkg/services/oracle/request.go index 4210c35bc..af062bf68 100644 --- a/pkg/services/oracle/request.go +++ b/pkg/services/oracle/request.go @@ -115,6 +115,14 @@ func (o *Oracle) processRequest(priv *keys.PrivateKey, req request) error { } break } + if req.Req.Filter != nil { + res, err := filter(result, *req.Req.Filter) + if err != nil { + resp.Code = transaction.Error + break + } + result = res + } resp.Code = transaction.Success resp.Result = result case r.StatusCode == http.StatusForbidden: