github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/oracle/oracle_test.go (about) 1 package oracle_test 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/json" 7 "errors" 8 "fmt" 9 gio "io" 10 "net/http" 11 "path/filepath" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/nspcc-dev/neo-go/internal/contracts" 18 "github.com/nspcc-dev/neo-go/pkg/config" 19 "github.com/nspcc-dev/neo-go/pkg/config/netmode" 20 "github.com/nspcc-dev/neo-go/pkg/core" 21 "github.com/nspcc-dev/neo-go/pkg/core/native" 22 "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" 23 "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" 24 "github.com/nspcc-dev/neo-go/pkg/core/state" 25 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 26 "github.com/nspcc-dev/neo-go/pkg/crypto/keys" 27 "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" 28 "github.com/nspcc-dev/neo-go/pkg/neotest" 29 "github.com/nspcc-dev/neo-go/pkg/neotest/chain" 30 "github.com/nspcc-dev/neo-go/pkg/services/oracle" 31 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 32 "github.com/nspcc-dev/neo-go/pkg/util" 33 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 34 "github.com/nspcc-dev/neo-go/pkg/wallet" 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/require" 37 "go.uber.org/zap/zaptest" 38 ) 39 40 var pathToInternalContracts = filepath.Join("..", "..", "..", "internal", "contracts") 41 42 func putOracleRequest(t *testing.T, oracleValidatorInvoker *neotest.ContractInvoker, 43 url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 { 44 var filtItem any 45 if filter != nil { 46 filtItem = *filter 47 } 48 return oracleValidatorInvoker.Invoke(t, stackitem.Null{}, "requestURL", url, filtItem, cb, userData, gas) 49 } 50 51 func getOracleConfig(t *testing.T, bc *core.Blockchain, w, pass string, returnOracleRedirectionErrOn func(address string) bool) oracle.Config { 52 return oracle.Config{ 53 Log: zaptest.NewLogger(t), 54 Network: netmode.UnitTestNet, 55 MainCfg: config.OracleConfiguration{ 56 RefreshInterval: time.Second, 57 AllowedContentTypes: []string{"application/json"}, 58 UnlockWallet: config.Wallet{ 59 Path: w, 60 Password: pass, 61 }, 62 }, 63 Chain: bc, 64 Client: newDefaultHTTPClient(returnOracleRedirectionErrOn), 65 } 66 } 67 68 func getTestOracle(t *testing.T, bc *core.Blockchain, walletPath, pass string) ( 69 *wallet.Account, 70 *oracle.Oracle, 71 map[uint64]*responseWithSig, 72 chan *transaction.Transaction) { 73 m := make(map[uint64]*responseWithSig) 74 ch := make(chan *transaction.Transaction, 5) 75 orcCfg := getOracleConfig(t, bc, walletPath, pass, func(address string) bool { 76 return strings.HasPrefix(address, "https://private") 77 }) 78 orcCfg.ResponseHandler = &saveToMapBroadcaster{m: m} 79 orcCfg.OnTransaction = saveTxToChan(ch) 80 orc, err := oracle.NewOracle(orcCfg) 81 require.NoError(t, err) 82 83 w, err := wallet.NewWalletFromFile(walletPath) 84 require.NoError(t, err) 85 require.NoError(t, w.Accounts[0].Decrypt(pass, w.Scrypt)) 86 return w.Accounts[0], orc, m, ch 87 } 88 89 // Compatibility test from C# code. 90 // https://github.com/neo-project/neo-modules/blob/master/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs#L61 91 func TestCreateResponseTx(t *testing.T) { 92 bc, validator, committee := chain.NewMulti(t) 93 e := neotest.NewExecutor(t, bc, validator, committee) 94 managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) 95 96 cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) 97 rawManifest, err := json.Marshal(cs.Manifest) 98 require.NoError(t, err) 99 rawNef, err := cs.NEF.Bytes() 100 require.NoError(t, err) 101 tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) 102 e.AddNewBlock(t, tx) 103 e.CheckHalt(t, tx.Hash()) 104 cInvoker := e.ValidatorInvoker(cs.Hash) 105 106 require.Equal(t, int64(30), bc.GetBaseExecFee()) 107 require.Equal(t, int64(1000), bc.FeePerByte()) 108 acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle1.json", "one") 109 req := &state.OracleRequest{ 110 OriginalTxID: util.Uint256{}, 111 GasForResponse: 100000000, 112 URL: "https://127.0.0.1/test", 113 Filter: new(string), 114 CallbackContract: util.Uint160{}, 115 CallbackMethod: "callback", 116 UserData: []byte{}, 117 } 118 resp := &transaction.OracleResponse{ 119 ID: 1, 120 Code: transaction.Success, 121 Result: []byte{0}, 122 } 123 cInvoker.Invoke(t, stackitem.Null{}, "requestURL", req.URL, *req.Filter, req.CallbackMethod, req.UserData, int64(req.GasForResponse)) 124 bc.SetOracle(orc) 125 orc.UpdateOracleNodes(keys.PublicKeys{acc.PublicKey()}) 126 tx, err = orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) 127 require.NoError(t, err) 128 assert.Equal(t, 166, tx.Size()) 129 assert.Equal(t, int64(2198650), tx.NetworkFee) 130 assert.Equal(t, int64(97801350), tx.SystemFee) 131 } 132 133 func TestOracle_InvalidWallet(t *testing.T) { 134 bc, _, _ := chain.NewMulti(t) 135 136 _, err := oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "invalid", nil)) 137 require.Error(t, err) 138 139 _, err = oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "one", nil)) 140 require.NoError(t, err) 141 } 142 143 func TestOracle(t *testing.T) { 144 bc, validator, committee := chain.NewMulti(t) 145 e := neotest.NewExecutor(t, bc, validator, committee) 146 managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) 147 designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) 148 nativeOracleH := e.NativeHash(t, nativenames.Oracle) 149 nativeOracleID := e.NativeID(t, nativenames.Oracle) 150 151 acc1, orc1, m1, ch1 := getTestOracle(t, bc, "./testdata/oracle1.json", "one") 152 acc2, orc2, m2, ch2 := getTestOracle(t, bc, "./testdata/oracle2.json", "two") 153 oracleNodes := keys.PublicKeys{acc1.PublicKey(), acc2.PrivateKey().PublicKey()} 154 // Must be set in native contract for tx verification. 155 designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", 156 int64(roles.Oracle), []any{oracleNodes[0].Bytes(), oracleNodes[1].Bytes()}) 157 orc1.UpdateOracleNodes(oracleNodes.Copy()) 158 orc2.UpdateOracleNodes(oracleNodes.Copy()) 159 160 nativeOracleState := bc.GetContractState(nativeOracleH) 161 require.NotNil(t, nativeOracleState) 162 md := nativeOracleState.Manifest.ABI.GetMethod(manifest.MethodVerify, -1) 163 require.NotNil(t, md) 164 oracleRespScript := native.CreateOracleResponseScript(nativeOracleH) 165 orc1.UpdateNativeContract(nativeOracleState.NEF.Script, bytes.Clone(oracleRespScript), nativeOracleH, md.Offset) 166 orc2.UpdateNativeContract(nativeOracleState.NEF.Script, bytes.Clone(oracleRespScript), nativeOracleH, md.Offset) 167 168 cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) 169 rawManifest, err := json.Marshal(cs.Manifest) 170 require.NoError(t, err) 171 rawNef, err := cs.NEF.Bytes() 172 require.NoError(t, err) 173 tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) 174 e.AddNewBlock(t, tx) 175 e.CheckHalt(t, tx.Hash()) 176 cInvoker := e.ValidatorInvoker(cs.Hash) 177 178 putOracleRequest(t, cInvoker, "https://get.1234", nil, "handle", []byte{}, 10_000_000) 179 putOracleRequest(t, cInvoker, "https://get.1234", nil, "handle", []byte{}, 10_000_000) 180 putOracleRequest(t, cInvoker, "https://get.timeout", nil, "handle", []byte{}, 10_000_000) 181 putOracleRequest(t, cInvoker, "https://get.notfound", nil, "handle", []byte{}, 10_000_000) 182 putOracleRequest(t, cInvoker, "https://get.forbidden", nil, "handle", []byte{}, 10_000_000) 183 putOracleRequest(t, cInvoker, "https://private.url", nil, "handle", []byte{}, 10_000_000) 184 putOracleRequest(t, cInvoker, "https://get.big", nil, "handle", []byte{}, 10_000_000) 185 putOracleRequest(t, cInvoker, "https://get.maxallowed", nil, "handle", []byte{}, 10_000_000) 186 putOracleRequest(t, cInvoker, "https://get.maxallowed", nil, "handle", []byte{}, 100_000_000) 187 188 flt := "$.Values[1]" 189 putOracleRequest(t, cInvoker, "https://get.filter", &flt, "handle", []byte{}, 10_000_000) 190 putOracleRequest(t, cInvoker, "https://get.filterinv", &flt, "handle", []byte{}, 10_000_000) 191 192 putOracleRequest(t, cInvoker, "https://get.invalidcontent", nil, "handle", []byte{}, 10_000_000) 193 194 checkResp := func(t *testing.T, id uint64, resp *transaction.OracleResponse) *state.OracleRequest { 195 // Use a hack to get request from Oracle contract, because we can't use GetRequestInternal directly. 196 requestKey := make([]byte, 9) 197 requestKey[0] = 7 // prefixRequest from native Oracle contract 198 binary.BigEndian.PutUint64(requestKey[1:], id) 199 si := bc.GetStorageItem(nativeOracleID, requestKey) 200 require.NotNil(t, si) 201 req := new(state.OracleRequest) 202 require.NoError(t, stackitem.DeserializeConvertible(si, req)) 203 204 reqs := map[uint64]*state.OracleRequest{id: req} 205 orc1.ProcessRequestsInternal(reqs) 206 require.NotNil(t, m1[id]) 207 require.Equal(t, resp, m1[id].resp) 208 require.Empty(t, ch1) 209 return req 210 } 211 212 // Checks if tx is ready and valid. 213 checkEmitTx := func(t *testing.T, ch chan *transaction.Transaction) { 214 require.Len(t, ch, 1) 215 tx := <-ch 216 217 // Response transaction has its hash being precalculated. Check that this hash 218 // matches the actual one. 219 cachedHash := tx.Hash() 220 cp := transaction.Transaction{ 221 Version: tx.Version, 222 Nonce: tx.Nonce, 223 SystemFee: tx.SystemFee, 224 NetworkFee: tx.NetworkFee, 225 ValidUntilBlock: tx.ValidUntilBlock, 226 Script: tx.Script, 227 Attributes: tx.Attributes, 228 Signers: tx.Signers, 229 Scripts: tx.Scripts, 230 Trimmed: tx.Trimmed, 231 } 232 actualHash := cp.Hash() 233 require.Equal(t, actualHash, cachedHash, "transaction hash was changed during ") 234 235 require.NoError(t, bc.PoolTx(tx)) 236 } 237 238 t.Run("NormalRequest", func(t *testing.T) { 239 resp := &transaction.OracleResponse{ 240 ID: 0, 241 Code: transaction.Success, 242 Result: []byte{1, 2, 3, 4}, 243 } 244 req := checkResp(t, 0, resp) 245 246 reqs := map[uint64]*state.OracleRequest{0: req} 247 orc2.ProcessRequestsInternal(reqs) 248 require.Equal(t, resp, m2[0].resp) 249 require.Empty(t, ch2) 250 251 t.Run("InvalidSignature", func(t *testing.T) { 252 orc1.AddResponse(acc2.PublicKey(), m2[0].resp.ID, []byte{1, 2, 3}) 253 require.Empty(t, ch1) 254 }) 255 orc1.AddResponse(acc2.PublicKey(), m2[0].resp.ID, m2[0].txSig) 256 checkEmitTx(t, ch1) 257 258 t.Run("FirstOtherThenMe", func(t *testing.T) { 259 const reqID = 1 260 261 resp := &transaction.OracleResponse{ 262 ID: reqID, 263 Code: transaction.Success, 264 Result: []byte{1, 2, 3, 4}, 265 } 266 req := checkResp(t, reqID, resp) 267 orc2.AddResponse(acc1.PublicKey(), reqID, m1[reqID].txSig) 268 require.Empty(t, ch2) 269 270 reqs := map[uint64]*state.OracleRequest{reqID: req} 271 orc2.ProcessRequestsInternal(reqs) 272 require.Equal(t, resp, m2[reqID].resp) 273 checkEmitTx(t, ch2) 274 }) 275 }) 276 t.Run("Invalid", func(t *testing.T) { 277 t.Run("Timeout", func(t *testing.T) { 278 checkResp(t, 2, &transaction.OracleResponse{ 279 ID: 2, 280 Code: transaction.Timeout, 281 }) 282 }) 283 t.Run("NotFound", func(t *testing.T) { 284 checkResp(t, 3, &transaction.OracleResponse{ 285 ID: 3, 286 Code: transaction.NotFound, 287 }) 288 }) 289 t.Run("Forbidden", func(t *testing.T) { 290 checkResp(t, 4, &transaction.OracleResponse{ 291 ID: 4, 292 Code: transaction.Forbidden, 293 }) 294 }) 295 t.Run("PrivateNetwork", func(t *testing.T) { 296 checkResp(t, 5, &transaction.OracleResponse{ 297 ID: 5, 298 Code: transaction.Forbidden, 299 }) 300 }) 301 t.Run("Big", func(t *testing.T) { 302 checkResp(t, 6, &transaction.OracleResponse{ 303 ID: 6, 304 Code: transaction.ResponseTooLarge, 305 }) 306 }) 307 t.Run("MaxAllowedSmallGAS", func(t *testing.T) { 308 checkResp(t, 7, &transaction.OracleResponse{ 309 ID: 7, 310 Code: transaction.InsufficientFunds, 311 }) 312 }) 313 }) 314 t.Run("MaxAllowedEnoughGAS", func(t *testing.T) { 315 checkResp(t, 8, &transaction.OracleResponse{ 316 ID: 8, 317 Code: transaction.Success, 318 Result: make([]byte, transaction.MaxOracleResultSize), 319 }) 320 }) 321 t.Run("WithFilter", func(t *testing.T) { 322 checkResp(t, 9, &transaction.OracleResponse{ 323 ID: 9, 324 Code: transaction.Success, 325 Result: []byte(`[2]`), 326 }) 327 t.Run("invalid response", func(t *testing.T) { 328 checkResp(t, 10, &transaction.OracleResponse{ 329 ID: 10, 330 Code: transaction.Error, 331 }) 332 }) 333 }) 334 t.Run("InvalidContentType", func(t *testing.T) { 335 checkResp(t, 11, &transaction.OracleResponse{ 336 ID: 11, 337 Code: transaction.ContentTypeNotSupported, 338 }) 339 }) 340 } 341 342 func TestOracle_GenesisRole(t *testing.T) { 343 const ( 344 oraclePath = "./testdata/oracle1.json" 345 oraclePass = "one" 346 ) 347 w, err := wallet.NewWalletFromFile(oraclePath) 348 require.NoError(t, err) 349 require.NoError(t, w.Accounts[0].Decrypt(oraclePass, w.Scrypt)) 350 acc := w.Accounts[0] 351 352 bc, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) { 353 c.Genesis.Roles = map[noderoles.Role]keys.PublicKeys{ 354 noderoles.Oracle: {acc.PublicKey()}, 355 } 356 }) 357 358 orc, err := oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "one", nil)) 359 require.NoError(t, err) 360 require.False(t, orc.IsAuthorized()) 361 362 bc.SetOracle(orc) 363 require.True(t, orc.IsAuthorized()) 364 } 365 366 func TestOracleFull(t *testing.T) { 367 bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false) 368 e := neotest.NewExecutor(t, bc, validator, committee) 369 designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) 370 371 acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two") 372 mp := bc.GetMemPool() 373 orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } 374 bc.SetOracle(orc) 375 376 go bc.Run() 377 orc.Start() 378 t.Cleanup(func() { 379 orc.Shutdown() 380 bc.Close() 381 }) 382 383 designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", 384 int64(roles.Oracle), []any{acc.PublicKey().Bytes()}) 385 386 cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) 387 e.DeployContract(t, &neotest.Contract{ 388 Hash: cs.Hash, 389 NEF: &cs.NEF, 390 Manifest: &cs.Manifest, 391 }, nil) 392 cInvoker := e.ValidatorInvoker(cs.Hash) 393 394 putOracleRequest(t, cInvoker, "https://get.1234", new(string), "handle", []byte{}, 10_000_000) 395 396 require.Eventually(t, func() bool { return mp.Count() == 1 }, 397 time.Second*3, time.Millisecond*200) 398 399 txes := mp.GetVerifiedTransactions() 400 require.Len(t, txes, 1) 401 require.True(t, txes[0].HasAttribute(transaction.OracleResponseT)) 402 } 403 404 func TestNotYetRunningOracle(t *testing.T) { 405 bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false) 406 e := neotest.NewExecutor(t, bc, validator, committee) 407 designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) 408 409 acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two") 410 mp := bc.GetMemPool() 411 orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } 412 bc.SetOracle(orc) 413 414 go bc.Run() 415 t.Cleanup(bc.Close) 416 417 designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", 418 int64(roles.Oracle), []any{acc.PublicKey().Bytes()}) 419 420 var req state.OracleRequest 421 var reqs = make(map[uint64]*state.OracleRequest) 422 for i := uint64(0); i < 3; i++ { 423 reqs[i] = &req 424 } 425 orc.AddRequests(reqs) // 0, 1, 2 added to pending. 426 427 var ids = []uint64{0, 1} 428 orc.RemoveRequests(ids) // 0, 1 removed from pending, 2 left. 429 430 reqs = make(map[uint64]*state.OracleRequest) 431 for i := uint64(3); i < 5; i++ { 432 reqs[i] = &req 433 } 434 orc.AddRequests(reqs) // 3, 4 added to pending -> 2, 3, 4 in pending. 435 436 ids = []uint64{3} 437 orc.RemoveRequests(ids) // 3 removed from pending -> 2, 4 in pending. 438 439 orc.Start() 440 t.Cleanup(orc.Shutdown) 441 442 require.Eventually(t, func() bool { return mp.Count() == 2 }, 443 time.Second*3, time.Millisecond*200) 444 txes := mp.GetVerifiedTransactions() 445 require.Len(t, txes, 2) 446 var txids []uint64 447 for _, tx := range txes { 448 for _, attr := range tx.Attributes { 449 if attr.Type == transaction.OracleResponseT { 450 resp := attr.Value.(*transaction.OracleResponse) 451 txids = append(txids, resp.ID) 452 } 453 } 454 } 455 require.Len(t, txids, 2) 456 require.Contains(t, txids, uint64(2)) 457 require.Contains(t, txids, uint64(4)) 458 } 459 460 type saveToMapBroadcaster struct { 461 mtx sync.RWMutex 462 m map[uint64]*responseWithSig 463 } 464 465 func (b *saveToMapBroadcaster) SendResponse(_ *keys.PrivateKey, resp *transaction.OracleResponse, txSig []byte) { 466 b.mtx.Lock() 467 defer b.mtx.Unlock() 468 b.m[resp.ID] = &responseWithSig{ 469 resp: resp, 470 txSig: txSig, 471 } 472 } 473 func (*saveToMapBroadcaster) Run() {} 474 func (*saveToMapBroadcaster) Shutdown() {} 475 476 type responseWithSig struct { 477 resp *transaction.OracleResponse 478 txSig []byte 479 } 480 481 func saveTxToChan(ch chan *transaction.Transaction) oracle.TxCallback { 482 return func(tx *transaction.Transaction) error { 483 ch <- tx 484 return nil 485 } 486 } 487 488 type ( 489 // httpClient implements oracle.HTTPClient with 490 // mocked URL or responses. 491 httpClient struct { 492 returnOracleRedirectionErrOn func(address string) bool 493 responses map[string]testResponse 494 } 495 496 testResponse struct { 497 code int 498 ct string 499 body []byte 500 } 501 ) 502 503 // Get implements the oracle.HTTPClient interface. 504 func (c *httpClient) Do(req *http.Request) (*http.Response, error) { 505 if c.returnOracleRedirectionErrOn != nil && c.returnOracleRedirectionErrOn(req.URL.String()) { 506 return nil, fmt.Errorf("%w: private network", oracle.ErrRestrictedRedirect) 507 } 508 resp, ok := c.responses[req.URL.String()] 509 if ok { 510 return &http.Response{ 511 StatusCode: resp.code, 512 Header: http.Header{ 513 "Content-Type": {resp.ct}, 514 }, 515 Body: newResponseBody(resp.body), 516 }, nil 517 } 518 return nil, errors.New("request failed") 519 } 520 521 func newDefaultHTTPClient(returnOracleRedirectionErrOn func(address string) bool) oracle.HTTPClient { 522 return &httpClient{ 523 returnOracleRedirectionErrOn: returnOracleRedirectionErrOn, 524 responses: map[string]testResponse{ 525 "https://get.1234": { 526 code: http.StatusOK, 527 ct: "application/json", 528 body: []byte{1, 2, 3, 4}, 529 }, 530 "https://get.4321": { 531 code: http.StatusOK, 532 ct: "application/json", 533 body: []byte{4, 3, 2, 1}, 534 }, 535 "https://get.timeout": { 536 code: http.StatusRequestTimeout, 537 ct: "application/json", 538 body: []byte{}, 539 }, 540 "https://get.notfound": { 541 code: http.StatusNotFound, 542 ct: "application/json", 543 body: []byte{}, 544 }, 545 "https://get.forbidden": { 546 code: http.StatusForbidden, 547 ct: "application/json", 548 body: []byte{}, 549 }, 550 "https://private.url": { 551 code: http.StatusOK, 552 ct: "application/json", 553 body: []byte("passwords"), 554 }, 555 "https://get.big": { 556 code: http.StatusOK, 557 ct: "application/json", 558 body: make([]byte, transaction.MaxOracleResultSize+1), 559 }, 560 "https://get.maxallowed": { 561 code: http.StatusOK, 562 ct: "application/json", 563 body: make([]byte, transaction.MaxOracleResultSize), 564 }, 565 "https://get.filter": { 566 code: http.StatusOK, 567 ct: "application/json", 568 body: []byte(`{"Values":["one", 2, 3],"Another":null}`), 569 }, 570 "https://get.filterinv": { 571 code: http.StatusOK, 572 ct: "application/json", 573 body: []byte{0xFF}, 574 }, 575 "https://get.invalidcontent": { 576 code: http.StatusOK, 577 ct: "image/gif", 578 body: []byte{1, 2, 3}, 579 }, 580 }, 581 } 582 } 583 584 func newResponseBody(resp []byte) gio.ReadCloser { 585 return gio.NopCloser(bytes.NewReader(resp)) 586 }