neoneo-go/pkg/core/native/native_test/oracle_test.go
Roman Khimov 8d33206bb8 *: don't get private key from account if just public one is needed
Add PublicKey() API to the Account and use it as appropriate, avoid creating
additional references to the private key.
2022-09-02 14:43:28 +03:00

179 lines
6.9 KiB
Go

package native_test
import (
"encoding/json"
"math"
"math/big"
"path/filepath"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts")
func newOracleClient(t *testing.T) *neotest.ContractInvoker {
return newNativeClient(t, nativenames.Oracle)
}
func TestOracle_GetSetPrice(t *testing.T) {
testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64)
}
func TestOracle_GetSetPriceCache(t *testing.T) {
testGetSetCache(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice)
}
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
var filtItem interface{}
if filter != nil {
filtItem = *filter
}
if len(errStr) == 0 {
oracleInvoker.Invoke(t, stackitem.Null{}, "requestURL", url, filtItem, cb, userData, gas)
return
}
oracleInvoker.InvokeFail(t, errStr[0], "requestURL", url, filtItem, cb, userData, gas)
}
func TestOracle_Request(t *testing.T) {
oracleCommitteeInvoker := newOracleClient(t)
e := oracleCommitteeInvoker.Executor
managementCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management))
designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
cs := contracts.GetOracleContractState(t, pathToInternalContracts, e.Validator.ScriptHash(), 1)
nBytes, err := cs.NEF.Bytes()
require.NoError(t, err)
mBytes, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
expected, err := cs.ToStackItem()
require.NoError(t, err)
managementCommitteeInvoker.Invoke(t, expected, "deploy", nBytes, mBytes)
helperValidatorInvoker := e.ValidatorInvoker(cs.Hash)
gasForResponse := int64(2000_1234)
var filter = "flt"
userData := []byte("custom info")
putOracleRequest(t, helperValidatorInvoker, "url", &filter, "handle", userData, gasForResponse)
// Designate single Oracle node.
oracleNode := e.NewAccount(t)
designationCommitteeInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int(noderoles.Oracle), []interface{}{oracleNode.(neotest.SingleSigner).Account().PublicKey().Bytes()})
err = oracleNode.(neotest.SingleSigner).Account().ConvertMultisig(1, []*keys.PublicKey{oracleNode.(neotest.SingleSigner).Account().PublicKey()})
require.NoError(t, err)
oracleNodeMulti := neotest.NewMultiSigner(oracleNode.(neotest.SingleSigner).Account())
gasCommitteeInvoker.Invoke(t, true, "transfer", gasCommitteeInvoker.CommitteeHash, oracleNodeMulti.ScriptHash(), 100_0000_0000, nil)
// Finish.
prepareResponseTx := func(t *testing.T, requestID uint64) *transaction.Transaction {
script := native.CreateOracleResponseScript(oracleCommitteeInvoker.Hash)
tx := transaction.New(script, 1000_0000)
tx.Nonce = neotest.Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
tx.Attributes = []transaction.Attribute{{
Type: transaction.OracleResponseT,
Value: &transaction.OracleResponse{
ID: requestID,
Code: transaction.Success,
Result: []byte{4, 8, 15, 16, 23, 42},
},
}}
tx.Signers = []transaction.Signer{
{
Account: oracleNodeMulti.ScriptHash(),
Scopes: transaction.None,
},
{
Account: oracleCommitteeInvoker.Hash,
Scopes: transaction.None,
},
}
tx.NetworkFee = 1000_1234
tx.Scripts = []transaction.Witness{
{
InvocationScript: oracleNodeMulti.SignHashable(uint32(e.Chain.GetConfig().Magic), tx),
VerificationScript: oracleNodeMulti.Script(),
},
{
InvocationScript: []byte{},
VerificationScript: []byte{},
},
}
return tx
}
tx := prepareResponseTx(t, 0)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash(), stackitem.Null{})
// Ensure that callback was called.
si := e.Chain.GetStorageItem(cs.ID, []byte("lastOracleResponse"))
require.NotNil(t, si)
actual, err := stackitem.Deserialize(si)
require.NoError(t, err)
require.Equal(t, stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("url")),
stackitem.NewByteArray(userData),
stackitem.NewBigInteger(big.NewInt(int64(tx.Attributes[0].Value.(*transaction.OracleResponse).Code))),
stackitem.NewByteArray(tx.Attributes[0].Value.(*transaction.OracleResponse).Result),
}), actual)
// Check that the processed request is removed. We can't access GetRequestInternal directly,
// but adding a response to this request should fail due to invalid request error.
tx = prepareResponseTx(t, 0)
err = e.Chain.VerifyTx(tx)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
t.Run("ErrorOnFinish", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "handle", []byte{1, 2}, gasForResponse)
tx := prepareResponseTx(t, 1)
e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), "ABORT")
// Check that the processed request is cleaned up even if callback failed. We can't
// access GetRequestInternal directly, but adding a response to this request
// should fail due to invalid request error.
tx = prepareResponseTx(t, 1)
err = e.Chain.VerifyTx(tx)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
})
t.Run("Reentrant", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "handleRecursive", []byte{}, gasForResponse)
tx := prepareResponseTx(t, 2)
e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), "Oracle.finish called from non-entry script")
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 2, len(aer[0].Events)) // OracleResponse + Invocation
})
t.Run("BadRequest", func(t *testing.T) {
t.Run("non-UTF8 url", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "\xff", nil, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
})
t.Run("non-UTF8 filter", func(t *testing.T) {
var f = "\xff"
putOracleRequest(t, helperValidatorInvoker, "url", &f, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
})
t.Run("not enough gas", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "", nil, 1000, "not enough gas for response")
})
t.Run("disallowed callback", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "_deploy", nil, 1000_0000, "disallowed callback method (starts with '_')")
})
})
}