diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index cfe5f0f11..09b22018f 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -453,7 +453,7 @@ func TestVerifyTx(t *testing.T) { }) t.Run("Oracle", func(t *testing.T) { orc := bc.contracts.Oracle - req := &native.OracleRequest{GasForResponse: 1000_0000} + req := &state.OracleRequest{GasForResponse: 1000_0000} require.NoError(t, orc.PutRequestInternal(1, req, bc.dao)) oracleScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(oraclePubs) diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 7ba340384..d85ad7963 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -138,7 +138,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error { continue } reqKey := makeRequestKey(resp.ID) - req := new(OracleRequest) + req := new(state.OracleRequest) if err := o.getSerializableFromDAO(ic.DAO, reqKey, req); err != nil { return err } @@ -299,7 +299,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url, filter, cb string, us return ErrBigArgument } - req := &OracleRequest{ + req := &state.OracleRequest{ OriginalTxID: o.getOriginalTxID(ic.DAO, ic.Tx), GasForResponse: gas.Uint64(), URL: url, @@ -312,7 +312,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url, filter, cb string, us } // PutRequestInternal puts oracle request with the specified id to d. -func (o *Oracle) PutRequestInternal(id uint64, req *OracleRequest, d dao.DAO) error { +func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d dao.DAO) error { reqItem := &state.StorageItem{Value: req.Bytes()} reqKey := makeRequestKey(id) if err := d.PutStorageItem(o.ContractID, reqKey, reqItem); err != nil { @@ -345,9 +345,9 @@ func (o *Oracle) GetOracleNodes(d dao.DAO) (keys.PublicKeys, error) { } // GetRequestInternal returns request by ID and key under which it is stored. -func (o *Oracle) GetRequestInternal(d dao.DAO, id uint64) (*OracleRequest, error) { +func (o *Oracle) GetRequestInternal(d dao.DAO, id uint64) (*state.OracleRequest, error) { key := makeRequestKey(id) - req := new(OracleRequest) + req := new(state.OracleRequest) return req, o.getSerializableFromDAO(d, key, req) } diff --git a/pkg/core/native/oracle_types.go b/pkg/core/native/oracle_types.go index af66068e2..fc6c90145 100644 --- a/pkg/core/native/oracle_types.go +++ b/pkg/core/native/oracle_types.go @@ -4,11 +4,9 @@ import ( "crypto/elliptic" "errors" "math/big" - "unicode/utf8" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -18,17 +16,6 @@ type IDList []uint64 // NodeList represents list or oracle nodes. type NodeList keys.PublicKeys -// OracleRequest represents oracle request. -type OracleRequest struct { - OriginalTxID util.Uint256 - GasForResponse uint64 - URL string - Filter *string - CallbackContract util.Uint160 - CallbackMethod string - UserData []byte -} - // Bytes return l serizalized to a byte-slice. func (l IDList) Bytes() []byte { w := io.NewBufBinWriter() @@ -136,104 +123,3 @@ func (l *NodeList) fromStackItem(it stackitem.Item) error { } return nil } - -// Bytes return o serizalized to a byte-slice. -func (o *OracleRequest) Bytes() []byte { - w := io.NewBufBinWriter() - o.EncodeBinary(w.BinWriter) - return w.Bytes() -} - -// EncodeBinary implements io.Serializable. -func (o *OracleRequest) EncodeBinary(w *io.BinWriter) { - stackitem.EncodeBinaryStackItem(o.toStackItem(), w) -} - -// DecodeBinary implements io.Serializable. -func (o *OracleRequest) DecodeBinary(r *io.BinReader) { - item := stackitem.DecodeBinaryStackItem(r) - if r.Err != nil || item == nil { - return - } - r.Err = o.fromStackItem(item) -} - -func (o *OracleRequest) toStackItem() stackitem.Item { - filter := stackitem.Item(stackitem.Null{}) - if o.Filter != nil { - filter = stackitem.Make(*o.Filter) - } - return stackitem.NewArray([]stackitem.Item{ - stackitem.NewByteArray(o.OriginalTxID.BytesBE()), - stackitem.NewBigInteger(new(big.Int).SetUint64(o.GasForResponse)), - stackitem.Make(o.URL), - filter, - stackitem.NewByteArray(o.CallbackContract.BytesBE()), - stackitem.Make(o.CallbackMethod), - stackitem.NewByteArray(o.UserData), - }) -} - -func (o *OracleRequest) fromStackItem(it stackitem.Item) error { - arr, ok := it.Value().([]stackitem.Item) - if !ok || len(arr) < 7 { - return errors.New("not an array of needed length") - } - bs, err := arr[0].TryBytes() - if err != nil { - return err - } - o.OriginalTxID, err = util.Uint256DecodeBytesBE(bs) - if err != nil { - return err - } - - gas, err := arr[1].TryInteger() - if err != nil { - return err - } - o.GasForResponse = gas.Uint64() - - s, isNull, ok := itemToString(arr[2]) - if !ok || isNull { - return errors.New("invalid URL") - } - o.URL = s - - s, isNull, ok = itemToString(arr[3]) - if !ok { - return errors.New("invalid filter") - } else if !isNull { - filter := s - o.Filter = &filter - } - - bs, err = arr[4].TryBytes() - if err != nil { - return err - } - o.CallbackContract, err = util.Uint160DecodeBytesBE(bs) - if err != nil { - return err - } - - o.CallbackMethod, isNull, ok = itemToString(arr[5]) - if !ok || isNull { - return errors.New("invalid callback method") - } - - o.UserData, err = arr[6].TryBytes() - return err -} - -func itemToString(it stackitem.Item) (string, bool, bool) { - _, ok := it.(stackitem.Null) - if ok { - return "", true, true - } - bs, err := it.TryBytes() - if err != nil || !utf8.Valid(bs) { - return "", false, false - } - return string(bs), false, true -} diff --git a/pkg/core/native/oracle_types_test.go b/pkg/core/native/oracle_types_test.go index 85cc7ee10..7882fbb73 100644 --- a/pkg/core/native/oracle_types_test.go +++ b/pkg/core/native/oracle_types_test.go @@ -1,11 +1,9 @@ package native import ( - "math/big" "testing" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -71,71 +69,3 @@ func TestNodeList_EncodeBinary(t *testing.T) { }) }) } - -func TestOracleRequest_EncodeBinary(t *testing.T) { - t.Run("Valid", func(t *testing.T) { - r := &OracleRequest{ - OriginalTxID: random.Uint256(), - GasForResponse: 12345, - URL: "https://get.value", - CallbackContract: random.Uint160(), - CallbackMethod: "method", - UserData: []byte{1, 2, 3}, - } - testserdes.EncodeDecodeBinary(t, r, new(OracleRequest)) - - t.Run("WithFilter", func(t *testing.T) { - s := "filter" - r.Filter = &s - testserdes.EncodeDecodeBinary(t, r, new(OracleRequest)) - }) - }) - t.Run("Invalid", func(t *testing.T) { - w := io.NewBufBinWriter() - t.Run("NotArray", func(t *testing.T) { - w.Reset() - it := stackitem.NewByteArray([]byte{}) - stackitem.EncodeBinaryStackItem(it, w.BinWriter) - require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(OracleRequest))) - }) - t.Run("NotStackItem", func(t *testing.T) { - w.Reset() - require.Error(t, testserdes.DecodeBinary([]byte{0x77}, new(OracleRequest))) - }) - - items := []stackitem.Item{ - stackitem.NewByteArray(random.Uint256().BytesBE()), - stackitem.NewBigInteger(big.NewInt(123)), - stackitem.Make("url"), - stackitem.Null{}, - stackitem.NewByteArray(random.Uint160().BytesBE()), - stackitem.Make("method"), - stackitem.NewByteArray([]byte{1, 2, 3}), - } - arrItem := stackitem.NewArray(items) - runInvalid := func(i int, elem stackitem.Item) func(t *testing.T) { - return func(t *testing.T) { - w.Reset() - before := items[i] - items[i] = elem - stackitem.EncodeBinaryStackItem(arrItem, w.BinWriter) - items[i] = before - require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(OracleRequest))) - } - } - t.Run("TxID", func(t *testing.T) { - t.Run("Type", runInvalid(0, stackitem.NewMap())) - t.Run("Length", runInvalid(0, stackitem.NewByteArray([]byte{0, 1, 2}))) - }) - t.Run("Gas", runInvalid(1, stackitem.NewMap())) - t.Run("URL", runInvalid(2, stackitem.NewMap())) - t.Run("Filter", runInvalid(3, stackitem.NewMap())) - t.Run("Contract", func(t *testing.T) { - t.Run("Type", runInvalid(4, stackitem.NewMap())) - t.Run("Length", runInvalid(4, stackitem.NewByteArray([]byte{0, 1, 2}))) - }) - t.Run("Method", runInvalid(5, stackitem.NewMap())) - t.Run("UserData", runInvalid(6, stackitem.NewMap())) - }) - -} diff --git a/pkg/core/state/oracle.go b/pkg/core/state/oracle.go new file mode 100644 index 000000000..ac8fa114d --- /dev/null +++ b/pkg/core/state/oracle.go @@ -0,0 +1,123 @@ +package state + +import ( + "errors" + "math/big" + "unicode/utf8" + + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// OracleRequest represents oracle request. +type OracleRequest struct { + OriginalTxID util.Uint256 + GasForResponse uint64 + URL string + Filter *string + CallbackContract util.Uint160 + CallbackMethod string + UserData []byte +} + +// Bytes return o serizalized to a byte-slice. +func (o *OracleRequest) Bytes() []byte { + w := io.NewBufBinWriter() + o.EncodeBinary(w.BinWriter) + return w.Bytes() +} + +// EncodeBinary implements io.Serializable. +func (o *OracleRequest) EncodeBinary(w *io.BinWriter) { + stackitem.EncodeBinaryStackItem(o.toStackItem(), w) +} + +// DecodeBinary implements io.Serializable. +func (o *OracleRequest) DecodeBinary(r *io.BinReader) { + item := stackitem.DecodeBinaryStackItem(r) + if r.Err != nil || item == nil { + return + } + r.Err = o.fromStackItem(item) +} + +func (o *OracleRequest) toStackItem() stackitem.Item { + filter := stackitem.Item(stackitem.Null{}) + if o.Filter != nil { + filter = stackitem.Make(*o.Filter) + } + return stackitem.NewArray([]stackitem.Item{ + stackitem.NewByteArray(o.OriginalTxID.BytesBE()), + stackitem.NewBigInteger(new(big.Int).SetUint64(o.GasForResponse)), + stackitem.Make(o.URL), + filter, + stackitem.NewByteArray(o.CallbackContract.BytesBE()), + stackitem.Make(o.CallbackMethod), + stackitem.NewByteArray(o.UserData), + }) +} + +func (o *OracleRequest) fromStackItem(it stackitem.Item) error { + arr, ok := it.Value().([]stackitem.Item) + if !ok || len(arr) < 7 { + return errors.New("not an array of needed length") + } + bs, err := arr[0].TryBytes() + if err != nil { + return err + } + o.OriginalTxID, err = util.Uint256DecodeBytesBE(bs) + if err != nil { + return err + } + + gas, err := arr[1].TryInteger() + if err != nil { + return err + } + o.GasForResponse = gas.Uint64() + + s, isNull, ok := itemToString(arr[2]) + if !ok || isNull { + return errors.New("invalid URL") + } + o.URL = s + + s, isNull, ok = itemToString(arr[3]) + if !ok { + return errors.New("invalid filter") + } else if !isNull { + filter := s + o.Filter = &filter + } + + bs, err = arr[4].TryBytes() + if err != nil { + return err + } + o.CallbackContract, err = util.Uint160DecodeBytesBE(bs) + if err != nil { + return err + } + + o.CallbackMethod, isNull, ok = itemToString(arr[5]) + if !ok || isNull { + return errors.New("invalid callback method") + } + + o.UserData, err = arr[6].TryBytes() + return err +} + +func itemToString(it stackitem.Item) (string, bool, bool) { + _, ok := it.(stackitem.Null) + if ok { + return "", true, true + } + bs, err := it.TryBytes() + if err != nil || !utf8.Valid(bs) { + return "", false, false + } + return string(bs), false, true +} diff --git a/pkg/core/state/oracle_test.go b/pkg/core/state/oracle_test.go new file mode 100644 index 000000000..d57f98b73 --- /dev/null +++ b/pkg/core/state/oracle_test.go @@ -0,0 +1,80 @@ +package state + +import ( + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/internal/random" + "github.com/nspcc-dev/neo-go/pkg/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestOracleRequest_EncodeBinary(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + r := &OracleRequest{ + OriginalTxID: random.Uint256(), + GasForResponse: 12345, + URL: "https://get.value", + CallbackContract: random.Uint160(), + CallbackMethod: "method", + UserData: []byte{1, 2, 3}, + } + testserdes.EncodeDecodeBinary(t, r, new(OracleRequest)) + + t.Run("WithFilter", func(t *testing.T) { + s := "filter" + r.Filter = &s + testserdes.EncodeDecodeBinary(t, r, new(OracleRequest)) + }) + }) + t.Run("Invalid", func(t *testing.T) { + w := io.NewBufBinWriter() + t.Run("NotArray", func(t *testing.T) { + w.Reset() + it := stackitem.NewByteArray([]byte{}) + stackitem.EncodeBinaryStackItem(it, w.BinWriter) + require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(OracleRequest))) + }) + t.Run("NotStackItem", func(t *testing.T) { + w.Reset() + require.Error(t, testserdes.DecodeBinary([]byte{0x77}, new(OracleRequest))) + }) + + items := []stackitem.Item{ + stackitem.NewByteArray(random.Uint256().BytesBE()), + stackitem.NewBigInteger(big.NewInt(123)), + stackitem.Make("url"), + stackitem.Null{}, + stackitem.NewByteArray(random.Uint160().BytesBE()), + stackitem.Make("method"), + stackitem.NewByteArray([]byte{1, 2, 3}), + } + arrItem := stackitem.NewArray(items) + runInvalid := func(i int, elem stackitem.Item) func(t *testing.T) { + return func(t *testing.T) { + w.Reset() + before := items[i] + items[i] = elem + stackitem.EncodeBinaryStackItem(arrItem, w.BinWriter) + items[i] = before + require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(OracleRequest))) + } + } + t.Run("TxID", func(t *testing.T) { + t.Run("Type", runInvalid(0, stackitem.NewMap())) + t.Run("Length", runInvalid(0, stackitem.NewByteArray([]byte{0, 1, 2}))) + }) + t.Run("Gas", runInvalid(1, stackitem.NewMap())) + t.Run("URL", runInvalid(2, stackitem.NewMap())) + t.Run("Filter", runInvalid(3, stackitem.NewMap())) + t.Run("Contract", func(t *testing.T) { + t.Run("Type", runInvalid(4, stackitem.NewMap())) + t.Run("Length", runInvalid(4, stackitem.NewByteArray([]byte{0, 1, 2}))) + }) + t.Run("Method", runInvalid(5, stackitem.NewMap())) + t.Run("UserData", runInvalid(6, stackitem.NewMap())) + }) + +}