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  }