Merge pull request #2393 from nspcc-dev/core/refactor-TestCreateBasicChain

*: rebase core tests onto neotest
This commit is contained in:
Roman Khimov 2022-03-31 15:29:09 +03:00 committed by GitHub
commit df2bca0f40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 3767 additions and 4100 deletions

View file

@ -17,12 +17,12 @@ commands:
gomod: gomod:
steps: steps:
- restore_cache: - restore_cache:
keys: [deps-] keys: [deps-v2-]
- run: - run:
name: Download go module dependencies name: Download go module dependencies
command: go mod download command: go mod download
- save_cache: - save_cache:
key: deps-{{ checksum "go.sum" }}-{{ checksum "go.sum" }} key: deps-v2-{{ checksum "go.sum" }}-{{ checksum "go.sum" }}
paths: [/home/circleci/go/pkg/mod] paths: [/home/circleci/go/pkg/mod]
jobs: jobs:

View file

@ -0,0 +1,103 @@
package contracts
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
var (
helper1ContractNEFPath = filepath.Join("management_helper", "management_helper1.nef")
helper1ContractManifestPath = filepath.Join("management_helper", "management_helper1.manifest.json")
helper2ContractNEFPath = filepath.Join("management_helper", "management_helper2.nef")
helper2ContractManifestPath = filepath.Join("management_helper", "management_helper2.manifest.json")
oracleContractNEFPath = filepath.Join("oracle_contract", "oracle.nef")
oracleContractManifestPath = filepath.Join("oracle_contract", "oracle.manifest.json")
)
// GetTestContractState reads 2 pre-compiled contracts generated by
// TestGenerateHelperContracts second of which is allowed to call the first.
func GetTestContractState(t *testing.T, pathToInternalContracts string, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) {
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateHelperContracts to regenerate")
neBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, helper1ContractNEFPath))
require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, helper1ContractManifestPath))
require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
cs1 := &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Manifest: *m,
ID: id1,
},
}
neBytes, err = os.ReadFile(filepath.Join(pathToInternalContracts, helper2ContractNEFPath))
require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound))
ne, err = nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err = os.ReadFile(filepath.Join(pathToInternalContracts, helper2ContractManifestPath))
require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound))
m = &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
// Retrieve hash of the first contract from the permissions of the second contract.
require.Equal(t, 1, len(m.Permissions))
require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type)
cs1.Hash = m.Permissions[0].Contract.Hash()
cs2 := &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Manifest: *m,
ID: id2,
Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name),
},
}
return cs1, cs2
}
// GetOracleContractState reads pre-compiled oracle contract generated by
// TestGenerateHelperContracts and returns its state.
func GetOracleContractState(t *testing.T, pathToInternalContracts string, sender util.Uint160, id int32) *state.Contract {
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateHelperContracts to regenerate")
neBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, oracleContractNEFPath))
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, oracleContractManifestPath))
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
return &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
Manifest: *m,
ID: id,
},
}
}

View file

@ -0,0 +1,509 @@
package contracts
import (
"encoding/json"
"os"
"testing"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
// TestGenerateHelperContracts generates contract states that are used in tests.
// See generateOracleContract and generateManagementHelperContracts comments for
// details.
func TestGenerateHelperContracts(t *testing.T) {
const saveState = false
generateOracleContract(t, saveState)
generateManagementHelperContracts(t, saveState)
require.False(t, saveState)
}
// generateOracleContract generates helper contract that is able to call
// native Oracle contract and has callback method. It uses test chain to define
// Oracle and StdLib native hashes and saves generated NEF and manifest to `oracle_contract` folder.
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
func generateOracleContract(t *testing.T, saveState bool) {
bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
c.P2PSigExtensions = true
})
e := neotest.NewExecutor(t, bc, validator, committee)
oracleHash := e.NativeHash(t, nativenames.Oracle)
stdHash := e.NativeHash(t, nativenames.StdLib)
w := io.NewBufBinWriter()
emit.Int(w.BinWriter, 5)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Int(w.BinWriter, int64(callflag.All))
emit.String(w.BinWriter, "request")
emit.Bytes(w.BinWriter, oracleHash.BytesBE())
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
// `handle` method aborts if len(userData) == 2 and does NOT perform witness checks
// for the sake of contract code simplicity (the contract is used in multiple testchains).
offset := w.Len()
emit.Opcodes(w.BinWriter, opcode.OVER)
emit.Opcodes(w.BinWriter, opcode.SIZE)
emit.Int(w.BinWriter, 2)
emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3})
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Int(w.BinWriter, 4) // url, userData, code, result
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
emit.String(w.BinWriter, "lastOracleResponse")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
emit.Opcodes(w.BinWriter, opcode.RET)
m := manifest.NewManifest("TestOracle")
m.ABI.Methods = []manifest.Method{
{
Name: "requestURL",
Offset: 0,
Parameters: []manifest.Parameter{
manifest.NewParameter("url", smartcontract.StringType),
manifest.NewParameter("filter", smartcontract.StringType),
manifest.NewParameter("callback", smartcontract.StringType),
manifest.NewParameter("userData", smartcontract.AnyType),
manifest.NewParameter("gasForResponse", smartcontract.IntegerType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "handle",
Offset: offset,
Parameters: []manifest.Parameter{
manifest.NewParameter("url", smartcontract.StringType),
manifest.NewParameter("userData", smartcontract.AnyType),
manifest.NewParameter("code", smartcontract.IntegerType),
manifest.NewParameter("result", smartcontract.ByteArrayType),
},
ReturnType: smartcontract.VoidType,
},
}
perm := manifest.NewPermission(manifest.PermissionHash, oracleHash)
perm.Methods.Add("request")
m.Permissions = append(m.Permissions, *perm)
// Generate NEF file.
script := w.Bytes()
ne, err := nef.NewFile(script)
require.NoError(t, err)
// Write NEF file.
bytes, err := ne.Bytes()
require.NoError(t, err)
if saveState {
err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write manifest file.
mData, err := json.Marshal(m)
require.NoError(t, err)
if saveState {
err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
}
// generateManagementHelperContracts generates 2 helper contracts second of which is
// allowed to call the first. It uses test chain to define Management and StdLib
// native hashes and saves generated NEF and manifest to `management_contract` folder.
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
func generateManagementHelperContracts(t *testing.T, saveState bool) {
bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
c.P2PSigExtensions = true
})
e := neotest.NewExecutor(t, bc, validator, committee)
mgmtHash := e.NativeHash(t, nativenames.Management)
stdHash := e.NativeHash(t, nativenames.StdLib)
neoHash := e.NativeHash(t, nativenames.Neo)
singleChainValidatorAcc := e.Validator.(neotest.MultiSigner).Single(2).Account() // priv0
require.NoError(t, singleChainValidatorAcc.ConvertMultisig(1, keys.PublicKeys{singleChainValidatorAcc.PrivateKey().PublicKey()}))
singleChainValidatorHash := singleChainValidatorAcc.Contract.ScriptHash()
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
addOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET)
addMultiOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET)
ret7Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET)
dropOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET)
initOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET)
add3Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET)
invalidRetOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET)
justRetOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.RET)
verifyOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB,
opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET)
deployOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3)
emit.String(w.BinWriter, "create") // 8 bytes
emit.Int(w.BinWriter, 2) // 1 byte
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET)
emit.String(w.BinWriter, "update") // 8 bytes
emit.Int(w.BinWriter, 2) // 1 byte
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET)
putValOff := w.Len()
emit.String(w.BinWriter, "initial")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
emit.Opcodes(w.BinWriter, opcode.RET)
getValOff := w.Len()
emit.String(w.BinWriter, "initial")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
emit.Opcodes(w.BinWriter, opcode.RET)
delValOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete)
emit.Opcodes(w.BinWriter, opcode.RET)
onNEP17PaymentOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
emit.Int(w.BinWriter, 4)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "LastPayment")
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcodes(w.BinWriter, opcode.RET)
onNEP11PaymentOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
emit.Int(w.BinWriter, 5)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "LostPayment")
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcodes(w.BinWriter, opcode.RET)
update3Off := w.Len()
emit.Int(w.BinWriter, 3)
emit.Opcodes(w.BinWriter, opcode.JMP, 2+1)
updateOff := w.Len()
emit.Int(w.BinWriter, 2)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All)
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
destroyOff := w.Len()
emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All)
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
invalidStack1Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array
emit.Opcodes(w.BinWriter, opcode.RET)
invalidStack2Off := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item
emit.Opcodes(w.BinWriter, opcode.RET)
callT0Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)
callT1Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET)
callT2Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET)
burnGasOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas)
emit.Opcodes(w.BinWriter, opcode.RET)
invocCounterOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter)
emit.Opcodes(w.BinWriter, opcode.RET)
script := w.Bytes()
m := manifest.NewManifest("TestMain")
m.ABI.Methods = []manifest.Method{
{
Name: "add",
Offset: addOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "add",
Offset: addMultiOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
manifest.NewParameter("addend3", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "ret7",
Offset: ret7Off,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.IntegerType,
},
{
Name: "drop",
Offset: dropOff,
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodInit,
Offset: initOff,
ReturnType: smartcontract.VoidType,
},
{
Name: "add3",
Offset: add3Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "invalidReturn",
Offset: invalidRetOff,
ReturnType: smartcontract.IntegerType,
},
{
Name: "justReturn",
Offset: justRetOff,
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodVerify,
Offset: verifyOff,
ReturnType: smartcontract.BoolType,
},
{
Name: manifest.MethodDeploy,
Offset: deployOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("data", smartcontract.AnyType),
manifest.NewParameter("isUpdate", smartcontract.BoolType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "getValue",
Offset: getValOff,
ReturnType: smartcontract.StringType,
},
{
Name: "putValue",
Offset: putValOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("value", smartcontract.StringType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "delValue",
Offset: delValOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("key", smartcontract.StringType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodOnNEP11Payment,
Offset: onNEP11PaymentOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("tokenid", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodOnNEP17Payment,
Offset: onNEP17PaymentOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "update",
Offset: updateOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("nef", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "update",
Offset: update3Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("nef", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "destroy",
Offset: destroyOff,
ReturnType: smartcontract.VoidType,
},
{
Name: "invalidStack1",
Offset: invalidStack1Off,
ReturnType: smartcontract.AnyType,
},
{
Name: "invalidStack2",
Offset: invalidStack2Off,
ReturnType: smartcontract.AnyType,
},
{
Name: "callT0",
Offset: callT0Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("address", smartcontract.Hash160Type),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "callT1",
Offset: callT1Off,
ReturnType: smartcontract.IntegerType,
},
{
Name: "callT2",
Offset: callT2Off,
ReturnType: smartcontract.IntegerType,
},
{
Name: "burnGas",
Offset: burnGasOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("amount", smartcontract.IntegerType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "invocCounter",
Offset: invocCounterOff,
ReturnType: smartcontract.IntegerType,
},
}
m.Permissions = make([]manifest.Permission, 2)
m.Permissions[0].Contract.Type = manifest.PermissionHash
m.Permissions[0].Contract.Value = neoHash
m.Permissions[0].Methods.Add("balanceOf")
m.Permissions[1].Contract.Type = manifest.PermissionHash
m.Permissions[1].Contract.Value = util.Uint160{}
m.Permissions[1].Methods.Add("method")
// Generate NEF file.
ne, err := nef.NewFile(script)
require.NoError(t, err)
ne.Tokens = []nef.MethodToken{
{
Hash: neoHash,
Method: "balanceOf",
ParamCount: 1,
HasReturn: true,
CallFlag: callflag.ReadStates,
},
{
Hash: util.Uint160{},
Method: "method",
HasReturn: true,
CallFlag: callflag.ReadStates,
},
}
ne.Checksum = ne.CalculateChecksum()
// Write first NEF file.
bytes, err := ne.Bytes()
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write first manifest file.
mData, err := json.Marshal(m)
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
// Create hash of the first contract assuming that sender is single-chain validator.
h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name)
currScript := []byte{byte(opcode.RET)}
m = manifest.NewManifest("TestAux")
m.ABI.Methods = []manifest.Method{
{
Name: "simpleMethod",
Offset: 0,
ReturnType: smartcontract.VoidType,
},
}
perm := manifest.NewPermission(manifest.PermissionHash, h)
perm.Methods.Add("add")
perm.Methods.Add("drop")
perm.Methods.Add("add3")
perm.Methods.Add("invalidReturn")
perm.Methods.Add("justReturn")
perm.Methods.Add("getValue")
m.Permissions = append(m.Permissions, *perm)
ne, err = nef.NewFile(currScript)
require.NoError(t, err)
// Write second NEF file.
bytes, err = ne.Bytes()
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write second manifest file.
mData, err = json.Marshal(m)
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
}

View file

@ -0,0 +1,9 @@
## Management helper contracts
Management helper contracts NEF and manifest files are generated automatically by
`TestGenerateHelperContracts` and are used in tests. Do not modify these files manually.
To regenerate these files:
1. Open `TestGenerateHelperContracts` and set `saveState` flag to `true`.
2. Run `TestGenerateHelperContracts`.
3. Set `saveState` back to `false`.

View file

@ -1 +1 @@
{"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack","offset":325,"parameters":null,"returntype":"Void","safe":false},{"name":"callT0","offset":335,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":341,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":345,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":349,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":355,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null} {"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack1","offset":324,"parameters":null,"returntype":"Any","safe":false},{"name":"invalidStack2","offset":329,"parameters":null,"returntype":"Any","safe":false},{"name":"callT0","offset":335,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":341,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":345,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":349,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":355,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null}

View file

@ -0,0 +1 @@
{"name":"TestAux","abi":{"methods":[{"name":"simpleMethod","offset":0,"parameters":null,"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0x5a6a3b6c716e31465ed9be0b234d6223498c9f4e","methods":["add","drop","add3","invalidReturn","justReturn","getValue"]}],"supportedstandards":[],"trusts":[],"extra":null}

View file

@ -0,0 +1,9 @@
## Oracle helper contract
Oracle helper contract NEF and manifest files are generated automatically by
`TestGenerateHelperContracts` and are used in tests. Do not modify these files manually.
To regenerate these files:
1. Open `TestGenerateHelperContracts` and set `saveState` flag to `true`.
2. Run `TestGenerateHelperContracts`.
3. Set `saveState` back to `false`.

View file

@ -0,0 +1,282 @@
package core_test
import (
"encoding/base64"
"encoding/hex"
"math/big"
"os"
"path"
"path/filepath"
"testing"
"time"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"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/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/rpc/client/nns"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
const (
// examplesPrefix is a prefix of the example smart-contracts.
examplesPrefix = "../../examples/"
// basicChainPrefix is a prefix used to store Basic chain .acc file for tests.
// It is also used to retrieve smart contracts that should be deployed to
// Basic chain.
basicChainPrefix = "../rpc/server/testdata/"
// bcPersistInterval is the time period Blockchain persists changes to the
// underlying storage.
bcPersistInterval = time.Second
)
var (
notaryModulePath = filepath.Join("..", "services", "notary")
pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts")
)
// TestCreateBasicChain generates "../rpc/testdata/testblocks.acc" file which
// contains data for RPC unit tests. It also is a nice integration test.
// To generate new "../rpc/testdata/testblocks.acc", follow the steps:
// 1. Set saveChain down below to true
// 2. Run tests with `$ make test`
func TestCreateBasicChain(t *testing.T) {
const saveChain = false
bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(cfg *config.ProtocolConfiguration) {
cfg.P2PSigExtensions = true
})
e := neotest.NewExecutor(t, bc, validators, committee)
initBasicChain(t, e)
if saveChain {
outStream, err := os.Create(basicChainPrefix + "testblocks.acc")
require.NoError(t, err)
t.Cleanup(func() {
outStream.Close()
})
writer := io.NewBinWriterFromIO(outStream)
writer.WriteU32LE(bc.BlockHeight())
err = chaindump.Dump(bc, writer, 1, bc.BlockHeight())
require.NoError(t, err)
}
require.False(t, saveChain)
}
func initBasicChain(t *testing.T, e *neotest.Executor) {
if !e.Chain.GetConfig().P2PSigExtensions {
t.Fatal("P2PSitExtensions should be enabled to init basic chain")
}
const neoAmount = 99999000
gasHash := e.NativeHash(t, nativenames.Gas)
neoHash := e.NativeHash(t, nativenames.Neo)
policyHash := e.NativeHash(t, nativenames.Policy)
notaryHash := e.NativeHash(t, nativenames.Notary)
designationHash := e.NativeHash(t, nativenames.Designation)
t.Logf("native GAS hash: %v", gasHash)
t.Logf("native NEO hash: %v", neoHash)
t.Logf("native Policy hash: %v", policyHash)
t.Logf("native Notary hash: %v", notaryHash)
t.Logf("Block0 hash: %s", e.Chain.GetHeaderHash(0).StringLE())
acc0 := e.Validator.(neotest.MultiSigner).Single(2) // priv0 index->order and order->index conversion
priv0ScriptHash := acc0.ScriptHash()
acc1 := e.Validator.(neotest.MultiSigner).Single(0) // priv1 index->order and order->index conversion
priv1ScriptHash := acc1.ScriptHash()
neoValidatorInvoker := e.ValidatorInvoker(neoHash)
gasValidatorInvoker := e.ValidatorInvoker(gasHash)
neoPriv0Invoker := e.NewInvoker(neoHash, acc0)
gasPriv0Invoker := e.NewInvoker(gasHash, acc0)
designateSuperInvoker := e.NewInvoker(designationHash, e.Validator, e.Committee)
deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) {
txDeployHash, cH := newDeployTx(t, e, acc0, path, configPath, true)
b := e.TopBlock(t)
return b.Hash(), txDeployHash, cH
}
e.CheckGASBalance(t, priv0ScriptHash, big.NewInt(5000_0000)) // gas bounty
// Block #1: move 1000 GAS and neoAmount NEO to priv0.
txMoveNeo := neoValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, neoAmount, nil)
txMoveGas := gasValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil)
b := e.AddNewBlock(t, txMoveNeo, txMoveGas)
e.CheckHalt(t, txMoveNeo.Hash(), stackitem.Make(true))
e.CheckHalt(t, txMoveGas.Hash(), stackitem.Make(true))
t.Logf("Block1 hash: %s", b.Hash().StringLE())
bw := io.NewBufBinWriter()
b.EncodeBinary(bw.BinWriter)
require.NoError(t, bw.Err)
jsonB, err := b.MarshalJSON()
require.NoError(t, err)
t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
t.Logf("Block1 JSON: %s", string(jsonB))
bw.Reset()
b.Header.EncodeBinary(bw.BinWriter)
require.NoError(t, bw.Err)
jsonH, err := b.Header.MarshalJSON()
require.NoError(t, err)
t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
t.Logf("Header1 JSON: %s", string(jsonH))
jsonTxMoveNeo, err := txMoveNeo.MarshalJSON()
require.NoError(t, err)
t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE())
t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo))
t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes()))
t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE())
e.EnsureGASBalance(t, priv0ScriptHash, func(balance *big.Int) bool { return balance.Cmp(big.NewInt(1000*native.GASFactor)) >= 0 })
// info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter()
b.Header.EncodeBinary(buf.BinWriter)
t.Logf("header: %s", hex.EncodeToString(buf.Bytes()))
// Block #2: deploy test_contract (Rubles contract).
cfgPath := basicChainPrefix + "test_contract.yml"
block2H, txDeployH, cHash := deployContractFromPriv0(t, basicChainPrefix+"test_contract.go", "Rubl", cfgPath, 1)
t.Logf("txDeploy: %s", txDeployH.StringLE())
t.Logf("Block2 hash: %s", block2H.StringLE())
// Block #3: invoke `putValue` method on the test_contract.
rublPriv0Invoker := e.NewInvoker(cHash, acc0)
txInvH := rublPriv0Invoker.Invoke(t, true, "putValue", "testkey", "testvalue")
t.Logf("txInv: %s", txInvH.StringLE())
// Block #4: transfer 1000 NEO from priv0 to priv1.
neoPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 1000, nil)
// Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0.
initTx := rublPriv0Invoker.PrepareInvoke(t, "init")
transferTx := e.NewUnsignedTx(t, rublPriv0Invoker.Hash, "transfer", cHash, priv0ScriptHash, 1000, nil)
e.SignTx(t, transferTx, 1500_0000, acc0) // Set system fee manually to avoid verification failure.
e.AddNewBlock(t, initTx, transferTx)
e.CheckHalt(t, initTx.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, transferTx.Hash(), stackitem.Make(true))
t.Logf("receiveRublesTx: %v", transferTx.Hash().StringLE())
// Block #6: transfer 123 rubles from priv0 to priv1
transferTxH := rublPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 123, nil)
t.Logf("sendRublesTx: %v", transferTxH.StringLE())
// Block #7: push verification contract into the chain.
verifyPath := filepath.Join(basicChainPrefix, "verify", "verification_contract.go")
verifyCfg := filepath.Join(basicChainPrefix, "verify", "verification_contract.yml")
_, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", verifyCfg, 2)
// Block #8: deposit some GAS to notary contract for priv0.
transferTxH = gasPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, notaryHash, 10_0000_0000, []interface{}{priv0ScriptHash, int64(e.Chain.BlockHeight() + 1000)})
t.Logf("notaryDepositTxPriv0: %v", transferTxH.StringLE())
// Block #9: designate new Notary node.
ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json"))
require.NoError(t, err)
require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt))
designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.P2PNotary), []interface{}{ntr.Accounts[0].PrivateKey().PublicKey().Bytes()})
t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes()))
// Block #10: push verification contract with arguments into the chain.
verifyPath = filepath.Join(basicChainPrefix, "verify_args", "verification_with_args_contract.go")
verifyCfg = filepath.Join(basicChainPrefix, "verify_args", "verification_with_args_contract.yml")
_, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", verifyCfg, 3) // block #10
// Block #11: push NameService contract into the chain.
nsPath := examplesPrefix + "nft-nd-nns/"
nsConfigPath := nsPath + "nns.yml"
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11
nsCommitteeInvoker := e.CommitteeInvoker(nsHash)
nsPriv0Invoker := e.NewInvoker(nsHash, acc0)
// Block #12: transfer funds to committee for further NS record registration.
gasValidatorInvoker.Invoke(t, true, "transfer",
e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12
// Block #13: add `.com` root to NNS.
nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13
// Block #14: register `neo.com` via NNS.
registerTxH := nsPriv0Invoker.Invoke(t, true, "register",
"neo.com", priv0ScriptHash) // block #14
res := e.GetTxExecResult(t, registerTxH)
require.Equal(t, 1, len(res.Events)) // transfer
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #15: set A record type with priv0 owner via NNS.
nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1
// Invoke `test_contract.go`: put values to check `findstates` RPC call.
txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") // tx2
txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") // tx3
txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") // tx4
e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16
e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true))
e.CheckHalt(t, txPut3.Hash(), stackitem.NewBool(true))
// Block #17: deploy NeoFS Object contract (NEP11-Divisible).
nfsPath := examplesPrefix + "nft-d/"
nfsConfigPath := nfsPath + "nft.yml"
_, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, 5) // block #17
nfsPriv0Invoker := e.NewInvoker(nfsHash, acc0)
nfsPriv1Invoker := e.NewInvoker(nfsHash, acc1)
// Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
containerID := util.Uint256{1, 2, 3}
objectID := util.Uint256{4, 5, 6}
txGas0toNFSH := gasPriv0Invoker.Invoke(t, true, "transfer",
priv0ScriptHash, nfsHash, 10_0000_0000, []interface{}{containerID.BytesBE(), objectID.BytesBE()}) // block #18
res = e.GetTxExecResult(t, txGas0toNFSH)
require.Equal(t, 2, len(res.Events)) // GAS transfer + NFSO transfer
tokenID, err = res.Events[1].Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #19: transfer 0.25 NFSO from priv0 to priv1.
nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #19
// Block #20: transfer 1000 GAS to priv1.
gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(),
priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #20
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #21
// Compile contract to test `invokescript` RPC call
invokePath := filepath.Join(basicChainPrefix, "invoke", "invokescript_contract.go")
invokeCfg := filepath.Join(basicChainPrefix, "invoke", "invoke.yml")
_, _ = newDeployTx(t, e, acc0, invokePath, invokeCfg, false)
// Prepare some transaction for future submission.
txSendRaw := neoPriv0Invoker.PrepareInvoke(t, "transfer", priv0ScriptHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil)
bw.Reset()
txSendRaw.EncodeBinary(bw.BinWriter)
t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE())
}
func newDeployTx(t *testing.T, e *neotest.Executor, sender neotest.Signer, sourcePath, configPath string, deploy bool) (util.Uint256, util.Uint160) {
c := neotest.CompileFile(t, sender.ScriptHash(), sourcePath, configPath)
t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", sourcePath, c.Hash.StringLE(), base64.StdEncoding.EncodeToString(c.NEF.Script))
if deploy {
return e.DeployContractBy(t, sender, c, nil), c.Hash
}
return util.Uint256{}, c.Hash
}

View file

@ -1,34 +1,33 @@
package core package core_test
import ( import (
"fmt" "fmt"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func BenchmarkVerifyWitness(t *testing.B) { func BenchmarkBlockchain_VerifyWitness(t *testing.B) {
bc := newTestChain(t) bc, acc := chain.NewSingle(t)
acc, err := wallet.NewAccount() e := neotest.NewExecutor(t, bc, acc, acc)
require.NoError(t, err) tx := e.NewTx(t, []neotest.Signer{acc}, e.NativeHash(t, nativenames.Gas), "transfer", acc.ScriptHash(), acc.Script(), 1, nil)
tx := bc.newTestTx(acc.Contract.ScriptHash(), []byte{byte(opcode.PUSH1)})
require.NoError(t, acc.SignTx(netmode.UnitTestNet, tx))
t.ResetTimer() t.ResetTimer()
for n := 0; n < t.N; n++ { for n := 0; n < t.N; n++ {
_, _ = bc.VerifyWitness(tx.Signers[0].Account, tx, &tx.Scripts[0], 100000000) _, err := bc.VerifyWitness(tx.Signers[0].Account, tx, &tx.Scripts[0], 100000000)
require.NoError(t, err)
} }
} }
@ -57,38 +56,35 @@ func BenchmarkBlockchain_ForEachNEP17Transfer(t *testing.B) {
func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBlock, nBlocksToTake int) { func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBlock, nBlocksToTake int) {
var ( var (
nonce uint32 = 1
chainHeight = 2_100 // constant chain height to be able to compare paging results chainHeight = 2_100 // constant chain height to be able to compare paging results
transfersPerBlock = state.TokenTransferBatchSize/4 + // 4 blocks per batch transfersPerBlock = state.TokenTransferBatchSize/4 + // 4 blocks per batch
state.TokenTransferBatchSize/32 // shift state.TokenTransferBatchSize/32 // shift
) )
bc := newTestChainWithCustomCfgAndStore(t, ps, nil) bc, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, ps, true)
gasHash := bc.contracts.GAS.Hash
e := neotest.NewExecutor(t, bc, validators, committee)
gasHash := e.NativeHash(t, nativenames.Gas)
acc := random.Uint160() acc := random.Uint160()
from := e.Validator.ScriptHash()
for j := 0; j < chainHeight; j++ { for j := 0; j < chainHeight; j++ {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
for i := 0; i < transfersPerBlock; i++ { for i := 0; i < transfersPerBlock; i++ {
emit.AppCall(w.BinWriter, gasHash, "transfer", callflag.All, testchain.MultisigScriptHash(), acc, 1, nil) emit.AppCall(w.BinWriter, gasHash, "transfer", callflag.All, from, acc, 1, nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.ASSERT)
require.NoError(t, w.Err)
} }
require.NoError(t, w.Err)
script := w.Bytes() script := w.Bytes()
tx := transaction.New(script, int64(1100_0000*transfersPerBlock)) tx := transaction.New(script, int64(1100_0000*transfersPerBlock))
tx.NetworkFee = 1_0000_000
tx.ValidUntilBlock = bc.BlockHeight() + 1 tx.ValidUntilBlock = bc.BlockHeight() + 1
tx.Nonce = nonce tx.Nonce = neotest.Nonce()
nonce++ tx.Signers = []transaction.Signer{{Account: from, Scopes: transaction.CalledByEntry}}
tx.Signers = []transaction.Signer{{ require.NoError(t, validators.SignTx(netmode.UnitTestNet, tx))
Account: testchain.MultisigScriptHash(), e.AddNewBlock(t, tx)
Scopes: transaction.CalledByEntry, e.CheckHalt(t, tx.Hash())
AllowedContracts: nil,
AllowedGroups: nil,
}}
require.NoError(t, testchain.SignTx(bc, tx))
b := bc.newBlock(tx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, tx.Hash())
} }
newestB, err := bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()) - startFromBlock + 1)) newestB, err := bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()) - startFromBlock + 1))

View file

@ -2342,6 +2342,14 @@ func (bc *Blockchain) GetMaxVerificationGAS() int64 {
return bc.contracts.Policy.GetMaxVerificationGas(bc.dao) return bc.contracts.Policy.GetMaxVerificationGas(bc.dao)
} }
// GetMaxNotValidBeforeDelta returns maximum NotValidBeforeDelta Notary limit.
func (bc *Blockchain) GetMaxNotValidBeforeDelta() uint32 {
if !bc.config.P2PSigExtensions {
panic("disallowed call to Notary")
}
return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)
}
// GetStoragePrice returns current storage price. // GetStoragePrice returns current storage price.
func (bc *Blockchain) GetStoragePrice() int64 { func (bc *Blockchain) GetStoragePrice() int64 {
if bc.BlockHeight() == 0 { if bc.BlockHeight() == 0 {

View file

@ -0,0 +1,322 @@
package core
import (
"encoding/binary"
"errors"
"fmt"
"strings"
"testing"
"time"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/util/slice"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
func TestVerifyHeader(t *testing.T) {
bc := newTestChain(t)
prev := bc.topBlock.Load().(*block.Block).Header
t.Run("Invalid", func(t *testing.T) {
t.Run("Hash", func(t *testing.T) {
h := prev.Hash()
h[0] = ^h[0]
hdr := newBlock(bc.config, 1, h).Header
require.True(t, errors.Is(bc.verifyHeader(&hdr, &prev), ErrHdrHashMismatch))
})
t.Run("Index", func(t *testing.T) {
hdr := newBlock(bc.config, 3, prev.Hash()).Header
require.True(t, errors.Is(bc.verifyHeader(&hdr, &prev), ErrHdrIndexMismatch))
})
t.Run("Timestamp", func(t *testing.T) {
hdr := newBlock(bc.config, 1, prev.Hash()).Header
hdr.Timestamp = 0
require.True(t, errors.Is(bc.verifyHeader(&hdr, &prev), ErrHdrInvalidTimestamp))
})
})
t.Run("Valid", func(t *testing.T) {
hdr := newBlock(bc.config, 1, prev.Hash()).Header
require.NoError(t, bc.verifyHeader(&hdr, &prev))
})
}
func TestAddBlock(t *testing.T) {
const size = 3
bc := newTestChain(t)
blocks, err := bc.genBlocks(size)
require.NoError(t, err)
lastBlock := blocks[len(blocks)-1]
assert.Equal(t, lastBlock.Index, bc.HeaderHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
// This one tests persisting blocks, so it does need to persist()
_, err = bc.persist(false)
require.NoError(t, err)
key := make([]byte, 1+util.Uint256Size)
key[0] = byte(storage.DataExecutable)
for _, block := range blocks {
copy(key[1:], block.Hash().BytesBE())
_, err := bc.dao.Store.Get(key)
require.NoErrorf(t, err, "block %s not persisted", block.Hash())
}
assert.Equal(t, lastBlock.Index, bc.BlockHeight())
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
}
func TestRemoveOldTransfers(t *testing.T) {
// Creating proper number of transfers/blocks takes unneccessary time, so emulate
// some DB with stale entries.
bc := newTestChain(t)
h, err := bc.GetHeader(bc.GetHeaderHash(0))
require.NoError(t, err)
older := h.Timestamp - 1000
newer := h.Timestamp + 1000
acc1 := util.Uint160{1}
acc2 := util.Uint160{2}
acc3 := util.Uint160{3}
ttl := state.TokenTransferLog{Raw: []byte{1}} // It's incorrect, but who cares.
for i := uint32(0); i < 3; i++ {
bc.dao.PutTokenTransferLog(acc1, older, i, false, &ttl)
}
for i := uint32(0); i < 3; i++ {
bc.dao.PutTokenTransferLog(acc2, newer, i, false, &ttl)
}
for i := uint32(0); i < 2; i++ {
bc.dao.PutTokenTransferLog(acc3, older, i, true, &ttl)
}
for i := uint32(0); i < 2; i++ {
bc.dao.PutTokenTransferLog(acc3, newer, i, true, &ttl)
}
_, err = bc.dao.Persist()
require.NoError(t, err)
_ = bc.removeOldTransfers(0)
for i := uint32(0); i < 2; i++ {
log, err := bc.dao.GetTokenTransferLog(acc1, older, i, false)
require.NoError(t, err)
require.Equal(t, 0, len(log.Raw))
}
log, err := bc.dao.GetTokenTransferLog(acc1, older, 2, false)
require.NoError(t, err)
require.NotEqual(t, 0, len(log.Raw))
for i := uint32(0); i < 3; i++ {
log, err = bc.dao.GetTokenTransferLog(acc2, newer, i, false)
require.NoError(t, err)
require.NotEqual(t, 0, len(log.Raw))
}
log, err = bc.dao.GetTokenTransferLog(acc3, older, 0, true)
require.NoError(t, err)
require.Equal(t, 0, len(log.Raw))
log, err = bc.dao.GetTokenTransferLog(acc3, older, 1, true)
require.NoError(t, err)
require.NotEqual(t, 0, len(log.Raw))
for i := uint32(0); i < 2; i++ {
log, err = bc.dao.GetTokenTransferLog(acc3, newer, i, true)
require.NoError(t, err)
require.NotEqual(t, 0, len(log.Raw))
}
}
func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
var (
stateSyncInterval = 4
maxTraceable uint32 = 6
)
spountCfg := func(c *config.Config) {
c.ProtocolConfiguration.RemoveUntraceableBlocks = true
c.ProtocolConfiguration.StateRootInHeader = true
c.ProtocolConfiguration.P2PStateExchangeExtensions = true
c.ProtocolConfiguration.StateSyncInterval = stateSyncInterval
c.ProtocolConfiguration.MaxTraceableBlocks = maxTraceable
c.ProtocolConfiguration.KeepOnlyLatestState = true
}
bcSpout := newTestChainWithCustomCfg(t, spountCfg)
// Generate some content.
for i := 0; i < len(bcSpout.GetConfig().StandbyCommittee); i++ {
require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock()))
}
// reach next to the latest state sync point and pretend that we've just restored
stateSyncPoint := (int(bcSpout.BlockHeight())/stateSyncInterval + 1) * stateSyncInterval
for i := bcSpout.BlockHeight() + 1; i <= uint32(stateSyncPoint); i++ {
require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock()))
}
require.Equal(t, uint32(stateSyncPoint), bcSpout.BlockHeight())
b := bcSpout.newBlock()
require.NoError(t, bcSpout.AddHeaders(&b.Header))
// put storage items with STTemp prefix
batch := storage.NewMemCachedStore(bcSpout.dao.Store)
tempPrefix := storage.STTempStorage
if bcSpout.dao.Version.StoragePrefix == tempPrefix {
tempPrefix = storage.STStorage
}
bPrefix := make([]byte, 1)
bPrefix[0] = byte(bcSpout.dao.Version.StoragePrefix)
bcSpout.dao.Store.Seek(storage.SeekRange{Prefix: bPrefix}, func(k, v []byte) bool {
key := slice.Copy(k)
key[0] = byte(tempPrefix)
value := slice.Copy(v)
batch.Put(key, value)
return true
})
_, err := batch.Persist()
require.NoError(t, err)
checkNewBlockchainErr := func(t *testing.T, cfg func(c *config.Config), store storage.Store, errText string) {
unitTestNetCfg, err := config.Load("../../config", testchain.Network())
require.NoError(t, err)
cfg(&unitTestNetCfg)
log := zaptest.NewLogger(t)
_, err = NewBlockchain(store, unitTestNetCfg.ProtocolConfiguration, log)
if len(errText) != 0 {
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), errText))
} else {
require.NoError(t, err)
}
}
boltCfg := func(c *config.Config) {
spountCfg(c)
c.ProtocolConfiguration.KeepOnlyLatestState = true
}
// manually store statejump stage to check statejump recover process
bPrefix[0] = byte(storage.SYSStateJumpStage)
t.Run("invalid RemoveUntraceableBlocks setting", func(t *testing.T) {
bcSpout.dao.Store.Put(bPrefix, []byte{byte(stateJumpStarted)})
checkNewBlockchainErr(t, func(c *config.Config) {
boltCfg(c)
c.ProtocolConfiguration.RemoveUntraceableBlocks = false
}, bcSpout.dao.Store, "state jump was not completed, but P2PStateExchangeExtensions are disabled or archival node capability is on")
})
t.Run("invalid state jump stage format", func(t *testing.T) {
bcSpout.dao.Store.Put(bPrefix, []byte{0x01, 0x02})
checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, "invalid state jump stage format")
})
t.Run("missing state sync point", func(t *testing.T) {
bcSpout.dao.Store.Put(bPrefix, []byte{byte(stateJumpStarted)})
checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, "failed to get state sync point from the storage")
})
t.Run("invalid state sync point", func(t *testing.T) {
bcSpout.dao.Store.Put(bPrefix, []byte{byte(stateJumpStarted)})
point := make([]byte, 4)
binary.LittleEndian.PutUint32(point, uint32(len(bcSpout.headerHashes)))
bcSpout.dao.Store.Put([]byte{byte(storage.SYSStateSyncPoint)}, point)
checkNewBlockchainErr(t, boltCfg, bcSpout.dao.Store, "invalid state sync point")
})
for _, stage := range []stateJumpStage{stateJumpStarted, newStorageItemsAdded, genesisStateRemoved, 0x03} {
t.Run(fmt.Sprintf("state jump stage %d", stage), func(t *testing.T) {
bcSpout.dao.Store.Put(bPrefix, []byte{byte(stage)})
point := make([]byte, 4)
binary.LittleEndian.PutUint32(point, uint32(stateSyncPoint))
bcSpout.dao.Store.Put([]byte{byte(storage.SYSStateSyncPoint)}, point)
var errText string
if stage == 0x03 {
errText = "unknown state jump stage"
}
checkNewBlockchainErr(t, spountCfg, bcSpout.dao.Store, errText)
})
}
}
func TestChainWithVolatileNumOfValidators(t *testing.T) {
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
c.ProtocolConfiguration.ValidatorsCount = 0
c.ProtocolConfiguration.CommitteeHistory = map[uint32]int{
0: 1,
4: 4,
24: 6,
}
c.ProtocolConfiguration.ValidatorsHistory = map[uint32]int{
0: 1,
4: 4,
}
require.NoError(t, c.ProtocolConfiguration.Validate())
})
require.Equal(t, uint32(0), bc.BlockHeight())
priv0 := testchain.PrivateKeyByID(0)
vals, err := bc.GetValidators()
require.NoError(t, err)
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(vals)
require.NoError(t, err)
curWit := transaction.Witness{
VerificationScript: script,
}
for i := 1; i < 26; i++ {
comm, err := bc.GetCommittee()
require.NoError(t, err)
if i < 5 {
require.Equal(t, 1, len(comm))
} else if i < 25 {
require.Equal(t, 4, len(comm))
} else {
require.Equal(t, 6, len(comm))
}
// Mimic consensus.
if bc.config.ShouldUpdateCommitteeAt(uint32(i)) {
vals, err = bc.GetValidators()
} else {
vals, err = bc.GetNextBlockValidators()
}
require.NoError(t, err)
if i < 4 {
require.Equalf(t, 1, len(vals), "at %d", i)
} else {
require.Equalf(t, 4, len(vals), "at %d", i)
}
require.NoError(t, err)
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(vals)
require.NoError(t, err)
nextWit := transaction.Witness{
VerificationScript: script,
}
b := &block.Block{
Header: block.Header{
NextConsensus: nextWit.ScriptHash(),
Script: curWit,
},
}
curWit = nextWit
b.PrevHash = bc.GetHeaderHash(i - 1)
b.Timestamp = uint64(time.Now().UTC().Unix())*1000 + uint64(i)
b.Index = uint32(i)
b.RebuildMerkleRoot()
if i < 5 {
signa := priv0.SignHashable(uint32(bc.config.Magic), b)
b.Script.InvocationScript = append([]byte{byte(opcode.PUSHDATA1), byte(len(signa))}, signa...)
} else {
b.Script.InvocationScript = testchain.Sign(b)
}
err = bc.AddBlock(b)
require.NoErrorf(t, err, "at %d", i)
}
}
func setSigner(tx *transaction.Transaction, h util.Uint160) {
tx.Signers = []transaction.Signer{{
Account: h,
Scopes: transaction.Global,
}}
}

View file

@ -1,55 +1,21 @@
package core package core
import ( import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"path"
"path/filepath"
"strings"
"testing" "testing"
"time" "time"
"github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/client/nns"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
// multisig address which possess all NEO.
var neoOwner = testchain.MultisigScriptHash()
// examplesPrefix is a prefix of the example smart-contracts.
const examplesPrefix = "../../examples/"
// newTestChain should be called before newBlock invocation to properly setup // newTestChain should be called before newBlock invocation to properly setup
// global state. // global state.
func newTestChain(t testing.TB) *Blockchain { func newTestChain(t testing.TB) *Blockchain {
@ -67,38 +33,6 @@ func newTestChainWithCustomCfgAndStore(t testing.TB, st storage.Store, f func(*c
return chain return chain
} }
func newLevelDBForTesting(t testing.TB) storage.Store {
newLevelStore, _ := newLevelDBForTestingWithPath(t, "")
return newLevelStore
}
func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
if dbPath == "" {
dbPath = t.TempDir()
}
dbOptions := storage.LevelDBOptions{
DataDirectoryPath: dbPath,
}
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
require.Nil(t, err, "NewLevelDBStore error")
return newLevelStore, dbPath
}
func newBoltStoreForTesting(t testing.TB) storage.Store {
boltDBStore, _ := newBoltStoreForTestingWithPath(t, "")
return boltDBStore
}
func newBoltStoreForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) {
if dbPath == "" {
d := t.TempDir()
dbPath = filepath.Join(d, "test_bolt_db")
}
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
require.NoError(t, err)
return boltDBStore, dbPath
}
func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain { func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain {
chain, err := initTestChainNoCheck(t, st, f) chain, err := initTestChainNoCheck(t, st, f)
require.NoError(t, err) require.NoError(t, err)
@ -192,646 +126,3 @@ func (bc *Blockchain) genBlocks(n int) ([]*block.Block, error) {
} }
return blocks, nil return blocks, nil
} }
func TestBug1728(t *testing.T) {
src := `package example
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func init() { if true { } else { } }
func _deploy(_ interface{}, isUpdate bool) {
runtime.Log("Deploy")
}`
nf, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil)
require.NoError(t, err)
m, err := di.ConvertToManifest(&compiler.Options{Name: "TestContract"})
require.NoError(t, err)
rawManifest, err := json.Marshal(m)
require.NoError(t, err)
rawNef, err := nf.Bytes()
require.NoError(t, err)
bc := newTestChain(t)
aer, err := invokeContractMethod(bc, 10000000000,
bc.contracts.Management.Hash, "deploy", rawNef, rawManifest)
require.NoError(t, err)
require.Equal(t, aer.VMState, vm.HaltState)
}
// This function generates "../rpc/testdata/testblocks.acc" file which contains data
// for RPC unit tests. It also is a nice integration test.
// To generate new "../rpc/testdata/testblocks.acc", follow the steps:
// 1. Set saveChain down below to true
// 2. Run tests with `$ make test`
func TestCreateBasicChain(t *testing.T) {
const saveChain = false
const prefix = "../rpc/server/testdata/"
bc := newTestChain(t)
initBasicChain(t, bc)
if saveChain {
outStream, err := os.Create(prefix + "testblocks.acc")
require.NoError(t, err)
t.Cleanup(func() {
outStream.Close()
})
writer := io.NewBinWriterFromIO(outStream)
writer.WriteU32LE(bc.BlockHeight())
err = chaindump.Dump(bc, writer, 1, bc.BlockHeight())
require.NoError(t, err)
}
priv0 := testchain.PrivateKeyByID(0)
priv1 := testchain.PrivateKeyByID(1)
priv0ScriptHash := priv0.GetScriptHash()
acc0 := wallet.NewAccountFromPrivateKey(priv0)
// Prepare some transaction for future submission.
txSendRaw := newNEP17Transfer(bc.contracts.NEO.Hash, priv0ScriptHash, priv1.GetScriptHash(), int64(fixedn.Fixed8FromInt64(1000)))
txSendRaw.ValidUntilBlock = bc.config.MaxValidUntilBlockIncrement
txSendRaw.Nonce = 0x1234
txSendRaw.Signers = []transaction.Signer{{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
}}
require.NoError(t, addNetworkFee(bc, txSendRaw, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txSendRaw))
bw := io.NewBufBinWriter()
txSendRaw.EncodeBinary(bw.BinWriter)
t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE())
require.False(t, saveChain)
}
func initBasicChain(t *testing.T, bc *Blockchain) {
const prefix = "../rpc/server/testdata/"
// Increase in case if you need more blocks
const validUntilBlock = 1200
// To be incremented after each created transaction to keep chain constant.
var testNonce uint32 = 1
// Use as nonce when new transaction is created to avoid random data in tests.
getNextNonce := func() uint32 {
testNonce++
return testNonce
}
const neoAmount = 99999000
gasHash := bc.contracts.GAS.Hash
neoHash := bc.contracts.NEO.Hash
policyHash := bc.contracts.Policy.Hash
notaryHash := bc.contracts.Notary.Hash
t.Logf("native GAS hash: %v", gasHash)
t.Logf("native NEO hash: %v", neoHash)
t.Logf("native Policy hash: %v", policyHash)
t.Logf("native Notary hash: %v", notaryHash)
t.Logf("Block0 hash: %s", bc.GetHeaderHash(0).StringLE())
priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
priv1 := testchain.PrivateKeyByID(1)
priv1ScriptHash := priv1.GetScriptHash()
acc0 := wallet.NewAccountFromPrivateKey(priv0)
acc1 := wallet.NewAccountFromPrivateKey(priv1)
deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath *string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) {
txDeploy, _ := newDeployTx(t, bc, priv0ScriptHash, path, contractName, configPath)
txDeploy.Nonce = getNextNonce()
txDeploy.ValidUntilBlock = validUntilBlock
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txDeploy))
b := bc.newBlock(txDeploy)
require.NoError(t, bc.AddBlock(b)) // block #11
checkTxHalt(t, bc, txDeploy.Hash())
sh, err := bc.GetContractScriptHash(expectedID)
require.NoError(t, err)
return b.Hash(), txDeploy.Hash(), sh
}
require.Equal(t, big.NewInt(5000_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty
// Block #1: move 1000 GAS and neoAmount NEO to priv0.
txMoveNeo, err := testchain.NewTransferFromOwner(bc, neoHash, priv0ScriptHash, neoAmount, getNextNonce(), validUntilBlock)
require.NoError(t, err)
// Move some GAS to one simple account.
txMoveGas, err := testchain.NewTransferFromOwner(bc, gasHash, priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)),
getNextNonce(), validUntilBlock)
require.NoError(t, err)
b := bc.newBlock(txMoveNeo, txMoveGas)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txMoveGas.Hash())
checkTxHalt(t, bc, txMoveNeo.Hash())
t.Logf("Block1 hash: %s", b.Hash().StringLE())
bw := io.NewBufBinWriter()
b.EncodeBinary(bw.BinWriter)
require.NoError(t, bw.Err)
jsonB, err := b.MarshalJSON()
require.NoError(t, err)
t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
t.Logf("Block1 JSON: %s", string(jsonB))
bw.Reset()
b.Header.EncodeBinary(bw.BinWriter)
require.NoError(t, bw.Err)
jsonH, err := b.Header.MarshalJSON()
require.NoError(t, err)
t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes()))
t.Logf("Header1 JSON: %s", string(jsonH))
jsonTxMoveNeo, err := txMoveNeo.MarshalJSON()
require.NoError(t, err)
t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE())
t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo))
t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes()))
t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE())
require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash).Cmp(big.NewInt(1000*native.GASFactor)) >= 0)
// info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter()
b.Header.EncodeBinary(buf.BinWriter)
t.Logf("header: %s", hex.EncodeToString(buf.Bytes()))
// Block #2: deploy test_contract.
cfgPath := prefix + "test_contract.yml"
block2H, txDeployH, cHash := deployContractFromPriv0(t, prefix+"test_contract.go", "Rubl", &cfgPath, 1)
t.Logf("txDeploy: %s", txDeployH.StringLE())
t.Logf("Block2 hash: %s", block2H.StringLE())
// Block #3: invoke `putValue` method on the test_contract.
script := io.NewBufBinWriter()
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "testkey", "testvalue")
txInv := transaction.New(script.Bytes(), 1*native.GASFactor)
txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock
txInv.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txInv, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txInv))
b = bc.newBlock(txInv)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txInv.Hash())
t.Logf("txInv: %s", txInv.Hash().StringLE())
// Block #4: transfer 0.0000_1 NEO from priv0 to priv1.
txNeo0to1 := newNEP17Transfer(neoHash, priv0ScriptHash, priv1ScriptHash, 1000)
txNeo0to1.Nonce = getNextNonce()
txNeo0to1.ValidUntilBlock = validUntilBlock
txNeo0to1.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txNeo0to1))
b = bc.newBlock(txNeo0to1)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, txNeo0to1.Hash())
// Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0.
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, cHash, "init", callflag.All)
initTx := transaction.New(w.Bytes(), 1*native.GASFactor)
initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock
initTx.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, initTx, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), initTx))
transferTx := newNEP17Transfer(cHash, cHash, priv0ScriptHash, 1000)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 1000000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(initTx, transferTx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, initTx.Hash())
checkTxHalt(t, bc, transferTx.Hash())
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringLE())
// Block #6: transfer 123 rubles from priv0 to priv1
transferTx = newNEP17Transfer(cHash, priv0.GetScriptHash(), priv1ScriptHash, 123)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
AllowedContracts: nil,
AllowedGroups: nil,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 1000000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, transferTx.Hash())
t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
// Block #7: push verification contract into the chain.
verifyPath := filepath.Join(prefix, "verify", "verification_contract.go")
_, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", nil, 2)
// Block #8: deposit some GAS to notary contract for priv0.
transferTx = newNEP17Transfer(gasHash, priv0.GetScriptHash(), notaryHash, 10_0000_0000, priv0.GetScriptHash(), int64(bc.BlockHeight()+1000))
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
},
}
require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 10_0000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(transferTx)
require.NoError(t, bc.AddBlock(b))
checkTxHalt(t, bc, transferTx.Hash())
t.Logf("notaryDepositTxPriv0: %v", transferTx.Hash().StringLE())
// Block #9: designate new Notary node.
ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json"))
require.NoError(t, err)
require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt))
bc.setNodesByRole(t, true, noderoles.P2PNotary, keys.PublicKeys{ntr.Accounts[0].PrivateKey().PublicKey()})
t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes()))
// Block #10: push verification contract with arguments into the chain.
verifyPath = filepath.Join(prefix, "verify_args", "verification_with_args_contract.go")
_, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", nil, 3) // block #10
// Block #11: push NameService contract into the chain.
nsPath := examplesPrefix + "nft-nd-nns/"
nsConfigPath := nsPath + "nns.yml"
_, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, &nsConfigPath, 4) // block #11
// Block #12: transfer funds to committee for futher NS record registration.
transferFundsToCommittee(t, bc) // block #12
// Block #13: add `.com` root to NNS.
res, err := invokeContractMethodGeneric(bc, -1,
nsHash, "addRoot", true, "com") // block #13
require.NoError(t, err)
checkResult(t, res, stackitem.Null{})
// Block #14: register `neo.com` via NNS.
res, err = invokeContractMethodGeneric(bc, -1,
nsHash, "register", acc0, "neo.com", priv0ScriptHash) // block #14
require.NoError(t, err)
checkResult(t, res, stackitem.NewBool(true))
require.Equal(t, 1, len(res.Events)) // transfer
tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #15: set A record type with priv0 owner via NNS.
res, err = invokeContractMethodGeneric(bc, -1, nsHash,
"setRecord", acc0, "neo.com", int64(nns.A), "1.2.3.4") // block #15
require.NoError(t, err)
checkResult(t, res, stackitem.Null{})
// Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call
script.Reset()
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "testkey", "newtestvalue")
// Invoke `test_contract.go`: put values to check `findstates` RPC call
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa", "v1")
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa10", "v2")
emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa50", "v3")
txInv = transaction.New(script.Bytes(), 1*native.GASFactor)
txInv.Nonce = getNextNonce()
txInv.ValidUntilBlock = validUntilBlock
txInv.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txInv, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txInv))
b = bc.newBlock(txInv)
require.NoError(t, bc.AddBlock(b)) // block #16
checkTxHalt(t, bc, txInv.Hash())
// Block #17: deploy NeoFS Object contract (NEP11-Divisible).
nfsPath := examplesPrefix + "nft-d/"
nfsConfigPath := nfsPath + "nft.yml"
_, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, &nfsConfigPath, 5) // block #17
// Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract.
containerID := util.Uint256{1, 2, 3}
objectID := util.Uint256{4, 5, 6}
txGas0toNFS := newNEP17Transfer(gasHash, priv0ScriptHash, nfsHash, 10_0000_0000, containerID.BytesBE(), objectID.BytesBE())
txGas0toNFS.SystemFee += 4000_0000
txGas0toNFS.Nonce = getNextNonce()
txGas0toNFS.ValidUntilBlock = validUntilBlock
txGas0toNFS.Signers = []transaction.Signer{
{
Account: priv0ScriptHash,
Scopes: transaction.CalledByEntry,
},
}
require.NoError(t, addNetworkFee(bc, txGas0toNFS, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txGas0toNFS))
b = bc.newBlock(txGas0toNFS)
require.NoError(t, bc.AddBlock(b)) // block #18
checkTxHalt(t, bc, txGas0toNFS.Hash())
aer, _ := bc.GetAppExecResults(txGas0toNFS.Hash(), trigger.Application)
require.Equal(t, 2, len(aer[0].Events)) // GAS transfer + NFSO transfer
tokenID, err = aer[0].Events[1].Item.Value().([]stackitem.Item)[3].TryBytes()
require.NoError(t, err)
t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID))
// Block #19: transfer 0.25 NFSO from priv0 to priv1.
script.Reset()
emit.AppCall(script.BinWriter, nfsHash, "transfer", callflag.All, priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil)
emit.Opcodes(script.BinWriter, opcode.ASSERT)
require.NoError(t, script.Err)
txNFS0to1 := transaction.New(script.Bytes(), 1*native.GASFactor)
txNFS0to1.Nonce = getNextNonce()
txNFS0to1.ValidUntilBlock = validUntilBlock
txNFS0to1.Signers = []transaction.Signer{{Account: priv0ScriptHash, Scopes: transaction.CalledByEntry}}
require.NoError(t, addNetworkFee(bc, txNFS0to1, acc0))
require.NoError(t, acc0.SignTx(testchain.Network(), txNFS0to1))
b = bc.newBlock(txNFS0to1)
require.NoError(t, bc.AddBlock(b)) // block #19
checkTxHalt(t, bc, txNFS0to1.Hash())
// Block #20: transfer 1000 GAS to priv1.
txMoveGas, err = testchain.NewTransferFromOwner(bc, gasHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)),
getNextNonce(), validUntilBlock)
require.NoError(t, err)
require.NoError(t, bc.AddBlock(bc.newBlock(txMoveGas)))
checkTxHalt(t, bc, txMoveGas.Hash()) // block #20
// Block #21: transfer 0.05 NFSO from priv1 back to priv0.
script.Reset()
emit.AppCall(script.BinWriter, nfsHash, "transfer", callflag.All, priv1ScriptHash, priv0.GetScriptHash(), 5, tokenID, nil)
emit.Opcodes(script.BinWriter, opcode.ASSERT)
require.NoError(t, script.Err)
txNFS1to0 := transaction.New(script.Bytes(), 1*native.GASFactor)
txNFS1to0.Nonce = getNextNonce()
txNFS1to0.ValidUntilBlock = validUntilBlock
txNFS1to0.Signers = []transaction.Signer{{Account: priv1ScriptHash, Scopes: transaction.CalledByEntry}}
require.NoError(t, addNetworkFee(bc, txNFS1to0, acc0))
require.NoError(t, acc1.SignTx(testchain.Network(), txNFS1to0))
b = bc.newBlock(txNFS1to0)
require.NoError(t, bc.AddBlock(b)) // block #21
checkTxHalt(t, bc, txNFS1to0.Hash())
// Compile contract to test `invokescript` RPC call
invokePath := filepath.Join(prefix, "invoke", "invokescript_contract.go")
invokeCfg := filepath.Join(prefix, "invoke", "invoke.yml")
_, _ = newDeployTx(t, bc, priv0ScriptHash, invokePath, "ContractForInvokescriptTest", &invokeCfg)
}
func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
return newNEP17TransferWithAssert(sc, from, to, amount, true, additionalArgs...)
}
func newNEP17TransferWithAssert(sc, from, to util.Uint160, amount int64, needAssert bool, additionalArgs ...interface{}) *transaction.Transaction {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, from, to, amount, additionalArgs)
if needAssert {
emit.Opcodes(w.BinWriter, opcode.ASSERT)
}
if w.Err != nil {
panic(fmt.Errorf("failed to create NEP-17 transfer transaction: %w", w.Err))
}
script := w.Bytes()
return transaction.New(script, 11000000)
}
func newDeployTx(t *testing.T, bc *Blockchain, sender util.Uint160, name, ctrName string, cfgName *string) (*transaction.Transaction, util.Uint160) {
tx, h, avm, err := testchain.NewDeployTx(bc, name, sender, nil, cfgName)
require.NoError(t, err)
t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", name, h.StringLE(), base64.StdEncoding.EncodeToString(avm))
return tx, h
}
func addSigners(sender util.Uint160, txs ...*transaction.Transaction) {
for _, tx := range txs {
tx.Signers = []transaction.Signer{{
Account: sender,
Scopes: transaction.Global,
AllowedContracts: nil,
AllowedGroups: nil,
}}
}
}
func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error {
size := io.GetVarSize(tx)
netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), sender.Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
for _, cosigner := range tx.Signers {
contract := bc.GetContractState(cosigner.Account)
if contract != nil {
netFee, sizeDelta = fee.Calculate(bc.GetBaseExecFee(), contract.NEF.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
}
tx.NetworkFee += int64(size) * bc.FeePerByte()
return nil
}
// Signer can be either bool or *wallet.Account.
// In the first case `true` means sign by committee, `false` means sign by validators.
func prepareContractMethodInvokeGeneric(chain *Blockchain, sysfee int64,
hash util.Uint160, method string, signer interface{}, args ...interface{}) (*transaction.Transaction, error) {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, hash, method, callflag.All, args...)
if w.Err != nil {
return nil, w.Err
}
script := w.Bytes()
tx := transaction.New(script, 0)
tx.ValidUntilBlock = chain.blockHeight + 1
var err error
switch s := signer.(type) {
case bool:
if s {
addSigners(testchain.CommitteeScriptHash(), tx)
setTxSystemFee(chain, sysfee, tx)
err = testchain.SignTxCommittee(chain, tx)
} else {
addSigners(neoOwner, tx)
setTxSystemFee(chain, sysfee, tx)
err = testchain.SignTx(chain, tx)
}
case *wallet.Account:
signTxWithAccounts(chain, sysfee, tx, s)
case []*wallet.Account:
signTxWithAccounts(chain, sysfee, tx, s...)
default:
panic("invalid signer")
}
if err != nil {
return nil, err
}
return tx, nil
}
func setTxSystemFee(bc *Blockchain, sysFee int64, tx *transaction.Transaction) {
if sysFee >= 0 {
tx.SystemFee = sysFee
return
}
lastBlock := bc.topBlock.Load().(*block.Block)
b := &block.Block{
Header: block.Header{
Index: lastBlock.Index + 1,
Timestamp: lastBlock.Timestamp + 1000,
},
Transactions: []*transaction.Transaction{tx},
}
ttx := *tx // prevent setting 'hash' field
ic := bc.GetTestVM(trigger.Application, &ttx, b)
defer ic.Finalize()
ic.VM.LoadWithFlags(tx.Script, callflag.All)
_ = ic.VM.Run()
tx.SystemFee = ic.VM.GasConsumed()
}
func signTxWithAccounts(chain *Blockchain, sysFee int64, tx *transaction.Transaction, accs ...*wallet.Account) {
scope := transaction.CalledByEntry
for _, acc := range accs {
accH, _ := address.StringToUint160(acc.Address)
tx.Signers = append(tx.Signers, transaction.Signer{
Account: accH,
Scopes: scope,
})
scope = transaction.Global
}
setTxSystemFee(chain, sysFee, tx)
size := io.GetVarSize(tx)
for _, acc := range accs {
if acc.Contract.Deployed {
// don't need precise calculation for tests
tx.NetworkFee += 1000_0000
continue
}
netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc.Contract.Script)
size += sizeDelta
tx.NetworkFee += netFee
}
tx.NetworkFee += int64(size) * chain.FeePerByte()
for _, acc := range accs {
if err := acc.SignTx(testchain.Network(), tx); err != nil {
panic(err)
}
}
}
func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) {
b := chain.newBlock(txs...)
err := chain.AddBlock(b)
if err != nil {
return nil, err
}
aers := make([]*state.AppExecResult, len(txs))
for i, tx := range txs {
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
if err != nil {
return nil, err
}
aers[i] = &res[0]
}
return aers, nil
}
func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) {
return invokeContractMethodGeneric(chain, sysfee, hash, method, false, args...)
}
func invokeContractMethodGeneric(chain *Blockchain, sysfee int64, hash util.Uint160, method string,
signer interface{}, args ...interface{}) (*state.AppExecResult, error) {
tx, err := prepareContractMethodInvokeGeneric(chain, sysfee, hash,
method, signer, args...)
if err != nil {
return nil, err
}
aers, err := persistBlock(chain, tx)
if err != nil {
return nil, err
}
return aers[0], nil
}
func transferTokenFromMultisigAccountCheckOK(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) {
transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...)
res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
require.Equal(t, 0, len(res[0].Stack))
}
func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction {
return transferTokenFromMultisigAccountWithAssert(t, chain, to, tokenHash, amount, true, additionalArgs...)
}
func transferTokenFromMultisigAccountWithAssert(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, needAssert bool, additionalArgs ...interface{}) *transaction.Transaction {
transferTx := newNEP17TransferWithAssert(tokenHash, testchain.MultisigScriptHash(), to, amount, needAssert, additionalArgs...)
transferTx.SystemFee = 100000000
transferTx.ValidUntilBlock = chain.BlockHeight() + 1
addSigners(neoOwner, transferTx)
require.NoError(t, testchain.SignTx(chain, transferTx))
b := chain.newBlock(transferTx)
require.NoError(t, chain.AddBlock(b))
return transferTx
}
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
require.Equal(t, vm.HaltState, result.VMState, result.FaultException)
require.Equal(t, 1, len(result.Stack))
require.Equal(t, expected, result.Stack[0])
}
func checkTxHalt(t testing.TB, bc *Blockchain, h util.Uint256) {
aer, err := bc.GetAppExecResults(h, trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
}
func checkFAULTState(t *testing.T, result *state.AppExecResult) {
require.Equal(t, vm.FaultState, result.VMState)
}
func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected int) {
balance := chain.GetUtilityTokenBalance(addr)
require.Equal(t, int64(expected), balance.Int64())
}
type NotaryFeerStub struct {
bc blockchainer.Blockchainer
}
func (f NotaryFeerStub) FeePerByte() int64 { return f.bc.FeePerByte() }
func (f NotaryFeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
return f.bc.GetNotaryBalance(acc)
}
func (f NotaryFeerStub) BlockHeight() uint32 { return f.bc.BlockHeight() }
func (f NotaryFeerStub) P2PSigExtensionsEnabled() bool { return f.bc.P2PSigExtensionsEnabled() }
func NewNotaryFeerStub(bc blockchainer.Blockchainer) NotaryFeerStub {
return NotaryFeerStub{
bc: bc,
}
}

View file

@ -1,24 +1,20 @@
package core package core
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"math"
"math/big" "math/big"
"os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
@ -29,7 +25,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
@ -39,10 +34,11 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts")
// Tests are taken from // Tests are taken from
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs // https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs
func TestRuntimeGetRandomCompatibility(t *testing.T) { func TestRuntimeGetRandomCompatibility(t *testing.T) {
@ -72,29 +68,6 @@ func TestRuntimeGetRandomCompatibility(t *testing.T) {
require.Equal(t, "217172703763162599519098299724476526911", ic.VM.Estack().Pop().BigInt().String()) require.Equal(t, "217172703763162599519098299724476526911", ic.VM.Estack().Pop().BigInt().String())
} }
func TestRuntimeGetRandomDifferentTransactions(t *testing.T) {
bc := newTestChain(t)
b, _ := bc.GetBlock(bc.GetHeaderHash(0))
tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
ic1 := bc.newInteropContext(trigger.Application, bc.dao.GetWrapped(), b, tx1)
ic1.VM = vm.New()
ic1.VM.LoadScript(tx1.Script)
tx2 := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
ic2 := bc.newInteropContext(trigger.Application, bc.dao.GetWrapped(), b, tx2)
ic2.VM = vm.New()
ic2.VM.LoadScript(tx2.Script)
require.NoError(t, runtime.GetRandom(ic1))
require.NoError(t, runtime.GetRandom(ic2))
require.NotEqual(t, ic1.VM.Estack().Pop().BigInt(), ic2.VM.Estack().Pop().BigInt())
require.NoError(t, runtime.GetRandom(ic1))
require.NoError(t, runtime.GetRandom(ic2))
require.NotEqual(t, ic1.VM.Estack().Pop().BigInt(), ic2.VM.Estack().Pop().BigInt())
}
func getSharpTestTx(sender util.Uint160) *transaction.Transaction { func getSharpTestTx(sender util.Uint160) *transaction.Transaction {
tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0) tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
tx.Nonce = 0 tx.Nonce = 0
@ -116,81 +89,6 @@ func getSharpTestGenesis(t *testing.T) *block.Block {
return b return b
} }
func TestContractCreateAccount(t *testing.T) {
v, ic, _ := createVM(t)
t.Run("Good", func(t *testing.T) {
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
pub := priv.PublicKey()
v.Estack().PushVal(pub.Bytes())
require.NoError(t, contractCreateStandardAccount(ic))
value := v.Estack().Pop().Bytes()
u, err := util.Uint160DecodeBytesBE(value)
require.NoError(t, err)
require.Equal(t, pub.GetScriptHash(), u)
})
t.Run("InvalidKey", func(t *testing.T) {
v.Estack().PushVal([]byte{1, 2, 3})
require.Error(t, contractCreateStandardAccount(ic))
})
}
func TestContractCreateMultisigAccount(t *testing.T) {
v, ic, _ := createVM(t)
t.Run("Good", func(t *testing.T) {
m, n := 3, 5
pubs := make(keys.PublicKeys, n)
arr := make([]stackitem.Item, n)
for i := range pubs {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
pubs[i] = pk.PublicKey()
arr[i] = stackitem.Make(pubs[i].Bytes())
}
v.Estack().PushVal(stackitem.Make(arr))
v.Estack().PushVal(m)
require.NoError(t, contractCreateMultisigAccount(ic))
expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
require.NoError(t, err)
value := v.Estack().Pop().Bytes()
u, err := util.Uint160DecodeBytesBE(value)
require.NoError(t, err)
require.Equal(t, hash.Hash160(expected), u)
})
t.Run("InvalidKey", func(t *testing.T) {
v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make([]byte{1, 2, 3})}))
v.Estack().PushVal(1)
require.Error(t, contractCreateMultisigAccount(ic))
})
t.Run("Invalid m", func(t *testing.T) {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make(pk.PublicKey().Bytes())}))
v.Estack().PushVal(2)
require.Error(t, contractCreateMultisigAccount(ic))
})
t.Run("m overflows int64", func(t *testing.T) {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make(pk.PublicKey().Bytes())}))
m := big.NewInt(math.MaxInt64)
m.Add(m, big.NewInt(1))
v.Estack().PushVal(stackitem.NewBigInteger(m))
require.Error(t, contractCreateMultisigAccount(ic))
})
}
func TestRuntimeGasLeft(t *testing.T) {
v, ic, _ := createVM(t)
v.GasLimit = 100
v.AddGas(58)
require.NoError(t, runtime.GasLeft(ic))
require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64())
}
func TestRuntimeGetNotifications(t *testing.T) { func TestRuntimeGetNotifications(t *testing.T) {
v, ic, _ := createVM(t) v, ic, _ := createVM(t)
@ -235,7 +133,7 @@ func TestRuntimeGetNotifications(t *testing.T) {
func TestRuntimeGetInvocationCounter(t *testing.T) { func TestRuntimeGetInvocationCounter(t *testing.T) {
v, ic, bc := createVM(t) v, ic, bc := createVM(t)
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
ic.Invocations[hash.Hash160([]byte{2})] = 42 ic.Invocations[hash.Hash160([]byte{2})] = 42
@ -661,432 +559,6 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C
return v, contractState, context, chain return v, contractState, context, chain
} }
var (
helper1ContractNEFPath = filepath.Join("test_data", "management_helper", "management_helper1.nef")
helper1ContractManifestPath = filepath.Join("test_data", "management_helper", "management_helper1.manifest.json")
helper2ContractNEFPath = filepath.Join("test_data", "management_helper", "management_helper2.nef")
helper2ContractManifestPath = filepath.Join("test_data", "management_helper", "management_helper2.manifest.json")
)
// TestGenerateManagementHelperContracts generates 2 helper contracts second of which is
// allowed to call the first. It uses test chain to define Management and StdLib
// native hashes and saves generated NEF and manifest to ../test_data/management_contract folder.
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
func TestGenerateManagementHelperContracts(t *testing.T) {
const saveState = false
bc := newTestChain(t)
mgmtHash := bc.contracts.Management.Hash
stdHash := bc.contracts.Std.Hash
neoHash := bc.contracts.NEO.Hash
singleChainValidator := testchain.PrivateKey(testchain.IDToOrder(0))
acc := wallet.NewAccountFromPrivateKey(singleChainValidator)
require.NoError(t, acc.ConvertMultisig(1, keys.PublicKeys{singleChainValidator.PublicKey()}))
singleChainValidatorHash := acc.Contract.ScriptHash()
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
addOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET)
addMultiOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET)
ret7Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET)
dropOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET)
initOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET)
add3Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET)
invalidRetOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET)
justRetOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.RET)
verifyOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB,
opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET)
deployOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3)
emit.String(w.BinWriter, "create") // 8 bytes
emit.Int(w.BinWriter, 2) // 1 byte
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET)
emit.String(w.BinWriter, "update") // 8 bytes
emit.Int(w.BinWriter, 2) // 1 byte
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET)
putValOff := w.Len()
emit.String(w.BinWriter, "initial")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
emit.Opcodes(w.BinWriter, opcode.RET)
getValOff := w.Len()
emit.String(w.BinWriter, "initial")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
emit.Opcodes(w.BinWriter, opcode.RET)
delValOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete)
emit.Opcodes(w.BinWriter, opcode.RET)
onNEP17PaymentOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
emit.Int(w.BinWriter, 4)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "LastPayment")
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcodes(w.BinWriter, opcode.RET)
onNEP11PaymentOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
emit.Int(w.BinWriter, 5)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.String(w.BinWriter, "LostPayment")
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcodes(w.BinWriter, opcode.RET)
update3Off := w.Len()
emit.Int(w.BinWriter, 3)
emit.Opcodes(w.BinWriter, opcode.JMP, 2+1)
updateOff := w.Len()
emit.Int(w.BinWriter, 2)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All)
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
destroyOff := w.Len()
emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All)
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
invalidStackOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item
emit.Opcodes(w.BinWriter, opcode.RET)
callT0Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)
callT1Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET)
callT2Off := w.Len()
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET)
burnGasOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas)
emit.Opcodes(w.BinWriter, opcode.RET)
invocCounterOff := w.Len()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter)
emit.Opcodes(w.BinWriter, opcode.RET)
script := w.Bytes()
m := manifest.NewManifest("TestMain")
m.ABI.Methods = []manifest.Method{
{
Name: "add",
Offset: addOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "add",
Offset: addMultiOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
manifest.NewParameter("addend3", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "ret7",
Offset: ret7Off,
Parameters: []manifest.Parameter{},
ReturnType: smartcontract.IntegerType,
},
{
Name: "drop",
Offset: dropOff,
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodInit,
Offset: initOff,
ReturnType: smartcontract.VoidType,
},
{
Name: "add3",
Offset: add3Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("addend", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "invalidReturn",
Offset: invalidRetOff,
ReturnType: smartcontract.IntegerType,
},
{
Name: "justReturn",
Offset: justRetOff,
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodVerify,
Offset: verifyOff,
ReturnType: smartcontract.BoolType,
},
{
Name: manifest.MethodDeploy,
Offset: deployOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("data", smartcontract.AnyType),
manifest.NewParameter("isUpdate", smartcontract.BoolType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "getValue",
Offset: getValOff,
ReturnType: smartcontract.StringType,
},
{
Name: "putValue",
Offset: putValOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("value", smartcontract.StringType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "delValue",
Offset: delValOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("key", smartcontract.StringType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodOnNEP11Payment,
Offset: onNEP11PaymentOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("tokenid", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: manifest.MethodOnNEP17Payment,
Offset: onNEP17PaymentOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "update",
Offset: updateOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("nef", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "update",
Offset: update3Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("nef", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "destroy",
Offset: destroyOff,
ReturnType: smartcontract.VoidType,
},
{
Name: "invalidStack",
Offset: invalidStackOff,
ReturnType: smartcontract.VoidType,
},
{
Name: "callT0",
Offset: callT0Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("address", smartcontract.Hash160Type),
},
ReturnType: smartcontract.IntegerType,
},
{
Name: "callT1",
Offset: callT1Off,
ReturnType: smartcontract.IntegerType,
},
{
Name: "callT2",
Offset: callT2Off,
ReturnType: smartcontract.IntegerType,
},
{
Name: "burnGas",
Offset: burnGasOff,
Parameters: []manifest.Parameter{
manifest.NewParameter("amount", smartcontract.IntegerType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "invocCounter",
Offset: invocCounterOff,
ReturnType: smartcontract.IntegerType,
},
}
m.Permissions = make([]manifest.Permission, 2)
m.Permissions[0].Contract.Type = manifest.PermissionHash
m.Permissions[0].Contract.Value = bc.contracts.NEO.Hash
m.Permissions[0].Methods.Add("balanceOf")
m.Permissions[1].Contract.Type = manifest.PermissionHash
m.Permissions[1].Contract.Value = util.Uint160{}
m.Permissions[1].Methods.Add("method")
// Generate NEF file.
ne, err := nef.NewFile(script)
if err != nil {
panic(err)
}
ne.Tokens = []nef.MethodToken{
{
Hash: neoHash,
Method: "balanceOf",
ParamCount: 1,
HasReturn: true,
CallFlag: callflag.ReadStates,
},
{
Hash: util.Uint160{},
Method: "method",
HasReturn: true,
CallFlag: callflag.ReadStates,
},
}
ne.Checksum = ne.CalculateChecksum()
// Write first NEF file.
bytes, err := ne.Bytes()
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write first manifest file.
mData, err := json.Marshal(m)
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
// Create hash of the first contract assuming that sender is single-chain validator.
h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name)
currScript := []byte{byte(opcode.RET)}
m = manifest.NewManifest("TestAux")
perm := manifest.NewPermission(manifest.PermissionHash, h)
perm.Methods.Add("add")
perm.Methods.Add("drop")
perm.Methods.Add("add3")
perm.Methods.Add("invalidReturn")
perm.Methods.Add("justReturn")
perm.Methods.Add("getValue")
m.Permissions = append(m.Permissions, *perm)
ne, err = nef.NewFile(currScript)
if err != nil {
panic(err)
}
// Write second NEF file.
bytes, err = ne.Bytes()
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write second manifest file.
mData, err = json.Marshal(m)
require.NoError(t, err)
if saveState {
err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
require.False(t, saveState)
}
// getTestContractState returns 2 contracts second of which is allowed to call the first.
func getTestContractState(t *testing.T, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) {
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
neBytes, err := os.ReadFile(helper1ContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := os.ReadFile(helper1ContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
cs1 := &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Manifest: *m,
ID: id1,
},
}
neBytes, err = os.ReadFile(helper2ContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound))
ne, err = nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err = os.ReadFile(helper2ContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound))
m = &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
// Retrieve hash of the first contract from the permissions of the second contract.
require.Equal(t, 1, len(m.Permissions))
require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type)
cs1.Hash = m.Permissions[0].Contract.Hash()
cs2 := &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Manifest: *m,
ID: id2,
Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name),
},
}
return cs1, cs2
}
func loadScript(ic *interop.Context, script []byte, args ...interface{}) { func loadScript(ic *interop.Context, script []byte, args ...interface{}) {
ic.SpawnVM() ic.SpawnVM()
ic.VM.LoadScriptWithFlags(script, callflag.AllowCall) ic.VM.LoadScriptWithFlags(script, callflag.AllowCall)
@ -1108,7 +580,7 @@ func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Ui
func TestContractCall(t *testing.T) { func TestContractCall(t *testing.T) {
_, ic, bc := createVM(t) _, ic, bc := createVM(t)
cs, currCs := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test cs, currCs := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs))
require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs)) require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs))
@ -1496,80 +968,3 @@ func TestRuntimeCheckWitness(t *testing.T) {
}) })
}) })
} }
func TestLoadToken(t *testing.T) {
bc := newTestChain(t)
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
t.Run("good", func(t *testing.T) {
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "callT0", neoOwner.BytesBE())
require.NoError(t, err)
realBalance, _ := bc.GetGoverningTokenBalance(neoOwner)
checkResult(t, aer, stackitem.Make(realBalance.Int64()+1))
})
t.Run("invalid param count", func(t *testing.T) {
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "callT2")
require.NoError(t, err)
checkFAULTState(t, aer)
})
t.Run("invalid contract", func(t *testing.T) {
aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "callT1")
require.NoError(t, err)
checkFAULTState(t, aer)
})
}
func TestRuntimeGetNetwork(t *testing.T) {
bc := newTestChain(t)
w := io.NewBufBinWriter()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork)
require.NoError(t, w.Err)
tx := transaction.New(w.Bytes(), 10_000)
tx.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(neoOwner, tx)
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
checkResult(t, &aer[0], stackitem.Make(uint32(bc.config.Magic)))
}
func TestRuntimeBurnGas(t *testing.T) {
bc := newTestChain(t)
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
const sysFee = 2_000000
t.Run("good", func(t *testing.T) {
aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", int64(1))
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer.VMState)
t.Run("gas limit exceeded", func(t *testing.T) {
aer, err = invokeContractMethod(bc, aer.GasConsumed, cs.Hash, "burnGas", int64(2))
require.NoError(t, err)
require.Equal(t, vm.FaultState, aer.VMState)
})
})
t.Run("too big integer", func(t *testing.T) {
gas := big.NewInt(math.MaxInt64)
gas.Add(gas, big.NewInt(1))
aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", gas)
require.NoError(t, err)
checkFAULTState(t, aer)
})
t.Run("zero GAS", func(t *testing.T) {
aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", int64(0))
require.NoError(t, err)
checkFAULTState(t, aer)
})
}

View file

@ -0,0 +1,245 @@
package core_test
import (
"encoding/json"
"math"
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)
func TestSystemRuntimeGetRandom_DifferentTransactions(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetRandom)
require.NoError(t, w.Err)
script := w.Bytes()
tx1 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
tx2 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
e.AddNewBlock(t, tx1, tx2)
e.CheckHalt(t, tx1.Hash())
e.CheckHalt(t, tx2.Hash())
res1 := e.GetTxExecResult(t, tx1.Hash())
res2 := e.GetTxExecResult(t, tx2.Hash())
r1, err := res1.Stack[0].TryInteger()
require.NoError(t, err)
r2, err := res2.Stack[0].TryInteger()
require.NoError(t, err)
require.NotEqual(t, r1, r2)
}
func TestSystemContractCreateStandardAccount(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
t.Run("Good", func(t *testing.T) {
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
pub := priv.PublicKey()
emit.Bytes(w.BinWriter, pub.Bytes())
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
require.NoError(t, w.Err)
script := w.Bytes()
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
res := e.GetTxExecResult(t, tx.Hash())
value := res.Stack[0].Value().([]byte)
u, err := util.Uint160DecodeBytesBE(value)
require.NoError(t, err)
require.Equal(t, pub.GetScriptHash(), u)
})
t.Run("InvalidKey", func(t *testing.T) {
w.Reset()
emit.Bytes(w.BinWriter, []byte{1, 2, 3})
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount)
require.NoError(t, w.Err)
script := w.Bytes()
tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1)
e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), "invalid prefix 1")
})
}
func TestSystemContractCreateMultisigAccount(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
createScript := func(t *testing.T, pubs []interface{}, m int) []byte {
w.Reset()
emit.Array(w.BinWriter, pubs...)
emit.Int(w.BinWriter, int64(m))
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
require.NoError(t, w.Err)
return w.Bytes()
}
t.Run("Good", func(t *testing.T) {
m, n := 3, 5
pubs := make(keys.PublicKeys, n)
arr := make([]interface{}, n)
for i := range pubs {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
pubs[i] = pk.PublicKey()
arr[i] = pubs[i].Bytes()
}
script := createScript(t, arr, m)
txH := e.InvokeScript(t, script, []neotest.Signer{acc})
e.CheckHalt(t, txH)
res := e.GetTxExecResult(t, txH)
value := res.Stack[0].Value().([]byte)
u, err := util.Uint160DecodeBytesBE(value)
require.NoError(t, err)
expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs)
require.NoError(t, err)
require.Equal(t, hash.Hash160(expected), u)
})
t.Run("InvalidKey", func(t *testing.T) {
script := createScript(t, []interface{}{[]byte{1, 2, 3}}, 1)
e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "invalid prefix 1")
})
t.Run("Invalid m", func(t *testing.T) {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
script := createScript(t, []interface{}{pk.PublicKey().Bytes()}, 2)
e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "length of the signatures (2) is higher then the number of public keys")
})
t.Run("m overflows int32", func(t *testing.T) {
pk, err := keys.NewPrivateKey()
require.NoError(t, err)
m := big.NewInt(math.MaxInt32)
m.Add(m, big.NewInt(1))
w.Reset()
emit.Array(w.BinWriter, pk.Bytes())
emit.BigInt(w.BinWriter, m)
emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount)
require.NoError(t, w.Err)
e.InvokeScriptCheckFAULT(t, w.Bytes(), []neotest.Signer{acc}, "m must be positive and fit int32")
})
}
func TestSystemRuntimeGasLeft(t *testing.T) {
const runtimeGasLeftPrice = 1 << 4
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
gasLimit := 1100
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft)
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft)
require.NoError(t, w.Err)
tx := transaction.New(w.Bytes(), int64(gasLimit))
tx.Nonce = neotest.Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
e.SignTx(t, tx, int64(gasLimit), acc)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
res := e.GetTxExecResult(t, tx.Hash())
l1 := res.Stack[0].Value().(*big.Int)
l2 := res.Stack[1].Value().(*big.Int)
require.Equal(t, int64(gasLimit-runtimeGasLeftPrice*interop.DefaultBaseExecFee), l1.Int64())
require.Equal(t, int64(gasLimit-2*runtimeGasLeftPrice*interop.DefaultBaseExecFee), l2.Int64())
}
func TestLoadToken(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash())
rawManifest, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
rawNef, err := cs.NEF.Bytes()
require.NoError(t, err)
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
cInvoker := e.ValidatorInvoker(cs.Hash)
t.Run("good", func(t *testing.T) {
realBalance, _ := bc.GetGoverningTokenBalance(acc.ScriptHash())
cInvoker.Invoke(t, stackitem.NewBigInteger(big.NewInt(realBalance.Int64()+1)), "callT0", acc.ScriptHash())
})
t.Run("invalid param count", func(t *testing.T) {
cInvoker.InvokeFail(t, "method not found: callT2/1", "callT2", acc.ScriptHash())
})
t.Run("invalid contract", func(t *testing.T) {
cInvoker.InvokeFail(t, "token contract 0000000000000000000000000000000000000000 not found: key not found", "callT1")
})
}
func TestSystemRuntimeGetNetwork(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
w := io.NewBufBinWriter()
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork)
require.NoError(t, w.Err)
e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(bc.GetConfig().Magic))))
}
func TestSystemRuntimeBurnGas(t *testing.T) {
bc, acc := chain.NewSingle(t)
e := neotest.NewExecutor(t, bc, acc, acc)
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash())
rawManifest, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
rawNef, err := cs.NEF.Bytes()
require.NoError(t, err)
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
cInvoker := e.ValidatorInvoker(cs.Hash)
t.Run("good", func(t *testing.T) {
h := cInvoker.Invoke(t, stackitem.Null{}, "burnGas", int64(1))
res := e.GetTxExecResult(t, h)
t.Run("gas limit exceeded", func(t *testing.T) {
tx := e.NewUnsignedTx(t, cs.Hash, "burnGas", int64(2))
e.SignTx(t, tx, res.GasConsumed, acc)
e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), "GAS limit exceeded")
})
})
t.Run("too big integer", func(t *testing.T) {
gas := big.NewInt(math.MaxInt64)
gas.Add(gas, big.NewInt(1))
cInvoker.InvokeFail(t, "invalid GAS value", "burnGas", gas)
})
t.Run("zero GAS", func(t *testing.T) {
cInvoker.InvokeFail(t, "GAS must be positive", "burnGas", int64(0))
})
}

View file

@ -17,14 +17,15 @@ func Call(ic *interop.Context) error {
return fmt.Errorf("native contract of version %d is not active", version) return fmt.Errorf("native contract of version %d is not active", version)
} }
var c interop.Contract var c interop.Contract
curr := ic.VM.GetCurrentScriptHash()
for _, ctr := range ic.Natives { for _, ctr := range ic.Natives {
if ctr.Metadata().Hash == ic.VM.GetCurrentScriptHash() { if ctr.Metadata().Hash == curr {
c = ctr c = ctr
break break
} }
} }
if c == nil { if c == nil {
return fmt.Errorf("native contract %d not found", version) return fmt.Errorf("native contract %s (version %d) not found", curr.StringLE(), version)
} }
history := c.Metadata().UpdateHistory history := c.Metadata().UpdateHistory
if len(history) == 0 { if len(history) == 0 {

View file

@ -200,7 +200,7 @@ func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stac
gas := ic.Chain.GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes)) gas := ic.Chain.GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes))
if isDeploy { if isDeploy {
fee := m.GetMinimumDeploymentFee(ic.DAO) fee := m.minimumDeploymentFee(ic.DAO)
if fee > gas { if fee > gas {
gas = fee gas = fee
} }
@ -400,11 +400,11 @@ func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error {
} }
func (m *Management) getMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (m *Management) getMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(m.GetMinimumDeploymentFee(ic.DAO))) return stackitem.NewBigInteger(big.NewInt(m.minimumDeploymentFee(ic.DAO)))
} }
// GetMinimumDeploymentFee returns the minimum required fee for contract deploy. // minimumDeploymentFee returns the minimum required fee for contract deploy.
func (m *Management) GetMinimumDeploymentFee(dao *dao.Simple) int64 { func (m *Management) minimumDeploymentFee(dao *dao.Simple) int64 {
return getIntWithKey(m.ID, dao, keyMinimumDeploymentFee) return getIntWithKey(m.ID, dao, keyMinimumDeploymentFee)
} }

View file

@ -90,7 +90,7 @@ func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok
func checkNodeRoles(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, index uint32, res keys.PublicKeys) { func checkNodeRoles(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, index uint32, res keys.PublicKeys) {
if ok { if ok {
designateInvoker.InvokeAndCheck(t, func(t *testing.T, stack []stackitem.Item) { designateInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack)) require.Equal(t, 1, len(stack))
arr := stack[0].Value().([]stackitem.Item) arr := stack[0].Value().([]stackitem.Item)
require.Equal(t, len(res), len(arr)) require.Equal(t, len(res), len(arr))

View file

@ -82,7 +82,7 @@ func TestLedger_GetTransactionFromBlock(t *testing.T) {
ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block. ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block.
b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight())) b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight()))
check := func(t *testing.T, stack []stackitem.Item) { check := func(t testing.TB, stack []stackitem.Item) {
require.Equal(t, 1, len(stack)) require.Equal(t, 1, len(stack))
actual, ok := stack[0].Value().([]stackitem.Item) actual, ok := stack[0].Value().([]stackitem.Item)
require.True(t, ok) require.True(t, ok)

View file

@ -3,17 +3,14 @@ package native_test
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os"
"path/filepath"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/core/chaindump" "github.com/nspcc-dev/neo-go/pkg/core/chaindump"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
@ -23,20 +20,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var (
helper1ContractNEFPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper1.nef")
helper1ContractManifestPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper1.manifest.json")
helper2ContractNEFPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper2.nef")
helper2ContractManifestPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper2.manifest.json")
)
func newManagementClient(t *testing.T) *neotest.ContractInvoker { func newManagementClient(t *testing.T) *neotest.ContractInvoker {
return newNativeClient(t, nativenames.Management) return newNativeClient(t, nativenames.Management)
} }
@ -45,62 +34,11 @@ func TestManagement_MinimumDeploymentFee(t *testing.T) {
testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0) testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0)
} }
// getTestContractState returns 2 contracts second of which is allowed to call the first.
func getTestContractState(t *testing.T, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) {
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
neBytes, err := os.ReadFile(helper1ContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := os.ReadFile(helper1ContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
cs1 := &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Manifest: *m,
ID: id1,
},
}
neBytes, err = os.ReadFile(helper2ContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound))
ne, err = nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err = os.ReadFile(helper2ContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound))
m = &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
// Retrieve hash of the first contract from the permissions of the second contract.
require.Equal(t, 1, len(m.Permissions))
require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type)
cs1.Hash = m.Permissions[0].Contract.Hash()
cs2 := &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Manifest: *m,
ID: id2,
Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name),
},
}
return cs1, cs2
}
func TestManagement_ContractDeploy(t *testing.T) { func TestManagement_ContractDeploy(t *testing.T) {
c := newManagementClient(t) c := newManagementClient(t)
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.Committee.ScriptHash()) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.Committee.ScriptHash())
manifestBytes, err := json.Marshal(cs1.Manifest) manifestBytes, err := json.Marshal(cs1.Manifest)
require.NoError(t, err) require.NoError(t, err)
nefBytes, err := cs1.NEF.Bytes() nefBytes, err := cs1.NEF.Bytes()
@ -301,7 +239,7 @@ func TestManagement_StartFromHeight(t *testing.T) {
c := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management)) c := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management))
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
manifestBytes, err := json.Marshal(cs1.Manifest) manifestBytes, err := json.Marshal(cs1.Manifest)
require.NoError(t, err) require.NoError(t, err)
nefBytes, err := cs1.NEF.Bytes() nefBytes, err := cs1.NEF.Bytes()
@ -329,7 +267,7 @@ func TestManagement_DeployManifestOverflow(t *testing.T) {
c := newManagementClient(t) c := newManagementClient(t)
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
manif1, err := json.Marshal(cs1.Manifest) manif1, err := json.Marshal(cs1.Manifest)
require.NoError(t, err) require.NoError(t, err)
nef1, err := nef.NewFile(cs1.NEF.Script) nef1, err := nef.NewFile(cs1.NEF.Script)
@ -359,7 +297,7 @@ func TestManagement_ContractDeployAndUpdateWithParameter(t *testing.T) {
c := newManagementClient(t) c := newManagementClient(t)
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
cs1.ID = 1 cs1.ID = 1
cs1.Hash = state.CreateContractHash(c.CommitteeHash, cs1.NEF.Checksum, cs1.Manifest.Name) cs1.Hash = state.CreateContractHash(c.CommitteeHash, cs1.NEF.Checksum, cs1.Manifest.Name)
@ -400,7 +338,7 @@ func TestManagement_ContractUpdate(t *testing.T) {
c := newManagementClient(t) c := newManagementClient(t)
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
// Allow calling management contract. // Allow calling management contract.
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
manifestBytes, err := json.Marshal(cs1.Manifest) manifestBytes, err := json.Marshal(cs1.Manifest)
@ -535,7 +473,7 @@ func TestManagement_GetContract(t *testing.T) {
c := newManagementClient(t) c := newManagementClient(t)
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
manifestBytes, err := json.Marshal(cs1.Manifest) manifestBytes, err := json.Marshal(cs1.Manifest)
require.NoError(t, err) require.NoError(t, err)
nefBytes, err := cs1.NEF.Bytes() nefBytes, err := cs1.NEF.Bytes()
@ -560,7 +498,7 @@ func TestManagement_ContractDestroy(t *testing.T) {
c := newManagementClient(t) c := newManagementClient(t)
managementInvoker := c.WithSigners(c.Committee) managementInvoker := c.WithSigners(c.Committee)
cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash)
// Allow calling management contract. // Allow calling management contract.
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
manifestBytes, err := json.Marshal(cs1.Manifest) manifestBytes, err := json.Marshal(cs1.Manifest)

View file

@ -7,6 +7,7 @@ import (
"sort" "sort"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/pkg/core/native" "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/nativenames"
@ -276,7 +277,7 @@ func TestNEO_TransferOnPayment(t *testing.T) {
e := neoValidatorsInvoker.Executor e := neoValidatorsInvoker.Executor
managementValidatorsInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) managementValidatorsInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
cs, _ := getTestContractState(t, 1, 2, e.CommitteeHash) cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, e.CommitteeHash)
cs.Hash = state.CreateContractHash(e.Validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash cs.Hash = state.CreateContractHash(e.Validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash
manifB, err := json.Marshal(cs.Manifest) manifB, err := json.Marshal(cs.Manifest)
require.NoError(t, err) require.NoError(t, err)

View file

@ -2,32 +2,28 @@ package native_test
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"math" "math"
"math/big" "math/big"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "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"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts")
func newOracleClient(t *testing.T) *neotest.ContractInvoker { func newOracleClient(t *testing.T) *neotest.ContractInvoker {
return newNativeClient(t, nativenames.Oracle) return newNativeClient(t, nativenames.Oracle)
} }
@ -36,36 +32,6 @@ func TestGetSetPrice(t *testing.T) {
testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64) testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64)
} }
// getOracleContractState reads pre-compiled oracle contract generated by
// TestGenerateOracleContract and returns its state.
func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract {
var (
oracleContractNEFPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.nef")
oracleContractManifestPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.manifest.json")
)
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
neBytes, err := os.ReadFile(oracleContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := os.ReadFile(oracleContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
return &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
Manifest: *m,
ID: id,
},
}
}
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker, func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) { url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
var filtItem interface{} var filtItem interface{}
@ -86,7 +52,7 @@ func TestOracle_Request(t *testing.T) {
designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation)) designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas)) gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
cs := getOracleContractState(t, e.Validator.ScriptHash(), 1) cs := contracts.GetOracleContractState(t, pathToInternalContracts, e.Validator.ScriptHash(), 1)
nBytes, err := cs.NEF.Bytes() nBytes, err := cs.NEF.Bytes()
require.NoError(t, err) require.NoError(t, err)
mBytes, err := json.Marshal(cs.Manifest) mBytes, err := json.Marshal(cs.Manifest)

View file

@ -86,9 +86,7 @@ func newOracle() *Oracle {
o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)} o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)}
defer o.UpdateHash() defer o.UpdateHash()
w := io.NewBufBinWriter() o.oracleScript = CreateOracleResponseScript(o.Hash)
emit.AppCall(w.BinWriter, o.Hash, "finish", callflag.All)
o.oracleScript = w.Bytes()
desc := newDescriptor("request", smartcontract.VoidType, desc := newDescriptor("request", smartcontract.VoidType,
manifest.NewParameter("url", smartcontract.StringType), manifest.NewParameter("url", smartcontract.StringType),
@ -526,3 +524,14 @@ func (o *Oracle) updateCache(d *dao.Simple) error {
orc.AddRequests(reqs) orc.AddRequests(reqs)
return nil return nil
} }
// CreateOracleResponseScript returns script that is used to create native Oracle
// response.
func CreateOracleResponseScript(nativeOracleHash util.Uint160) []byte {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, nativeOracleHash, "finish", callflag.All)
if w.Err != nil {
panic(fmt.Errorf("failed to create Oracle response script: %w", w.Err))
}
return w.Bytes()
}

View file

@ -1,323 +1,164 @@
package core package core_test
import ( import (
"errors" "encoding/json"
"math/big" "fmt"
"strings"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type testNative struct {
meta interop.ContractMD
blocks chan uint32
}
func (tn *testNative) Initialize(_ *interop.Context) error {
return nil
}
func (tn *testNative) Metadata() *interop.ContractMD {
return &tn.meta
}
func (tn *testNative) OnPersist(ic *interop.Context) error {
select {
case tn.blocks <- ic.Block.Index:
return nil
default:
return errors.New("can't send index")
}
}
func (tn *testNative) PostPersist(ic *interop.Context) error {
return nil
}
var _ interop.Contract = (*testNative)(nil)
// registerNative registers native contract in the blockchain.
func (bc *Blockchain) registerNative(c interop.Contract) {
bc.contracts.Contracts = append(bc.contracts.Contracts, c)
bc.config.NativeUpdateHistories[c.Metadata().Name] = c.Metadata().UpdateHistory
}
const (
testSumCPUFee = 1 << 15 // same as contract.Call
testSumStorageFee = 200
)
func newTestNative() *testNative {
cMD := interop.NewContractMD("Test.Native.Sum", 0)
cMD.UpdateHistory = []uint32{0}
tn := &testNative{
meta: *cMD,
blocks: make(chan uint32, 1),
}
defer tn.meta.UpdateHash()
desc := &manifest.Method{
Name: "sum",
Parameters: []manifest.Parameter{
manifest.NewParameter("addend1", smartcontract.IntegerType),
manifest.NewParameter("addend2", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
Safe: true,
}
md := &interop.MethodAndPrice{
Func: tn.sum,
CPUFee: testSumCPUFee,
StorageFee: testSumStorageFee,
RequiredFlags: callflag.NoneFlag,
}
tn.meta.AddMethod(md, desc)
desc = &manifest.Method{
Name: "callOtherContractNoReturn",
Parameters: []manifest.Parameter{
manifest.NewParameter("contractHash", smartcontract.Hash160Type),
manifest.NewParameter("method", smartcontract.StringType),
manifest.NewParameter("arg", smartcontract.ArrayType),
},
ReturnType: smartcontract.VoidType,
Safe: true,
}
md = &interop.MethodAndPrice{
Func: tn.callOtherContractNoReturn,
CPUFee: testSumCPUFee,
RequiredFlags: callflag.NoneFlag}
tn.meta.AddMethod(md, desc)
desc = &manifest.Method{
Name: "callOtherContractWithReturn",
Parameters: []manifest.Parameter{
manifest.NewParameter("contractHash", smartcontract.Hash160Type),
manifest.NewParameter("method", smartcontract.StringType),
manifest.NewParameter("arg", smartcontract.ArrayType),
},
ReturnType: smartcontract.IntegerType,
}
md = &interop.MethodAndPrice{
Func: tn.callOtherContractWithReturn,
CPUFee: testSumCPUFee,
RequiredFlags: callflag.NoneFlag}
tn.meta.AddMethod(md, desc)
return tn
}
func (tn *testNative) sum(_ *interop.Context, args []stackitem.Item) stackitem.Item {
s1, err := args[0].TryInteger()
if err != nil {
panic(err)
}
s2, err := args[1].TryInteger()
if err != nil {
panic(err)
}
return stackitem.NewBigInteger(s1.Add(s1, s2))
}
func toUint160(item stackitem.Item) util.Uint160 {
bs, err := item.TryBytes()
if err != nil {
panic(err)
}
u, err := util.Uint160DecodeBytesBE(bs)
if err != nil {
panic(err)
}
return u
}
func (tn *testNative) call(ic *interop.Context, args []stackitem.Item, hasReturn bool) {
cs, err := ic.GetContract(toUint160(args[0]))
if err != nil {
panic(err)
}
bs, err := args[1].TryBytes()
if err != nil {
panic(err)
}
err = contract.CallFromNative(ic, tn.meta.Hash, cs, string(bs), args[2].Value().([]stackitem.Item), hasReturn)
if err != nil {
panic(err)
}
}
func (tn *testNative) callOtherContractNoReturn(ic *interop.Context, args []stackitem.Item) stackitem.Item {
tn.call(ic, args, false)
return stackitem.Null{}
}
func (tn *testNative) callOtherContractWithReturn(ic *interop.Context, args []stackitem.Item) stackitem.Item {
tn.call(ic, args, true)
bi := ic.VM.Estack().Pop().BigInt()
return stackitem.Make(bi.Add(bi, big.NewInt(1)))
}
func TestNativeContract_Invoke(t *testing.T) { func TestNativeContract_Invoke(t *testing.T) {
chain := newTestChain(t) const (
transferCPUFee = 1 << 17
transferStorageFee = 50
systemContractCallPrice = 1 << 15
)
bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
gasHash := e.NativeHash(t, nativenames.Gas)
tn := newTestNative() baseExecFee := bc.GetBaseExecFee()
chain.registerNative(tn) price := fee.Opcode(baseExecFee, opcode.SYSCALL, // System.Contract.Call
opcode.PUSHDATA1, // contract hash (20 byte)
opcode.PUSHDATA1, // method
opcode.PUSH15, // call flags
// `transfer` args:
opcode.PUSHDATA1, // from
opcode.PUSHDATA1, // to
opcode.PUSH1, // amount
opcode.PUSHNULL, // data
// end args
opcode.PUSH4, // amount of args
opcode.PACK, // pack args
)
price += systemContractCallPrice*baseExecFee + // System.Contract.Call price
transferCPUFee*baseExecFee + // `transfer` itself
transferStorageFee*bc.GetStoragePrice() // `transfer` storage price
err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ tx := e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil)
ContractBase: state.ContractBase{ e.SignTx(t, tx, -1, validator)
ID: 1, e.AddNewBlock(t, tx)
NEF: tn.meta.NEF, e.CheckHalt(t, tx.Hash(), stackitem.Make(true))
Hash: tn.meta.Hash,
Manifest: tn.meta.Manifest,
},
})
require.NoError(t, err)
// System.Contract.Call + "sum" itself + opcodes for pushing arguments. // Enough for Call and other opcodes, but not enough for "transfer" call.
price := int64(testSumCPUFee * chain.GetBaseExecFee() * 2) tx = e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil)
price += testSumStorageFee * chain.GetStoragePrice() e.SignTx(t, tx, price-1, validator)
price += 3 * fee.Opcode(chain.GetBaseExecFee(), opcode.PUSHINT8) e.AddNewBlock(t, tx)
price += 2 * fee.Opcode(chain.GetBaseExecFee(), opcode.SYSCALL, opcode.PUSHDATA1, opcode.PUSHINT8) e.CheckFault(t, tx.Hash(), "gas limit exceeded")
price += fee.Opcode(chain.GetBaseExecFee(), opcode.PACK)
res, err := invokeContractMethod(chain, price, tn.Metadata().Hash, "sum", int64(14), int64(28))
require.NoError(t, err)
checkResult(t, res, stackitem.Make(42))
_, err = chain.persist(false)
require.NoError(t, err)
select {
case index := <-tn.blocks:
require.Equal(t, chain.blockHeight, index)
default:
require.Fail(t, "onPersist wasn't called")
}
// Enough for Call and other opcodes, but not enough for "sum" call.
res, err = invokeContractMethod(chain, price-1, tn.Metadata().Hash, "sum", int64(14), int64(28))
require.NoError(t, err)
checkFAULTState(t, res)
} }
func TestNativeContract_InvokeInternal(t *testing.T) { func TestNativeContract_InvokeInternal(t *testing.T) {
chain := newTestChain(t) bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
tn := newTestNative() clState := bc.GetContractState(e.NativeHash(t, nativenames.CryptoLib))
chain.registerNative(tn) require.NotNil(t, clState)
md := clState.Manifest.ABI.GetMethod("ripemd160", 1)
err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ require.NotNil(t, md)
ContractBase: state.ContractBase{
ID: 1,
NEF: tn.meta.NEF,
Manifest: tn.meta.Manifest,
},
})
require.NoError(t, err)
d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions)
ic := chain.newInteropContext(trigger.Application, d, nil, nil)
sumOffset := 0
for _, md := range tn.Metadata().Methods {
if md.MD.Name == "sum" {
sumOffset = md.MD.Offset
break
}
}
t.Run("fail, bad current script hash", func(t *testing.T) { t.Run("fail, bad current script hash", func(t *testing.T) {
ic := bc.GetTestVM(trigger.Application, nil, nil)
v := ic.SpawnVM() v := ic.SpawnVM()
v.LoadScriptWithHash(tn.Metadata().NEF.Script, util.Uint160{1, 2, 3}, callflag.All) fakeH := util.Uint160{1, 2, 3}
v.Estack().PushVal(14) v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All)
v.Estack().PushVal(28) input := []byte{1, 2, 3, 4}
v.Context().Jump(sumOffset) v.Estack().PushVal(input)
v.Context().Jump(md.Offset)
// it's prohibited to call natives directly // Bad current script hash
require.Error(t, v.Run()) err := v.Run()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s (version 0) not found", fakeH.StringLE())), err.Error())
}) })
t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) { t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) {
tn.Metadata().UpdateHistory = []uint32{chain.blockHeight + 1} bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
c.NativeUpdateHistories = map[string][]uint32{
nativenames.Policy: {0},
nativenames.Neo: {0},
nativenames.Gas: {0},
nativenames.Designation: {0},
nativenames.StdLib: {0},
nativenames.Management: {0},
nativenames.Oracle: {0},
nativenames.Ledger: {0},
nativenames.CryptoLib: {1},
}
})
eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad)
ic := bcBad.GetTestVM(trigger.Application, nil, nil)
v := ic.SpawnVM() v := ic.SpawnVM()
v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All) v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
v.Estack().PushVal(14) input := []byte{1, 2, 3, 4}
v.Estack().PushVal(28) v.Estack().PushVal(input)
v.Context().Jump(sumOffset) v.Context().Jump(md.Offset)
// it's prohibited to call natives before NativeUpdateHistory[0] height // It's prohibited to call natives before NativeUpdateHistory[0] height.
require.Error(t, v.Run()) err := v.Run()
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1"))
// set the value back to 0 // Add new block => CryptoLib should be active now.
tn.Metadata().UpdateHistory = []uint32{0} eBad.AddNewBlock(t)
ic = bcBad.GetTestVM(trigger.Application, nil, nil)
v = ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
v.Estack().PushVal(input)
v.Context().Jump(md.Offset)
require.NoError(t, v.Run())
value := v.Estack().Pop().Bytes()
require.Equal(t, hash.RipeMD160(input).BytesBE(), value)
}) })
t.Run("success", func(t *testing.T) { t.Run("success", func(t *testing.T) {
ic := bc.GetTestVM(trigger.Application, nil, nil)
v := ic.SpawnVM() v := ic.SpawnVM()
v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All) v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All)
v.Estack().PushVal(14) input := []byte{1, 2, 3, 4}
v.Estack().PushVal(28) v.Estack().PushVal(input)
v.Context().Jump(sumOffset) v.Context().Jump(md.Offset)
require.NoError(t, v.Run()) require.NoError(t, v.Run())
value := v.Estack().Pop().BigInt() value := v.Estack().Pop().Bytes()
require.Equal(t, int64(42), value.Int64()) require.Equal(t, hash.RipeMD160(input).BytesBE(), value)
}) })
} }
func TestNativeContract_InvokeOtherContract(t *testing.T) { func TestNativeContract_InvokeOtherContract(t *testing.T) {
chain := newTestChain(t) bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
gasInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
tn := newTestNative() cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, validator.ScriptHash())
chain.registerNative(tn) cs.Hash = state.CreateContractHash(validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash
manifB, err := json.Marshal(cs.Manifest)
err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{
ContractBase: state.ContractBase{
ID: 1,
Hash: tn.meta.Hash,
NEF: tn.meta.NEF,
Manifest: tn.meta.Manifest,
},
})
require.NoError(t, err) require.NoError(t, err)
nefB, err := cs.NEF.Bytes()
require.NoError(t, err)
si, err := cs.ToStackItem()
require.NoError(t, err)
managementInvoker.Invoke(t, si, "deploy", nefB, manifB)
var drainTN = func(t *testing.T) {
select {
case <-tn.blocks:
default:
require.Fail(t, "testNative didn't send us block")
}
}
cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs))
baseFee := chain.GetBaseExecFee()
t.Run("non-native, no return", func(t *testing.T) { t.Run("non-native, no return", func(t *testing.T) {
res, err := invokeContractMethod(chain, testSumCPUFee*baseFee*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{}) // `onNEP17Payment` will be invoked on test contract from GAS contract.
require.NoError(t, err) gasInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), cs.Hash, 1, nil)
drainTN(t)
require.Equal(t, vm.HaltState, res.VMState, res.FaultException)
checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty
})
t.Run("non-native, with return", func(t *testing.T) {
res, err := invokeContractMethod(chain, testSumCPUFee*baseFee*4+10000, tn.Metadata().Hash,
"callOtherContractWithReturn", cs.Hash, "ret7", []interface{}{})
require.NoError(t, err)
drainTN(t)
checkResult(t, res, stackitem.Make(8))
}) })
} }

View file

@ -10,69 +10,11 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r noderoles.Role, nodes keys.PublicKeys) {
w := io.NewBufBinWriter()
for _, pub := range nodes {
emit.Bytes(w.BinWriter, pub.Bytes())
}
emit.Int(w.BinWriter, int64(len(nodes)))
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Int(w.BinWriter, int64(r))
emit.Int(w.BinWriter, 2)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.AppCallNoArgs(w.BinWriter, bc.contracts.Designate.Hash, "designateAsRole", callflag.All)
require.NoError(t, w.Err)
tx := transaction.New(w.Bytes(), 0)
tx.NetworkFee = 10_000_000
tx.SystemFee = 10_000_000
tx.ValidUntilBlock = 100
tx.Signers = []transaction.Signer{
{
Account: testchain.MultisigScriptHash(),
Scopes: transaction.None,
},
{
Account: testchain.CommitteeScriptHash(),
Scopes: transaction.CalledByEntry,
},
}
require.NoError(t, testchain.SignTx(bc, tx))
tx.Scripts = append(tx.Scripts, transaction.Witness{
InvocationScript: testchain.SignCommittee(tx),
VerificationScript: testchain.CommitteeVerificationScript(),
})
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
if ok {
require.Equal(t, vm.HaltState, aer[0].VMState)
require.Equal(t, 1, len(aer[0].Events))
ev := aer[0].Events[0]
require.Equal(t, bc.contracts.Designate.Hash, ev.ScriptHash)
require.Equal(t, native.DesignationEventName, ev.Name)
require.Equal(t, []stackitem.Item{
stackitem.Make(int64(r)),
stackitem.Make(bc.BlockHeight()),
}, ev.Item.Value().([]stackitem.Item))
} else {
require.Equal(t, vm.FaultState, aer[0].VMState)
require.Equal(t, 0, len(aer[0].Events))
}
}
func TestDesignate_DesignateAsRole(t *testing.T) { func TestDesignate_DesignateAsRole(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)

View file

@ -1,39 +1,33 @@
package core package core_test
import ( import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type memoryStore struct {
*storage.MemoryStore
}
func (memoryStore) Close() error { return nil }
func TestMinimumDeploymentFee(t *testing.T) {
chain := newTestChain(t)
t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao)
require.Equal(t, 10_00000000, int(n))
})
}
func TestManagement_GetNEP17Contracts(t *testing.T) { func TestManagement_GetNEP17Contracts(t *testing.T) {
t.Run("empty chain", func(t *testing.T) { t.Run("empty chain", func(t *testing.T) {
chain := newTestChain(t) bc, validators, committee := chain.NewMulti(t)
require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash}, chain.contracts.Management.GetNEP17Contracts()) e := neotest.NewExecutor(t, bc, validators, committee)
require.ElementsMatch(t, []util.Uint160{e.NativeHash(t, nativenames.Neo),
e.NativeHash(t, nativenames.Gas)}, bc.GetNEP17Contracts())
}) })
t.Run("test chain", func(t *testing.T) { t.Run("basic chain", func(t *testing.T) {
chain := newTestChain(t) bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
initBasicChain(t, chain) c.P2PSigExtensions = true // `initBasicChain` requires Notary enabled
rublesHash, err := chain.GetContractScriptHash(1) })
require.NoError(t, err) e := neotest.NewExecutor(t, bc, validators, committee)
require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash, rublesHash}, chain.contracts.Management.GetNEP17Contracts()) initBasicChain(t, e)
require.ElementsMatch(t, []util.Uint160{e.NativeHash(t, nativenames.Neo),
e.NativeHash(t, nativenames.Gas), e.ContractHash(t, 1)}, bc.GetNEP17Contracts())
}) })
} }

View file

@ -1,20 +1,17 @@
package core package core_test
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"path/filepath"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -40,96 +37,88 @@ func BenchmarkNEO_GetGASPerVote(t *testing.B) {
} }
} }
func newLevelDBForTesting(t testing.TB) storage.Store {
dbPath := t.TempDir()
dbOptions := storage.LevelDBOptions{
DataDirectoryPath: dbPath,
}
newLevelStore, err := storage.NewLevelDBStore(dbOptions)
require.Nil(t, err, "NewLevelDBStore error")
return newLevelStore
}
func newBoltStoreForTesting(t testing.TB) storage.Store {
d := t.TempDir()
dbPath := filepath.Join(d, "test_bolt_db")
boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath})
require.NoError(t, err)
return boltDBStore
}
func benchmarkGasPerVote(t *testing.B, ps storage.Store, nRewardRecords int, rewardDistance int) { func benchmarkGasPerVote(t *testing.B, ps storage.Store, nRewardRecords int, rewardDistance int) {
bc := newTestChainWithCustomCfgAndStore(t, ps, nil) bc, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, ps, true)
cfg := bc.GetConfig()
neo := bc.contracts.NEO e := neotest.NewExecutor(t, bc, validators, committee)
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) neoHash := e.NativeHash(t, nativenames.Neo)
ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx) gasHash := e.NativeHash(t, nativenames.Gas)
ic.SpawnVM() neoSuperInvoker := e.NewInvoker(neoHash, validators, committee)
ic.Block = bc.newBlock(tx) neoValidatorsInvoker := e.ValidatorInvoker(neoHash)
gasValidatorsInvoker := e.ValidatorInvoker(gasHash)
advanceChain := func(t *testing.B, count int) {
for i := 0; i < count; i++ {
require.NoError(t, bc.AddBlock(bc.newBlock()))
ic.Block.Index++
}
}
// Vote for new committee. // Vote for new committee.
sz := testchain.CommitteeSize() sz := len(cfg.StandbyCommittee)
accs := make([]*wallet.Account, sz) voters := make([]*wallet.Account, sz)
candidates := make(keys.PublicKeys, sz) candidates := make(keys.PublicKeys, sz)
txs := make([]*transaction.Transaction, 0, len(accs)) txs := make([]*transaction.Transaction, 0, len(voters)*3)
for i := 0; i < sz; i++ { for i := 0; i < sz; i++ {
priv, err := keys.NewPrivateKey() priv, err := keys.NewPrivateKey()
require.NoError(t, err) require.NoError(t, err)
candidates[i] = priv.PublicKey() candidates[i] = priv.PublicKey()
accs[i], err = wallet.NewAccount() voters[i], err = wallet.NewAccount()
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i])) registerTx := neoSuperInvoker.PrepareInvoke(t, "registerCandidate", candidates[i].Bytes())
txs = append(txs, registerTx)
to := accs[i].Contract.ScriptHash() to := voters[i].Contract.ScriptHash()
w := io.NewBufBinWriter() transferNeoTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, big.NewInt(int64(sz-i)*1000000).Int64(), nil)
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All, txs = append(txs, transferNeoTx)
neoOwner.BytesBE(), to.BytesBE(),
big.NewInt(int64(sz-i)*1000000).Int64(), nil) transferGasTx := gasValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, int64(1_000_000_000), nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT) txs = append(txs, transferGasTx)
emit.AppCall(w.BinWriter, bc.contracts.GAS.Hash, "transfer", callflag.All,
neoOwner.BytesBE(), to.BytesBE(),
int64(1_000_000_000), nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
require.NoError(t, w.Err)
tx := transaction.New(w.Bytes(), 1000_000_000)
tx.ValidUntilBlock = bc.BlockHeight() + 1
setSigner(tx, testchain.MultisigScriptHash())
require.NoError(t, testchain.SignTx(bc, tx))
txs = append(txs, tx)
} }
require.NoError(t, bc.AddBlock(bc.newBlock(txs...))) e.AddNewBlock(t, txs...)
for _, tx := range txs { for _, tx := range txs {
checkTxHalt(t, bc, tx.Hash()) e.CheckHalt(t, tx.Hash())
} }
voteTxs := make([]*transaction.Transaction, 0, sz)
for i := 0; i < sz; i++ { for i := 0; i < sz; i++ {
priv := accs[i].PrivateKey() priv := voters[i].PrivateKey()
h := priv.GetScriptHash() h := priv.GetScriptHash()
setSigner(tx, h) voteTx := e.NewTx(t, []neotest.Signer{neotest.NewSingleSigner(voters[i])}, neoHash, "vote", h, candidates[i].Bytes())
ic.VM.Load(priv.PublicKey().GetVerificationScript()) voteTxs = append(voteTxs, voteTx)
require.NoError(t, neo.VoteInternal(ic, h, candidates[i])) }
e.AddNewBlock(t, voteTxs...)
for _, tx := range voteTxs {
e.CheckHalt(t, tx.Hash())
} }
_, err := ic.DAO.Persist()
require.NoError(t, err)
// Collect set of nRewardRecords reward records for each voter. // Collect set of nRewardRecords reward records for each voter.
advanceChain(t, nRewardRecords*testchain.CommitteeSize()) e.GenerateNewBlocks(t, len(cfg.StandbyCommittee))
// Transfer some more NEO to first voter to update his balance height. // Transfer some more NEO to first voter to update his balance height.
to := accs[0].Contract.ScriptHash() to := voters[0].Contract.ScriptHash()
w := io.NewBufBinWriter() neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), to, int64(1), nil)
emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All,
neoOwner.BytesBE(), to.BytesBE(), int64(1), nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
require.NoError(t, w.Err)
tx = transaction.New(w.Bytes(), 1000_000_000)
tx.ValidUntilBlock = bc.BlockHeight() + 1
setSigner(tx, testchain.MultisigScriptHash())
require.NoError(t, testchain.SignTx(bc, tx))
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
// Advance chain one more time to avoid same start/end rewarding bounds. // Advance chain one more time to avoid same start/end rewarding bounds.
advanceChain(t, rewardDistance) e.GenerateNewBlocks(t, rewardDistance)
end := bc.BlockHeight() end := bc.BlockHeight()
t.ResetTimer() t.ResetTimer()
t.ReportAllocs() t.ReportAllocs()
t.StartTimer() t.StartTimer()
for i := 0; i < t.N; i++ { for i := 0; i < t.N; i++ {
_, err := neo.CalculateBonus(ic.DAO, to, end) _, err := bc.CalculateClaimable(to, end)
require.NoError(t, err) require.NoError(t, err)
} }
t.StopTimer() t.StopTimer()

View file

@ -1,54 +1,67 @@
package core package core_test
import ( import (
"fmt"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/native" "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/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func transferFundsToCommittee(t *testing.T, chain *Blockchain) { func TestPolicy_FeePerByte(t *testing.T) {
transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(), bc, _, _ := chain.NewMulti(t)
chain.contracts.GAS.Hash, 1000_00000000)
}
func TestFeePerByte(t *testing.T) {
chain := newTestChain(t)
t.Run("get, internal method", func(t *testing.T) { t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) n := bc.FeePerByte()
require.Equal(t, 1000, int(n)) require.Equal(t, 1000, int(n))
}) })
} }
func TestExecFeeFactor(t *testing.T) { func TestPolicy_ExecFeeFactor(t *testing.T) {
chain := newTestChain(t) bc, _, _ := chain.NewMulti(t)
t.Run("get, internal method", func(t *testing.T) { t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao) n := bc.GetBaseExecFee()
require.EqualValues(t, interop.DefaultBaseExecFee, n) require.EqualValues(t, interop.DefaultBaseExecFee, n)
}) })
} }
func TestStoragePrice(t *testing.T) { func TestPolicy_StoragePrice(t *testing.T) {
chain := newTestChain(t) bc, validators, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validators, committee)
t.Run("get, internal method", func(t *testing.T) { t.Run("get, internal method", func(t *testing.T) {
n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao) e.AddNewBlock(t) // avoid default value got from Blockchain.
n := bc.GetStoragePrice()
require.Equal(t, int64(native.DefaultStoragePrice), n) require.Equal(t, int64(native.DefaultStoragePrice), n)
}) })
} }
func TestBlockedAccounts(t *testing.T) { func TestPolicy_BlockedAccounts(t *testing.T) {
chain := newTestChain(t) bc, validators, committee := chain.NewMulti(t)
transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(), e := neotest.NewExecutor(t, bc, validators, committee)
chain.contracts.GAS.Hash, 100_00000000) policyHash := e.NativeHash(t, nativenames.Policy)
policySuperInvoker := e.NewInvoker(policyHash, validators, committee)
unlucky := e.NewAccount(t, 5_0000_0000)
policyUnluckyInvoker := e.NewInvoker(policyHash, unlucky)
// Block unlucky account.
policySuperInvoker.Invoke(t, true, "blockAccount", unlucky.ScriptHash())
// Transaction from blocked account shouldn't be accepted.
t.Run("isBlocked, internal method", func(t *testing.T) { t.Run("isBlocked, internal method", func(t *testing.T) {
isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160()) tx := policyUnluckyInvoker.PrepareInvoke(t, "getStoragePrice")
require.Equal(t, false, isBlocked) b := e.NewUnsignedBlock(t, tx)
e.SignBlock(b)
expectedErr := fmt.Sprintf("transaction %s failed to verify: not allowed by policy: account %s is blocked", tx.Hash().StringLE(), unlucky.ScriptHash().StringLE())
err := e.Chain.AddBlock(b)
require.Error(t, err)
require.Equal(t, expectedErr, err.Error())
}) })
} }

View file

@ -1,8 +1,9 @@
package core package core_test
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"math/rand" "math/rand"
"path" "path"
"path/filepath" "path/filepath"
@ -10,29 +11,32 @@ import (
"testing" "testing"
"time" "time"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/mempool"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/network"
"github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/services/notary" "github.com/nspcc-dev/neo-go/pkg/services/notary"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
var notaryModulePath = filepath.Join("..", "services", "notary") func getTestNotary(t *testing.T, bc *core.Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) {
func getTestNotary(t *testing.T, bc *Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) {
mainCfg := config.P2PNotary{ mainCfg := config.P2PNotary{
Enabled: true, Enabled: true,
UnlockWallet: config.Wallet{ UnlockWallet: config.Wallet{
@ -46,7 +50,7 @@ func getTestNotary(t *testing.T, bc *Blockchain, walletPath, pass string, onTx f
Log: zaptest.NewLogger(t), Log: zaptest.NewLogger(t),
} }
mp := mempool.New(10, 1, true) mp := mempool.New(10, 1, true)
ntr, err := notary.NewNotary(cfg, testchain.Network(), mp, onTx) ntr, err := notary.NewNotary(cfg, netmode.UnitTestNet, mp, onTx)
require.NoError(t, err) require.NoError(t, err)
w, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, walletPath)) w, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, walletPath))
@ -68,7 +72,14 @@ func dupNotaryRequest(t *testing.T, p *payload.P2PNotaryRequest) *payload.P2PNot
} }
func TestNotary(t *testing.T) { func TestNotary(t *testing.T) {
bc := newTestChain(t) bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
c.P2PSigExtensions = true
})
e := neotest.NewExecutor(t, bc, validators, committee)
notaryHash := e.NativeHash(t, nativenames.Notary)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validators, committee)
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
var ( var (
nonce uint32 nonce uint32
nvbDiffFallback uint32 = 20 nvbDiffFallback uint32 = 20
@ -145,8 +156,9 @@ func TestNotary(t *testing.T) {
mp1.StopSubscriptions() mp1.StopSubscriptions()
}) })
notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()} notaryNodes := []interface{}{acc1.PrivateKey().PublicKey().Bytes(), acc2.PrivateKey().PublicKey().Bytes()}
bc.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodes) designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.P2PNotary), notaryNodes)
type requester struct { type requester struct {
accounts []*wallet.Account accounts []*wallet.Account
@ -193,7 +205,7 @@ func TestNotary(t *testing.T) {
VerificationScript: []byte{}, VerificationScript: []byte{},
}, },
} }
err = requester.SignTx(testchain.Network(), fallback) err = requester.SignTx(netmode.UnitTestNet, fallback)
require.NoError(t, err) require.NoError(t, err)
return fallback return fallback
} }
@ -251,7 +263,7 @@ func TestNotary(t *testing.T) {
for j := range main.Scripts { for j := range main.Scripts {
main.Scripts[j].VerificationScript = verificationScripts[j] main.Scripts[j].VerificationScript = verificationScripts[j]
if i == j { if i == j {
main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(testchain.Network()), main)...) main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), main)...)
} }
} }
main.Scripts = append(main.Scripts, transaction.Witness{}) // empty Notary witness main.Scripts = append(main.Scripts, transaction.Witness{}) // empty Notary witness
@ -296,12 +308,11 @@ func TestNotary(t *testing.T) {
require.Equal(t, io.GetVarSize(completedTx), completedTx.Size()) require.Equal(t, io.GetVarSize(completedTx), completedTx.Size())
for i := 0; i < len(completedTx.Scripts)-1; i++ { for i := 0; i < len(completedTx.Scripts)-1; i++ {
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx) _, err := bc.VerifyWitness(completedTx.Signers[i].Account, completedTx, &completedTx.Scripts[i], -1)
_, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], interopCtx, -1)
require.NoError(t, err) require.NoError(t, err)
} }
require.Equal(t, transaction.Witness{ require.Equal(t, transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), requests[0].MainTransaction)...), InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), requests[0].MainTransaction)...),
VerificationScript: []byte{}, VerificationScript: []byte{},
}, completedTx.Scripts[len(completedTx.Scripts)-1]) }, completedTx.Scripts[len(completedTx.Scripts)-1])
} else { } else {
@ -316,15 +327,14 @@ func TestNotary(t *testing.T) {
require.Equal(t, 2, len(completedTx.Signers)) require.Equal(t, 2, len(completedTx.Signers))
require.Equal(t, 2, len(completedTx.Scripts)) require.Equal(t, 2, len(completedTx.Scripts))
require.Equal(t, transaction.Witness{ require.Equal(t, transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), req.FallbackTransaction)...), InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), req.FallbackTransaction)...),
VerificationScript: []byte{}, VerificationScript: []byte{},
}, completedTx.Scripts[0]) }, completedTx.Scripts[0])
// check that tx size was updated // check that tx size was updated
require.Equal(t, io.GetVarSize(completedTx), completedTx.Size()) require.Equal(t, io.GetVarSize(completedTx), completedTx.Size())
interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx) _, err := bc.VerifyWitness(completedTx.Signers[1].Account, completedTx, &completedTx.Scripts[1], -1)
_, err := bc.verifyHashAgainstScript(completedTx.Signers[1].Account, &completedTx.Scripts[1], interopCtx, -1)
require.NoError(t, err) require.NoError(t, err)
} else { } else {
completedTx := getCompletedTx(t, false, req.FallbackTransaction.Hash()) completedTx := getCompletedTx(t, false, req.FallbackTransaction.Hash())
@ -483,7 +493,8 @@ func TestNotary(t *testing.T) {
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock()))
e.AddNewBlock(t)
checkMainTx(t, requesters, r, 1, false) checkMainTx(t, requesters, r, 1, false)
checkFallbackTxs(t, r, false) checkFallbackTxs(t, r, false)
// set account back for the next tests // set account back for the next tests
@ -494,11 +505,11 @@ func TestNotary(t *testing.T) {
requests, requesters := checkCompleteStandardRequest(t, 3, false) requests, requesters := checkCompleteStandardRequest(t, 3, false)
// check PostPersist with finalisation error // check PostPersist with finalisation error
setFinalizeWithError(true) setFinalizeWithError(true)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
// check PostPersist without finalisation error // check PostPersist without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), true) checkMainTx(t, requesters, requests, len(requests), true)
// PostPersist: complete main transaction, multisignature account // PostPersist: complete main transaction, multisignature account
@ -507,12 +518,12 @@ func TestNotary(t *testing.T) {
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist with finalisation error // check PostPersist with finalisation error
setFinalizeWithError(true) setFinalizeWithError(true)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist without finalisation error // check PostPersist without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), true) checkMainTx(t, requesters, requests, len(requests), true)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
@ -521,15 +532,15 @@ func TestNotary(t *testing.T) {
requests, requesters = checkCompleteStandardRequest(t, 3, false) requests, requesters = checkCompleteStandardRequest(t, 3, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid // make fallbacks valid
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
// check PostPersist for valid fallbacks with finalisation error // check PostPersist for valid fallbacks with finalisation error
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist for valid fallbacks without finalisation error // check PostPersist for valid fallbacks without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, true) checkFallbackTxs(t, requests, true)
@ -540,15 +551,15 @@ func TestNotary(t *testing.T) {
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false) requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid // make fallbacks valid
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
// check PostPersist for valid fallbacks with finalisation error // check PostPersist for valid fallbacks with finalisation error
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// check PostPersist for valid fallbacks without finalisation error // check PostPersist for valid fallbacks without finalisation error
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:nSigs], true) checkFallbackTxs(t, requests[:nSigs], true)
// the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent // the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent
@ -559,14 +570,14 @@ func TestNotary(t *testing.T) {
requests, requesters = checkCompleteStandardRequest(t, 5, false) requests, requesters = checkCompleteStandardRequest(t, 5, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid // make fallbacks valid
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
// some of fallbacks should fail finalisation // some of fallbacks should fail finalisation
unluckies = []*payload.P2PNotaryRequest{requests[0], requests[4]} unluckies = []*payload.P2PNotaryRequest{requests[0], requests[4]}
lucky := requests[1:4] lucky := requests[1:4]
setChoosy(true) setChoosy(true)
// check PostPersist for lucky fallbacks // check PostPersist for lucky fallbacks
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, lucky, true) checkFallbackTxs(t, lucky, true)
checkFallbackTxs(t, unluckies, false) checkFallbackTxs(t, unluckies, false)
@ -574,7 +585,7 @@ func TestNotary(t *testing.T) {
setChoosy(false) setChoosy(false)
setFinalizeWithError(false) setFinalizeWithError(false)
// check PostPersist for unlucky fallbacks // check PostPersist for unlucky fallbacks
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, lucky, true) checkFallbackTxs(t, lucky, true)
checkFallbackTxs(t, unluckies, true) checkFallbackTxs(t, unluckies, true)
@ -585,19 +596,19 @@ func TestNotary(t *testing.T) {
requests, requesters = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5) requests, requesters = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// generate blocks to reach the most earlier fallback's NVB // generate blocks to reach the most earlier fallback's NVB
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
// check PostPersist for valid fallbacks without finalisation error // check PostPersist for valid fallbacks without finalisation error
// Add block before allowing tx to finalize to exclude race condition when // Add block before allowing tx to finalize to exclude race condition when
// main transaction is finalized between `finalizeWithError` restore and adding new block. // main transaction is finalized between `finalizeWithError` restore and adding new block.
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
mtx.RLock() mtx.RLock()
start := len(completedTxes) start := len(completedTxes)
mtx.RUnlock() mtx.RUnlock()
setFinalizeWithError(false) setFinalizeWithError(false)
for i := range requests { for i := range requests {
if i != 0 { if i != 0 {
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
} }
require.Eventually(t, func() bool { require.Eventually(t, func() bool {
mtx.RLock() mtx.RLock()
@ -615,13 +626,13 @@ func TestNotary(t *testing.T) {
requests, requesters = checkCompleteStandardRequest(t, 4, false) requests, requesters = checkCompleteStandardRequest(t, 4, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove one fallback // make fallbacks valid and remove one fallback
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()}) ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()})
ntr1.OnRequestRemoval(requests[3]) ntr1.OnRequestRemoval(requests[3])
// non of the fallbacks should be completed // non of the fallbacks should be completed
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// set account back for the next tests // set account back for the next tests
@ -633,13 +644,13 @@ func TestNotary(t *testing.T) {
requests, requesters = checkCompleteStandardRequest(t, 4, false) requests, requesters = checkCompleteStandardRequest(t, 4, false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove one fallback // make fallbacks valid and remove one fallback
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
unlucky := requests[3] unlucky := requests[3]
ntr1.OnRequestRemoval(unlucky) ntr1.OnRequestRemoval(unlucky)
// rest of the fallbacks should be completed // rest of the fallbacks should be completed
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:3], true) checkFallbackTxs(t, requests[:3], true)
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()]) require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
@ -648,20 +659,20 @@ func TestNotary(t *testing.T) {
setFinalizeWithError(true) setFinalizeWithError(true)
requests, requesters = checkCompleteStandardRequest(t, 4, false) requests, requesters = checkCompleteStandardRequest(t, 4, false)
// remove all fallbacks // remove all fallbacks
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
for i := range requests { for i := range requests {
ntr1.OnRequestRemoval(requests[i]) ntr1.OnRequestRemoval(requests[i])
} }
// then the whole request should be removed, i.e. there are no completed transactions // then the whole request should be removed, i.e. there are no completed transactions
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// OnRequestRemoval: signature request, remove unexisting fallback // OnRequestRemoval: signature request, remove unexisting fallback
ntr1.OnRequestRemoval(requests[0]) ntr1.OnRequestRemoval(requests[0])
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
@ -673,13 +684,13 @@ func TestNotary(t *testing.T) {
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// make fallbacks valid and remove the last fallback // make fallbacks valid and remove the last fallback
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
unlucky = requests[nSigs-1] unlucky = requests[nSigs-1]
ntr1.OnRequestRemoval(unlucky) ntr1.OnRequestRemoval(unlucky)
// then (m-1) out of n fallbacks should be completed // then (m-1) out of n fallbacks should be completed
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests[:nSigs-1], true) checkFallbackTxs(t, requests[:nSigs-1], true)
require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()]) require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()])
@ -690,20 +701,20 @@ func TestNotary(t *testing.T) {
setFinalizeWithError(true) setFinalizeWithError(true)
requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false) requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false)
// make fallbacks valid and then remove all of them // make fallbacks valid and then remove all of them
_, err = bc.genBlocks(int(nvbDiffFallback)) e.GenerateNewBlocks(t, int(nvbDiffFallback))
require.NoError(t, err) require.NoError(t, err)
for i := range requests { for i := range requests {
ntr1.OnRequestRemoval(requests[i]) ntr1.OnRequestRemoval(requests[i])
} }
// then the whole request should be removed, i.e. there are no completed transactions // then the whole request should be removed, i.e. there are no completed transactions
setFinalizeWithError(false) setFinalizeWithError(false)
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
// // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this // // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this
ntr1.OnRequestRemoval(requests[0]) ntr1.OnRequestRemoval(requests[0])
require.NoError(t, bc.AddBlock(bc.newBlock())) e.AddNewBlock(t)
checkMainTx(t, requesters, requests, len(requests), false) checkMainTx(t, requesters, requests, len(requests), false)
checkFallbackTxs(t, requests, false) checkFallbackTxs(t, requests, false)
@ -712,11 +723,11 @@ func TestNotary(t *testing.T) {
requester1, _ := wallet.NewAccount() requester1, _ := wallet.NewAccount()
requester2, _ := wallet.NewAccount() requester2, _ := wallet.NewAccount()
amount := int64(100_0000_0000) amount := int64(100_0000_0000)
feer := NewNotaryFeerStub(bc) gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester1.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight() + 50)})
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester1.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50)) e.CheckGASBalance(t, notaryHash, big.NewInt(amount))
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(amount)) gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight() + 50)})
transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50)) e.CheckGASBalance(t, notaryHash, big.NewInt(2*amount))
checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(2*amount))
// create request for 2 standard signatures => main tx should be completed after the second request is added to the pool // create request for 2 standard signatures => main tx should be completed after the second request is added to the pool
requests = createMixedRequest([]requester{ requests = createMixedRequest([]requester{
{ {
@ -728,6 +739,7 @@ func TestNotary(t *testing.T) {
typ: notary.Signature, typ: notary.Signature,
}, },
}) })
feer := network.NewNotaryFeer(bc)
require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0])) require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0]))
require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1])) require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1]))
require.Eventually(t, func() bool { require.Eventually(t, func() bool {

View file

@ -1,13 +1,13 @@
package core package core_test
import ( import (
"bytes" "bytes"
"encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
gio "io" gio "io"
"net/http" "net/http"
"os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -15,168 +15,41 @@ import (
"testing" "testing"
"time" "time"
"github.com/nspcc-dev/neo-go/internal/contracts"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "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/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/services/oracle" "github.com/nspcc-dev/neo-go/pkg/services/oracle"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/util/slice"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
var ( var oracleModulePath = filepath.Join("..", "services", "oracle")
oracleModulePath = filepath.Join("..", "services", "oracle")
oracleContractNEFPath = filepath.Join("test_data", "oracle_contract", "oracle.nef")
oracleContractManifestPath = filepath.Join("test_data", "oracle_contract", "oracle.manifest.json")
)
// TestGenerateOracleContract generates helper contract that is able to call func putOracleRequest(t *testing.T, oracleValidatorInvoker *neotest.ContractInvoker,
// native Oracle contract and has callback method. It uses test chain to define
// Oracle and StdLib native hashes and saves generated NEF and manifest to ... folder.
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
func TestGenerateOracleContract(t *testing.T) {
const saveState = false
bc := newTestChain(t)
oracleHash := bc.contracts.Oracle.Hash
stdHash := bc.contracts.Std.Hash
w := io.NewBufBinWriter()
emit.Int(w.BinWriter, 5)
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Int(w.BinWriter, int64(callflag.All))
emit.String(w.BinWriter, "request")
emit.Bytes(w.BinWriter, oracleHash.BytesBE())
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
emit.Opcodes(w.BinWriter, opcode.DROP)
emit.Opcodes(w.BinWriter, opcode.RET)
// `handle` method aborts if len(userData) == 2 and does NOT perform witness checks
// for the sake of contract code simplicity (the contract is used in multiple testchains).
offset := w.Len()
emit.Opcodes(w.BinWriter, opcode.OVER)
emit.Opcodes(w.BinWriter, opcode.SIZE)
emit.Int(w.BinWriter, 2)
emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3})
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Int(w.BinWriter, 4) // url, userData, code, result
emit.Opcodes(w.BinWriter, opcode.PACK)
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
emit.String(w.BinWriter, "lastOracleResponse")
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
emit.Opcodes(w.BinWriter, opcode.RET)
m := manifest.NewManifest("TestOracle")
m.ABI.Methods = []manifest.Method{
{
Name: "requestURL",
Offset: 0,
Parameters: []manifest.Parameter{
manifest.NewParameter("url", smartcontract.StringType),
manifest.NewParameter("filter", smartcontract.StringType),
manifest.NewParameter("callback", smartcontract.StringType),
manifest.NewParameter("userData", smartcontract.AnyType),
manifest.NewParameter("gasForResponse", smartcontract.IntegerType),
},
ReturnType: smartcontract.VoidType,
},
{
Name: "handle",
Offset: offset,
Parameters: []manifest.Parameter{
manifest.NewParameter("url", smartcontract.StringType),
manifest.NewParameter("userData", smartcontract.AnyType),
manifest.NewParameter("code", smartcontract.IntegerType),
manifest.NewParameter("result", smartcontract.ByteArrayType),
},
ReturnType: smartcontract.VoidType,
},
}
perm := manifest.NewPermission(manifest.PermissionHash, oracleHash)
perm.Methods.Add("request")
m.Permissions = append(m.Permissions, *perm)
// Generate NEF file.
script := w.Bytes()
ne, err := nef.NewFile(script)
require.NoError(t, err)
// Write NEF file.
bytes, err := ne.Bytes()
require.NoError(t, err)
if saveState {
err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write manifest file.
mData, err := json.Marshal(m)
require.NoError(t, err)
if saveState {
err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
require.False(t, saveState)
}
// getOracleContractState reads pre-compiled oracle contract generated by
// TestGenerateOracleContract and returns its state.
func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract {
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
neBytes, err := os.ReadFile(oracleContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := os.ReadFile(oracleContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
return &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
Manifest: *m,
ID: id,
},
}
}
func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain,
url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 { url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 {
var filtItem interface{} var filtItem interface{}
if filter != nil { if filter != nil {
filtItem = *filter filtItem = *filter
} }
res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL", return oracleValidatorInvoker.Invoke(t, stackitem.Null{}, "requestURL", url, filtItem, cb, userData, gas)
url, filtItem, cb, userData, gas)
require.NoError(t, err)
return res.Container
} }
func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string, returnOracleRedirectionErrOn func(address string) bool) oracle.Config { func getOracleConfig(t *testing.T, bc *core.Blockchain, w, pass string, returnOracleRedirectionErrOn func(address string) bool) oracle.Config {
return oracle.Config{ return oracle.Config{
Log: zaptest.NewLogger(t), Log: zaptest.NewLogger(t),
Network: netmode.UnitTestNet, Network: netmode.UnitTestNet,
@ -193,7 +66,7 @@ func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string, returnOracleR
} }
} }
func getTestOracle(t *testing.T, bc *Blockchain, walletPath, pass string) ( func getTestOracle(t *testing.T, bc *core.Blockchain, walletPath, pass string) (
*wallet.Account, *wallet.Account,
*oracle.Oracle, *oracle.Oracle,
map[uint64]*responseWithSig, map[uint64]*responseWithSig,
@ -217,7 +90,19 @@ func getTestOracle(t *testing.T, bc *Blockchain, walletPath, pass string) (
// Compatibility test from C# code. // Compatibility test from C# code.
// https://github.com/neo-project/neo-modules/blob/master/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs#L61 // https://github.com/neo-project/neo-modules/blob/master/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs#L61
func TestCreateResponseTx(t *testing.T) { func TestCreateResponseTx(t *testing.T) {
bc := newTestChain(t) bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0)
rawManifest, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
rawNef, err := cs.NEF.Bytes()
require.NoError(t, err)
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
cInvoker := e.ValidatorInvoker(cs.Hash)
require.Equal(t, int64(30), bc.GetBaseExecFee()) require.Equal(t, int64(30), bc.GetBaseExecFee())
require.Equal(t, int64(1000), bc.FeePerByte()) require.Equal(t, int64(1000), bc.FeePerByte())
@ -236,10 +121,10 @@ func TestCreateResponseTx(t *testing.T) {
Code: transaction.Success, Code: transaction.Success,
Result: []byte{0}, Result: []byte{0},
} }
require.NoError(t, bc.contracts.Oracle.PutRequestInternal(1, req, bc.dao)) cInvoker.Invoke(t, stackitem.Null{}, "requestURL", req.URL, *req.Filter, req.CallbackMethod, req.UserData, int64(req.GasForResponse))
orc.UpdateOracleNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()}) orc.UpdateOracleNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()})
bc.SetOracle(orc) bc.SetOracle(orc)
tx, err := orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) tx, err = orc.CreateResponseTx(int64(req.GasForResponse), 1, resp)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, 166, tx.Size()) assert.Equal(t, 166, tx.Size())
assert.Equal(t, int64(2198650), tx.NetworkFee) assert.Equal(t, int64(2198650), tx.NetworkFee)
@ -247,7 +132,7 @@ func TestCreateResponseTx(t *testing.T) {
} }
func TestOracle_InvalidWallet(t *testing.T) { func TestOracle_InvalidWallet(t *testing.T) {
bc := newTestChain(t) bc, _, _ := chain.NewMulti(t)
_, err := oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "invalid", nil)) _, err := oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "invalid", nil))
require.Error(t, err) require.Error(t, err)
@ -257,45 +142,65 @@ func TestOracle_InvalidWallet(t *testing.T) {
} }
func TestOracle(t *testing.T) { func TestOracle(t *testing.T) {
bc := newTestChain(t) bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management))
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee)
nativeOracleH := e.NativeHash(t, nativenames.Oracle)
nativeOracleID := e.NativeID(t, nativenames.Oracle)
oracleCtr := bc.contracts.Oracle
acc1, orc1, m1, ch1 := getTestOracle(t, bc, "./testdata/oracle1.json", "one") acc1, orc1, m1, ch1 := getTestOracle(t, bc, "./testdata/oracle1.json", "one")
acc2, orc2, m2, ch2 := getTestOracle(t, bc, "./testdata/oracle2.json", "two") acc2, orc2, m2, ch2 := getTestOracle(t, bc, "./testdata/oracle2.json", "two")
oracleNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()} oracleNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()}
// Must be set in native contract for tx verification. // Must be set in native contract for tx verification.
bc.setNodesByRole(t, true, noderoles.Oracle, oracleNodes) designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.Oracle), []interface{}{oracleNodes[0].Bytes(), oracleNodes[1].Bytes()})
orc1.UpdateOracleNodes(oracleNodes.Copy()) orc1.UpdateOracleNodes(oracleNodes.Copy())
orc2.UpdateOracleNodes(oracleNodes.Copy()) orc2.UpdateOracleNodes(oracleNodes.Copy())
orcNative := bc.contracts.Oracle nativeOracleState := bc.GetContractState(nativeOracleH)
md, ok := orcNative.GetMethod(manifest.MethodVerify, -1) require.NotNil(t, nativeOracleState)
require.True(t, ok) md := nativeOracleState.Manifest.ABI.GetMethod(manifest.MethodVerify, -1)
orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) require.NotNil(t, md)
orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) oracleRespScript := native.CreateOracleResponseScript(nativeOracleH)
orc1.UpdateNativeContract(nativeOracleState.NEF.Script, slice.Copy(oracleRespScript), nativeOracleH, md.Offset)
orc2.UpdateNativeContract(nativeOracleState.NEF.Script, slice.Copy(oracleRespScript), nativeOracleH, md.Offset)
cs := getOracleContractState(t, util.Uint160{}, 42) cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) rawManifest, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
rawNef, err := cs.NEF.Bytes()
require.NoError(t, err)
tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash())
cInvoker := e.ValidatorInvoker(cs.Hash)
putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.1234", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.1234", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.timeout", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.timeout", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.notfound", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.notfound", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.forbidden", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.forbidden", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://private.url", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://private.url", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.big", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.big", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.maxallowed", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.maxallowed", nil, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.maxallowed", nil, "handle", []byte{}, 100_000_000) putOracleRequest(t, cInvoker, "https://get.maxallowed", nil, "handle", []byte{}, 100_000_000)
flt := "$.Values[1]" flt := "$.Values[1]"
putOracleRequest(t, cs.Hash, bc, "https://get.filter", &flt, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.filter", &flt, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.filterinv", &flt, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.filterinv", &flt, "handle", []byte{}, 10_000_000)
putOracleRequest(t, cs.Hash, bc, "https://get.invalidcontent", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cInvoker, "https://get.invalidcontent", nil, "handle", []byte{}, 10_000_000)
checkResp := func(t *testing.T, id uint64, resp *transaction.OracleResponse) *state.OracleRequest { checkResp := func(t *testing.T, id uint64, resp *transaction.OracleResponse) *state.OracleRequest {
req, err := oracleCtr.GetRequestInternal(bc.dao, id) // Use a hack to get request from Oracle contract, because we can't use GetRequestInternal directly.
require.NoError(t, err) requestKey := make([]byte, 9)
requestKey[0] = 7 // prefixRequest from native Oracle contract
binary.BigEndian.PutUint64(requestKey[1:], id)
si := bc.GetStorageItem(nativeOracleID, requestKey)
require.NotNil(t, si)
req := new(state.OracleRequest)
require.NoError(t, stackitem.DeserializeConvertible(si, req))
reqs := map[uint64]*state.OracleRequest{id: req} reqs := map[uint64]*state.OracleRequest{id: req}
orc1.ProcessRequestsInternal(reqs) orc1.ProcessRequestsInternal(reqs)
@ -328,7 +233,7 @@ func TestOracle(t *testing.T) {
actualHash := cp.Hash() actualHash := cp.Hash()
require.Equal(t, actualHash, cachedHash, "transaction hash was changed during ") require.Equal(t, actualHash, cachedHash, "transaction hash was changed during ")
require.NoError(t, bc.verifyAndPoolTx(tx, bc.GetMemPool(), bc)) require.NoError(t, bc.PoolTx(tx))
} }
t.Run("NormalRequest", func(t *testing.T) { t.Run("NormalRequest", func(t *testing.T) {
@ -436,21 +341,34 @@ func TestOracle(t *testing.T) {
} }
func TestOracleFull(t *testing.T) { func TestOracleFull(t *testing.T) {
bc := initTestChain(t, nil, nil) bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false)
e := neotest.NewExecutor(t, bc, validator, committee)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee)
acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two") acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two")
mp := bc.GetMemPool() mp := bc.GetMemPool()
orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) }
bc.SetOracle(orc) bc.SetOracle(orc)
cs := getOracleContractState(t, util.Uint160{}, 42)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
go bc.Run() go bc.Run()
orc.Start() orc.Start()
t.Cleanup(orc.Shutdown) t.Cleanup(func() {
orc.Shutdown()
bc.Close()
})
bc.setNodesByRole(t, true, noderoles.Oracle, keys.PublicKeys{acc.PrivateKey().PublicKey()}) designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
putOracleRequest(t, cs.Hash, bc, "https://get.1234", new(string), "handle", []byte{}, 10_000_000) int64(roles.Oracle), []interface{}{acc.PrivateKey().PublicKey().Bytes()})
cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0)
e.DeployContract(t, &neotest.Contract{
Hash: cs.Hash,
NEF: &cs.NEF,
Manifest: &cs.Manifest,
}, nil)
cInvoker := e.ValidatorInvoker(cs.Hash)
putOracleRequest(t, cInvoker, "https://get.1234", new(string), "handle", []byte{}, 10_000_000)
require.Eventually(t, func() bool { return mp.Count() == 1 }, require.Eventually(t, func() bool { return mp.Count() == 1 },
time.Second*3, time.Millisecond*200) time.Second*3, time.Millisecond*200)
@ -461,17 +379,20 @@ func TestOracleFull(t *testing.T) {
} }
func TestNotYetRunningOracle(t *testing.T) { func TestNotYetRunningOracle(t *testing.T) {
bc := initTestChain(t, nil, nil) bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false)
e := neotest.NewExecutor(t, bc, validator, committee)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee)
acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two") acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two")
mp := bc.GetMemPool() mp := bc.GetMemPool()
orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) }
bc.SetOracle(orc) bc.SetOracle(orc)
cs := getOracleContractState(t, util.Uint160{}, 42)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
go bc.Run() go bc.Run()
bc.setNodesByRole(t, true, noderoles.Oracle, keys.PublicKeys{acc.PrivateKey().PublicKey()}) t.Cleanup(bc.Close)
designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.Oracle), []interface{}{acc.PrivateKey().PublicKey().Bytes()})
var req state.OracleRequest var req state.OracleRequest
var reqs = make(map[uint64]*state.OracleRequest) var reqs = make(map[uint64]*state.OracleRequest)

View file

@ -1,6 +1,7 @@
package core package core_test
import ( import (
"crypto/elliptic"
"errors" "errors"
"path/filepath" "path/filepath"
"sort" "sort"
@ -10,18 +11,25 @@ import (
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core"
"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/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
corestate "github.com/nspcc-dev/neo-go/pkg/core/stateroot"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/interop/native/roles"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/services/stateroot" "github.com/nspcc-dev/neo-go/pkg/services/stateroot"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/atomic" "go.uber.org/atomic"
@ -70,95 +78,113 @@ func newMajorityMultisigWithGAS(t *testing.T, n int) (util.Uint160, keys.PublicK
} }
func TestStateRoot(t *testing.T) { func TestStateRoot(t *testing.T) {
bc := newTestChain(t) bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee)
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
h, pubs, accs := newMajorityMultisigWithGAS(t, 2) h, pubs, accs := newMajorityMultisigWithGAS(t, 2)
bc.setNodesByRole(t, true, noderoles.StateValidator, pubs) validatorNodes := []interface{}{pubs[0].Bytes(), pubs[1].Bytes()}
designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.StateValidator), validatorNodes)
updateIndex := bc.BlockHeight() updateIndex := bc.BlockHeight()
transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000)
gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), h, 1_0000_0000, nil)
tmpDir := t.TempDir() tmpDir := t.TempDir()
w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass") w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass")
cfg := createStateRootConfig(w.Path(), "pass") cfg := createStateRootConfig(w.Path(), "pass")
srv, err := stateroot.New(cfg, bc.stateRoot, zaptest.NewLogger(t), bc, nil) srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here.
srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, nil)
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight())
r, err := bc.stateRoot.GetStateRoot(bc.BlockHeight()) r, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight())
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, r.Root, bc.stateRoot.CurrentLocalStateRoot()) require.Equal(t, r.Root, bc.GetStateModule().CurrentLocalStateRoot())
t.Run("invalid message", func(t *testing.T) { t.Run("invalid message", func(t *testing.T) {
require.Error(t, srv.OnPayload(&payload.Extensible{Data: []byte{42}})) require.Error(t, srv.OnPayload(&payload.Extensible{Data: []byte{42}}))
require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight())
}) })
t.Run("drop zero index", func(t *testing.T) { t.Run("drop zero index", func(t *testing.T) {
r, err := bc.stateRoot.GetStateRoot(0) r, err := bc.GetStateModule().GetStateRoot(0)
require.NoError(t, err) require.NoError(t, err)
data, err := testserdes.EncodeBinary(stateroot.NewMessage(stateroot.RootT, r)) data, err := testserdes.EncodeBinary(stateroot.NewMessage(stateroot.RootT, r))
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data})) require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data}))
require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight())
}) })
t.Run("invalid height", func(t *testing.T) { t.Run("invalid height", func(t *testing.T) {
r, err := bc.stateRoot.GetStateRoot(1) r, err := bc.GetStateModule().GetStateRoot(1)
require.NoError(t, err) require.NoError(t, err)
r.Index = 10 r.Index = 10
data := testSignStateRoot(t, r, pubs, accs...) data := testSignStateRoot(t, r, pubs, accs...)
require.Error(t, srv.OnPayload(&payload.Extensible{Data: data})) require.Error(t, srv.OnPayload(&payload.Extensible{Data: data}))
require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight())
}) })
t.Run("invalid signer", func(t *testing.T) { t.Run("invalid signer", func(t *testing.T) {
accInv, err := wallet.NewAccount() accInv, err := wallet.NewAccount()
require.NoError(t, err) require.NoError(t, err)
pubs := keys.PublicKeys{accInv.PrivateKey().PublicKey()} pubs := keys.PublicKeys{accInv.PrivateKey().PublicKey()}
require.NoError(t, accInv.ConvertMultisig(1, pubs)) require.NoError(t, accInv.ConvertMultisig(1, pubs))
transferTokenFromMultisigAccount(t, bc, accInv.Contract.ScriptHash(), bc.contracts.GAS.Hash, 1_0000_0000) gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), accInv.Contract.ScriptHash(), 1_0000_0000, nil)
r, err := bc.stateRoot.GetStateRoot(1) r, err := bc.GetStateModule().GetStateRoot(1)
require.NoError(t, err) require.NoError(t, err)
data := testSignStateRoot(t, r, pubs, accInv) data := testSignStateRoot(t, r, pubs, accInv)
err = srv.OnPayload(&payload.Extensible{Data: data}) err = srv.OnPayload(&payload.Extensible{Data: data})
require.True(t, errors.Is(err, ErrWitnessHashMismatch), "got: %v", err) require.True(t, errors.Is(err, core.ErrWitnessHashMismatch), "got: %v", err)
require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight())
}) })
r, err = bc.stateRoot.GetStateRoot(updateIndex + 1) r, err = bc.GetStateModule().GetStateRoot(updateIndex + 1)
require.NoError(t, err) require.NoError(t, err)
data := testSignStateRoot(t, r, pubs, accs...) data := testSignStateRoot(t, r, pubs, accs...)
require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data})) require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data}))
require.EqualValues(t, 2, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 2, bc.GetStateModule().CurrentValidatedHeight())
r, err = bc.stateRoot.GetStateRoot(updateIndex + 1) r, err = bc.GetStateModule().GetStateRoot(updateIndex + 1)
require.NoError(t, err) require.NoError(t, err)
require.NotEqual(t, 0, len(r.Witness)) require.NotEqual(t, 0, len(r.Witness))
require.Equal(t, h, r.Witness[0].ScriptHash()) require.Equal(t, h, r.Witness[0].ScriptHash())
} }
type memoryStore struct {
*storage.MemoryStore
}
func (memoryStore) Close() error { return nil }
func TestStateRootInitNonZeroHeight(t *testing.T) { func TestStateRootInitNonZeroHeight(t *testing.T) {
st := memoryStore{storage.NewMemoryStore()} st := memoryStore{storage.NewMemoryStore()}
h, pubs, accs := newMajorityMultisigWithGAS(t, 2) h, pubs, accs := newMajorityMultisigWithGAS(t, 2)
var root util.Uint256 var root util.Uint256
t.Run("init", func(t *testing.T) { // this is in a separate test to do proper cleanup t.Run("init", func(t *testing.T) { // this is in a separate test to do proper cleanup
bc := newTestChainWithCustomCfgAndStore(t, st, nil) bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, st, true)
bc.setNodesByRole(t, true, noderoles.StateValidator, pubs) e := neotest.NewExecutor(t, bc, validator, committee)
transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000) designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee)
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
validatorNodes := []interface{}{pubs[0].Bytes(), pubs[1].Bytes()}
designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.StateValidator), validatorNodes)
gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), h, 1_0000_0000, nil)
_, err := persistBlock(bc)
require.NoError(t, err)
tmpDir := t.TempDir() tmpDir := t.TempDir()
w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass") w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass")
cfg := createStateRootConfig(w.Path(), "pass") cfg := createStateRootConfig(w.Path(), "pass")
srv, err := stateroot.New(cfg, bc.stateRoot, zaptest.NewLogger(t), bc, nil) srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here.
srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, nil)
require.NoError(t, err) require.NoError(t, err)
r, err := bc.stateRoot.GetStateRoot(2) r, err := bc.GetStateModule().GetStateRoot(2)
require.NoError(t, err) require.NoError(t, err)
data := testSignStateRoot(t, r, pubs, accs...) data := testSignStateRoot(t, r, pubs, accs...)
require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data})) require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data}))
require.EqualValues(t, 2, bc.stateRoot.CurrentValidatedHeight()) require.EqualValues(t, 2, bc.GetStateModule().CurrentValidatedHeight())
root = bc.stateRoot.CurrentLocalStateRoot() root = bc.GetStateModule().CurrentLocalStateRoot()
}) })
bc2 := newTestChainWithCustomCfgAndStore(t, st, nil) bc2, _, _ := chain.NewMultiWithCustomConfigAndStore(t, nil, st, true)
srv := bc2.GetStateModule() srv := bc2.GetStateModule()
require.EqualValues(t, 2, srv.CurrentValidatedHeight()) require.EqualValues(t, 2, srv.CurrentValidatedHeight())
require.Equal(t, root, srv.CurrentLocalStateRoot()) require.Equal(t, root, srv.CurrentLocalStateRoot())
@ -186,7 +212,22 @@ func createStateRootConfig(walletPath, password string) config.StateRoot {
func TestStateRootFull(t *testing.T) { func TestStateRootFull(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
bc := newTestChain(t) bc, validator, committee := chain.NewMulti(t)
e := neotest.NewExecutor(t, bc, validator, committee)
designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee)
gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas))
getDesignatedByRole := func(t *testing.T, h uint32) keys.PublicKeys {
res, err := designationSuperInvoker.TestInvoke(t, "getDesignatedByRole", int64(noderoles.StateValidator), h)
require.NoError(t, err)
nodes := res.Pop().Value().([]stackitem.Item)
pubs := make(keys.PublicKeys, len(nodes))
for i, node := range nodes {
pubs[i], err = keys.NewPublicKeyFromBytes(node.Value().([]byte), elliptic.P256())
require.NoError(t, err)
}
return pubs
}
h, pubs, accs := newMajorityMultisigWithGAS(t, 2) h, pubs, accs := newMajorityMultisigWithGAS(t, 2)
w := createAndWriteWallet(t, accs[1], filepath.Join(tmpDir, "wallet2"), "two") w := createAndWriteWallet(t, accs[1], filepath.Join(tmpDir, "wallet2"), "two")
@ -194,7 +235,8 @@ func TestStateRootFull(t *testing.T) {
var lastValidated atomic.Value var lastValidated atomic.Value
var lastHeight atomic.Uint32 var lastHeight atomic.Uint32
srv, err := stateroot.New(cfg, bc.stateRoot, zaptest.NewLogger(t), bc, func(ep *payload.Extensible) { srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here.
srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, func(ep *payload.Extensible) {
lastHeight.Store(ep.ValidBlockStart) lastHeight.Store(ep.ValidBlockStart)
lastValidated.Store(ep) lastValidated.Store(ep)
}) })
@ -202,16 +244,17 @@ func TestStateRootFull(t *testing.T) {
srv.Start() srv.Start()
t.Cleanup(srv.Shutdown) t.Cleanup(srv.Shutdown)
bc.setNodesByRole(t, true, noderoles.StateValidator, pubs) validatorNodes := []interface{}{pubs[0].Bytes(), pubs[1].Bytes()}
transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000) designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole",
int64(roles.StateValidator), validatorNodes)
gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), h, 1_0000_0000, nil)
require.Eventually(t, func() bool { return lastHeight.Load() == 2 }, time.Second, time.Millisecond) require.Eventually(t, func() bool { return lastHeight.Load() == 2 }, time.Second, time.Millisecond)
checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 2, 1) checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 2, 1, getDesignatedByRole)
_, err = persistBlock(bc) e.AddNewBlock(t)
require.NoError(t, err)
require.Eventually(t, func() bool { return lastHeight.Load() == 3 }, time.Second, time.Millisecond) require.Eventually(t, func() bool { return lastHeight.Load() == 3 }, time.Second, time.Millisecond)
checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 3, 1) checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 3, 1, getDesignatedByRole)
r, err := bc.stateRoot.GetStateRoot(2) r, err := bc.GetStateModule().GetStateRoot(2)
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, srv.AddSignature(2, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r))) require.NoError(t, srv.AddSignature(2, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r)))
require.NotNil(t, lastValidated.Load().(*payload.Extensible)) require.NotNil(t, lastValidated.Load().(*payload.Extensible))
@ -220,7 +263,7 @@ func TestStateRootFull(t *testing.T) {
require.NoError(t, testserdes.DecodeBinary(lastValidated.Load().(*payload.Extensible).Data, msg)) require.NoError(t, testserdes.DecodeBinary(lastValidated.Load().(*payload.Extensible).Data, msg))
require.NotEqual(t, stateroot.RootT, msg.Type) // not a sender for this root require.NotEqual(t, stateroot.RootT, msg.Type) // not a sender for this root
r, err = bc.stateRoot.GetStateRoot(3) r, err = bc.GetStateModule().GetStateRoot(3)
require.NoError(t, err) require.NoError(t, err)
require.Error(t, srv.AddSignature(2, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r))) require.Error(t, srv.AddSignature(2, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r)))
require.NoError(t, srv.AddSignature(3, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r))) require.NoError(t, srv.AddSignature(3, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r)))
@ -235,8 +278,8 @@ func TestStateRootFull(t *testing.T) {
require.Equal(t, r.Root, actual.Root) require.Equal(t, r.Root, actual.Root)
} }
func checkVoteBroadcasted(t *testing.T, bc *Blockchain, p *payload.Extensible, func checkVoteBroadcasted(t *testing.T, bc *core.Blockchain, p *payload.Extensible,
height uint32, valIndex byte) { height uint32, valIndex byte, getDesignatedByRole func(t *testing.T, h uint32) keys.PublicKeys) {
require.NotNil(t, p) require.NotNil(t, p)
m := new(stateroot.Message) m := new(stateroot.Message)
require.NoError(t, testserdes.DecodeBinary(p.Data, m)) require.NoError(t, testserdes.DecodeBinary(p.Data, m))
@ -249,8 +292,7 @@ func checkVoteBroadcasted(t *testing.T, bc *Blockchain, p *payload.Extensible,
require.Equal(t, height, vote.Height) require.Equal(t, height, vote.Height)
require.Equal(t, int32(valIndex), vote.ValidatorIndex) require.Equal(t, int32(valIndex), vote.ValidatorIndex)
pubs, _, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.StateValidator, bc.BlockHeight()) pubs := getDesignatedByRole(t, bc.BlockHeight())
require.NoError(t, err)
require.True(t, len(pubs) > int(valIndex)) require.True(t, len(pubs) > int(valIndex))
require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r)) require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r))
} }

View file

@ -1,13 +1,14 @@
package core package core_test
import ( import (
"testing" "testing"
"time"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mpt" "github.com/nspcc-dev/neo-go/pkg/core/mpt"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/util/slice" "github.com/nspcc-dev/neo-go/pkg/util/slice"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -18,40 +19,41 @@ func TestStateSyncModule_Init(t *testing.T) {
stateSyncInterval = 2 stateSyncInterval = 2
maxTraceable uint32 = 3 maxTraceable uint32 = 3
) )
spoutCfg := func(c *config.Config) { spoutCfg := func(c *config.ProtocolConfiguration) {
c.ProtocolConfiguration.StateRootInHeader = true c.StateRootInHeader = true
c.ProtocolConfiguration.P2PStateExchangeExtensions = true c.P2PStateExchangeExtensions = true
c.ProtocolConfiguration.StateSyncInterval = stateSyncInterval c.StateSyncInterval = stateSyncInterval
c.ProtocolConfiguration.MaxTraceableBlocks = maxTraceable c.MaxTraceableBlocks = maxTraceable
} }
bcSpout := newTestChainWithCustomCfg(t, spoutCfg) bcSpout, validators, committee := chain.NewMultiWithCustomConfig(t, spoutCfg)
e := neotest.NewExecutor(t, bcSpout, validators, committee)
for i := 0; i <= 2*stateSyncInterval+int(maxTraceable)+1; i++ { for i := 0; i <= 2*stateSyncInterval+int(maxTraceable)+1; i++ {
require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock())) e.AddNewBlock(t)
} }
boltCfg := func(c *config.Config) { boltCfg := func(c *config.ProtocolConfiguration) {
spoutCfg(c) spoutCfg(c)
c.ProtocolConfiguration.KeepOnlyLatestState = true c.KeepOnlyLatestState = true
c.ProtocolConfiguration.RemoveUntraceableBlocks = true c.RemoveUntraceableBlocks = true
} }
t.Run("error: module disabled by config", func(t *testing.T) { t.Run("error: module disabled by config", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, func(c *config.Config) { bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
boltCfg(c) boltCfg(c)
c.ProtocolConfiguration.RemoveUntraceableBlocks = false c.RemoveUntraceableBlocks = false
}) })
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.Error(t, module.Init(bcSpout.BlockHeight())) // module inactive (non-archival node) require.Error(t, module.Init(bcSpout.BlockHeight())) // module inactive (non-archival node)
}) })
t.Run("inactive: spout chain is too low to start state sync process", func(t *testing.T) { t.Run("inactive: spout chain is too low to start state sync process", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg)
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.NoError(t, module.Init(uint32(2*stateSyncInterval-1))) require.NoError(t, module.Init(uint32(2*stateSyncInterval-1)))
require.False(t, module.IsActive()) require.False(t, module.IsActive())
}) })
t.Run("inactive: bolt chain height is close enough to spout chain height", func(t *testing.T) { t.Run("inactive: bolt chain height is close enough to spout chain height", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg)
for i := 1; i < int(bcSpout.BlockHeight())-stateSyncInterval; i++ { for i := 1; i < int(bcSpout.BlockHeight())-stateSyncInterval; i++ {
b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i))
require.NoError(t, err) require.NoError(t, err)
@ -63,15 +65,16 @@ func TestStateSyncModule_Init(t *testing.T) {
}) })
t.Run("error: bolt chain is too low to start state sync process", func(t *testing.T) { t.Run("error: bolt chain is too low to start state sync process", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, validatorsBolt, committeeBolt := chain.NewMultiWithCustomConfig(t, boltCfg)
require.NoError(t, bcBolt.AddBlock(bcBolt.newBlock())) eBolt := neotest.NewExecutor(t, bcBolt, validatorsBolt, committeeBolt)
eBolt.AddNewBlock(t)
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.Error(t, module.Init(uint32(3*stateSyncInterval))) require.Error(t, module.Init(uint32(3*stateSyncInterval)))
}) })
t.Run("initialized: no previous state sync point", func(t *testing.T) { t.Run("initialized: no previous state sync point", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg)
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.NoError(t, module.Init(bcSpout.BlockHeight())) require.NoError(t, module.Init(bcSpout.BlockHeight()))
@ -82,7 +85,7 @@ func TestStateSyncModule_Init(t *testing.T) {
}) })
t.Run("error: outdated state sync point in the storage", func(t *testing.T) { t.Run("error: outdated state sync point in the storage", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg)
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.NoError(t, module.Init(bcSpout.BlockHeight())) require.NoError(t, module.Init(bcSpout.BlockHeight()))
@ -91,7 +94,7 @@ func TestStateSyncModule_Init(t *testing.T) {
}) })
t.Run("initialized: valid previous state sync point in the storage", func(t *testing.T) { t.Run("initialized: valid previous state sync point in the storage", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg)
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.NoError(t, module.Init(bcSpout.BlockHeight())) require.NoError(t, module.Init(bcSpout.BlockHeight()))
@ -104,7 +107,8 @@ func TestStateSyncModule_Init(t *testing.T) {
}) })
t.Run("initialization from headers/blocks/mpt synced stages", func(t *testing.T) { t.Run("initialization from headers/blocks/mpt synced stages", func(t *testing.T) {
bcBolt := newTestChainWithCustomCfg(t, boltCfg) bcBolt, validatorsBolt, committeeBolt := chain.NewMultiWithCustomConfig(t, boltCfg)
eBolt := neotest.NewExecutor(t, bcBolt, validatorsBolt, committeeBolt)
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
require.NoError(t, module.Init(bcSpout.BlockHeight())) require.NoError(t, module.Init(bcSpout.BlockHeight()))
@ -263,7 +267,7 @@ func TestStateSyncModule_Init(t *testing.T) {
// add one more block to the restored chain and start new module: the module should recognise state sync is completed // add one more block to the restored chain and start new module: the module should recognise state sync is completed
// and regular blocks processing was started // and regular blocks processing was started
require.NoError(t, bcBolt.AddBlock(bcBolt.newBlock())) eBolt.AddNewBlock(t)
module = bcBolt.GetStateSyncModule() module = bcBolt.GetStateSyncModule()
require.NoError(t, module.Init(bcSpout.BlockHeight())) require.NoError(t, module.Init(bcSpout.BlockHeight()))
require.False(t, module.IsActive()) require.False(t, module.IsActive())
@ -282,27 +286,31 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
maxTraceable uint32 = 6 maxTraceable uint32 = 6
stateSyncPoint = 20 stateSyncPoint = 20
) )
spoutCfg := func(c *config.Config) { spoutCfg := func(c *config.ProtocolConfiguration) {
c.ProtocolConfiguration.StateRootInHeader = true c.StateRootInHeader = true
c.ProtocolConfiguration.P2PStateExchangeExtensions = true c.P2PStateExchangeExtensions = true
c.ProtocolConfiguration.StateSyncInterval = stateSyncInterval c.StateSyncInterval = stateSyncInterval
c.ProtocolConfiguration.MaxTraceableBlocks = maxTraceable c.MaxTraceableBlocks = maxTraceable
c.P2PSigExtensions = true // `initBasicChain` assumes Notary is enabled.
} }
bcSpout := newTestChainWithCustomCfg(t, spoutCfg) bcSpoutStore := storage.NewMemoryStore()
initBasicChain(t, bcSpout) bcSpout, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, spoutCfg, bcSpoutStore, false)
go bcSpout.Run() // Will close it manually at the end.
e := neotest.NewExecutor(t, bcSpout, validators, committee)
initBasicChain(t, e)
// make spout chain higher that latest state sync point (add several blocks up to stateSyncPoint+2) // make spout chain higher that latest state sync point (add several blocks up to stateSyncPoint+2)
require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock())) e.AddNewBlock(t)
require.Equal(t, stateSyncPoint+2, int(bcSpout.BlockHeight())) require.Equal(t, stateSyncPoint+2, int(bcSpout.BlockHeight()))
boltCfg := func(c *config.Config) { boltCfg := func(c *config.ProtocolConfiguration) {
spoutCfg(c) spoutCfg(c)
c.ProtocolConfiguration.KeepOnlyLatestState = true c.KeepOnlyLatestState = true
c.ProtocolConfiguration.RemoveUntraceableBlocks = true c.RemoveUntraceableBlocks = true
} }
bcBoltStore := memoryStore{storage.NewMemoryStore()} bcBoltStore := storage.NewMemoryStore()
bcBolt := initTestChain(t, bcBoltStore, boltCfg) bcBolt, _, _ := chain.NewMultiWithCustomConfigAndStore(t, boltCfg, bcBoltStore, false)
go bcBolt.Run() go bcBolt.Run() // Will close it manually at the end.
module := bcBolt.GetStateSyncModule() module := bcBolt.GetStateSyncModule()
t.Run("error: add headers before initialisation", func(t *testing.T) { t.Run("error: add headers before initialisation", func(t *testing.T) {
@ -421,9 +429,9 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
require.Equal(t, bcSpout.BlockHeight(), bcBolt.BlockHeight()) require.Equal(t, bcSpout.BlockHeight(), bcBolt.BlockHeight())
// compare storage states // compare storage states
fetchStorage := func(bc *Blockchain) []storage.KeyValue { fetchStorage := func(ps storage.Store, storagePrefix byte) []storage.KeyValue {
var kv []storage.KeyValue var kv []storage.KeyValue
bc.dao.Store.Seek(storage.SeekRange{Prefix: []byte{byte(bc.dao.Version.StoragePrefix)}}, func(k, v []byte) bool { ps.Seek(storage.SeekRange{Prefix: []byte{storagePrefix}}, func(k, v []byte) bool {
key := slice.Copy(k) key := slice.Copy(k)
value := slice.Copy(v) value := slice.Copy(v)
if key[0] == byte(storage.STTempStorage) { if key[0] == byte(storage.STTempStorage) {
@ -437,25 +445,19 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) {
}) })
return kv return kv
} }
expected := fetchStorage(bcSpout) // Both blockchains are running, so we need to wait until recent changes will be persisted
actual := fetchStorage(bcBolt) // to the underlying backend store. Close blockchains to ensure persist was completed.
bcSpout.Close()
bcBolt.Close()
expected := fetchStorage(bcSpoutStore, byte(storage.STStorage))
actual := fetchStorage(bcBoltStore, byte(storage.STTempStorage))
require.ElementsMatch(t, expected, actual) require.ElementsMatch(t, expected, actual)
// no temp items should be left // no temp items should be left
require.Eventually(t, func() bool {
var haveItems bool var haveItems bool
bcBolt.dao.Store.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STStorage)}}, func(_, _ []byte) bool { bcBoltStore.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STStorage)}}, func(_, _ []byte) bool {
haveItems = true haveItems = true
return false return false
}) })
return !haveItems require.False(t, haveItems)
}, time.Second*5, time.Millisecond*100)
bcBolt.Close()
// Check restoring with new prefix.
bcBolt = initTestChain(t, bcBoltStore, boltCfg)
go bcBolt.Run()
defer bcBolt.Close()
require.Equal(t, storage.STTempStorage, bcBolt.dao.Version.StoragePrefix)
require.Equal(t, storage.STTempStorage, bcBolt.persistent.Version.StoragePrefix)
} }

View file

@ -1,9 +0,0 @@
## Management helper contracts
Management helper contracts NEF and manifest files are generated automatically by
`TestGenerateManagementHelperContracts` and are used in tests. Do not modify these files manually.
To regenerate these files:
1. Open `TestGenerateManagementHelperContracts` and set `saveState` flag to `true`.
2. Run `TestGenerateManagementHelperContracts`.
3. Set `saveState` back to `false`.

View file

@ -1 +0,0 @@
{"name":"TestAux","abi":{"methods":[],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0x00ecaa2f079b65e3b31572e4c2c160a1abd02997","methods":["add","drop","add3","invalidReturn","justReturn","getValue"]}],"supportedstandards":[],"trusts":[],"extra":null}

View file

@ -1,9 +0,0 @@
## Oracle helper contract
Oracle helper contract NEF and manifest files are generated automatically by
`TestGenerateOracleContract` and are used in tests. Do not modify these files manually.
To regenerate these files:
1. Open `TestGenerateOracleContract` and set `saveState` flag to `true`.
2. Run `TestGenerateOracleContract`.
3. Set `saveState` back to `false`.

View file

@ -36,7 +36,7 @@ type Executor struct {
} }
// NewExecutor creates new executor instance from provided blockchain and committee. // NewExecutor creates new executor instance from provided blockchain and committee.
func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, validator, committee Signer) *Executor { func NewExecutor(t testing.TB, bc blockchainer.Blockchainer, validator, committee Signer) *Executor {
checkMultiSigner(t, validator) checkMultiSigner(t, validator)
checkMultiSigner(t, committee) checkMultiSigner(t, committee)
@ -50,21 +50,28 @@ func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, validator, committe
} }
// TopBlock returns block with the highest index. // TopBlock returns block with the highest index.
func (e *Executor) TopBlock(t *testing.T) *block.Block { func (e *Executor) TopBlock(t testing.TB) *block.Block {
b, err := e.Chain.GetBlock(e.Chain.GetHeaderHash(int(e.Chain.BlockHeight()))) b, err := e.Chain.GetBlock(e.Chain.GetHeaderHash(int(e.Chain.BlockHeight())))
require.NoError(t, err) require.NoError(t, err)
return b return b
} }
// NativeHash returns native contract hash by name. // NativeHash returns native contract hash by name.
func (e *Executor) NativeHash(t *testing.T, name string) util.Uint160 { func (e *Executor) NativeHash(t testing.TB, name string) util.Uint160 {
h, err := e.Chain.GetNativeContractScriptHash(name) h, err := e.Chain.GetNativeContractScriptHash(name)
require.NoError(t, err) require.NoError(t, err)
return h return h
} }
// ContractHash returns contract hash by ID.
func (e *Executor) ContractHash(t testing.TB, id int32) util.Uint160 {
h, err := e.Chain.GetContractScriptHash(id)
require.NoError(t, err)
return h
}
// NativeID returns native contract ID by name. // NativeID returns native contract ID by name.
func (e *Executor) NativeID(t *testing.T, name string) int32 { func (e *Executor) NativeID(t testing.TB, name string) int32 {
h := e.NativeHash(t, name) h := e.NativeHash(t, name)
cs := e.Chain.GetContractState(h) cs := e.Chain.GetContractState(h)
require.NotNil(t, cs) require.NotNil(t, cs)
@ -72,7 +79,7 @@ func (e *Executor) NativeID(t *testing.T, name string) int32 {
} }
// NewUnsignedTx creates new unsigned transaction which invokes method of contract with hash. // NewUnsignedTx creates new unsigned transaction which invokes method of contract with hash.
func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction { func (e *Executor) NewUnsignedTx(t testing.TB, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, hash, method, callflag.All, args...) emit.AppCall(w.BinWriter, hash, method, callflag.All, args...)
require.NoError(t, w.Err) require.NoError(t, w.Err)
@ -86,14 +93,14 @@ func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string,
// NewTx creates new transaction which invokes contract method. // NewTx creates new transaction which invokes contract method.
// Transaction is signed with signer. // Transaction is signed with signer.
func (e *Executor) NewTx(t *testing.T, signers []Signer, func (e *Executor) NewTx(t testing.TB, signers []Signer,
hash util.Uint160, method string, args ...interface{}) *transaction.Transaction { hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
tx := e.NewUnsignedTx(t, hash, method, args...) tx := e.NewUnsignedTx(t, hash, method, args...)
return e.SignTx(t, tx, -1, signers...) return e.SignTx(t, tx, -1, signers...)
} }
// SignTx signs a transaction using provided signers. // SignTx signs a transaction using provided signers.
func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int64, signers ...Signer) *transaction.Transaction { func (e *Executor) SignTx(t testing.TB, tx *transaction.Transaction, sysFee int64, signers ...Signer) *transaction.Transaction {
for _, acc := range signers { for _, acc := range signers {
tx.Signers = append(tx.Signers, transaction.Signer{ tx.Signers = append(tx.Signers, transaction.Signer{
Account: acc.ScriptHash(), Account: acc.ScriptHash(),
@ -111,7 +118,7 @@ func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int6
// NewAccount returns new signer holding 100.0 GAS (or given amount is specified). // NewAccount returns new signer holding 100.0 GAS (or given amount is specified).
// This method advances the chain by one block with a transfer transaction. // This method advances the chain by one block with a transfer transaction.
func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer { func (e *Executor) NewAccount(t testing.TB, expectedGASBalance ...int64) Signer {
acc, err := wallet.NewAccount() acc, err := wallet.NewAccount()
require.NoError(t, err) require.NoError(t, err)
@ -131,8 +138,16 @@ func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer
// precalculated contract hash matches the actual one. // precalculated contract hash matches the actual one.
// data is an optional argument to `_deploy`. // data is an optional argument to `_deploy`.
// Returns hash of the deploy transaction. // Returns hash of the deploy transaction.
func (e *Executor) DeployContract(t *testing.T, c *Contract, data interface{}) util.Uint256 { func (e *Executor) DeployContract(t testing.TB, c *Contract, data interface{}) util.Uint256 {
tx := e.NewDeployTx(t, e.Chain, c, data) return e.DeployContractBy(t, e.Validator, c, data)
}
// DeployContractBy compiles and deploys contract to bc using provided signer.
// It also checks that precalculated contract hash matches the actual one.
// data is an optional argument to `_deploy`.
// Returns hash of the deploy transaction.
func (e *Executor) DeployContractBy(t testing.TB, signer Signer, c *Contract, data interface{}) util.Uint256 {
tx := NewDeployTxBy(t, e.Chain, signer, c, data)
e.AddNewBlock(t, tx) e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash()) e.CheckHalt(t, tx.Hash())
@ -148,9 +163,9 @@ func (e *Executor) DeployContract(t *testing.T, c *Contract, data interface{}) u
return tx.Hash() return tx.Hash()
} }
// DeployContractCheckFAULT compiles and deploys contract to bc. It checks that deploy // DeployContractCheckFAULT compiles and deploys contract to bc using validator
// transaction FAULTed with the specified error. // account. It checks that deploy transaction FAULTed with the specified error.
func (e *Executor) DeployContractCheckFAULT(t *testing.T, c *Contract, data interface{}, errMessage string) { func (e *Executor) DeployContractCheckFAULT(t testing.TB, c *Contract, data interface{}, errMessage string) {
tx := e.NewDeployTx(t, e.Chain, c, data) tx := e.NewDeployTx(t, e.Chain, c, data)
e.AddNewBlock(t, tx) e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), errMessage) e.CheckFault(t, tx.Hash(), errMessage)
@ -158,31 +173,41 @@ func (e *Executor) DeployContractCheckFAULT(t *testing.T, c *Contract, data inte
// InvokeScript adds transaction with the specified script to the chain and // InvokeScript adds transaction with the specified script to the chain and
// returns its hash. It does no faults check. // returns its hash. It does no faults check.
func (e *Executor) InvokeScript(t *testing.T, script []byte, signers []Signer) util.Uint256 { func (e *Executor) InvokeScript(t testing.TB, script []byte, signers []Signer) util.Uint256 {
tx := transaction.New(script, 0) tx := e.PrepareInvocation(t, script, signers)
tx.Nonce = Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
e.SignTx(t, tx, -1, signers...)
e.AddNewBlock(t, tx) e.AddNewBlock(t, tx)
return tx.Hash() return tx.Hash()
} }
// PrepareInvocation creates transaction with the specified script and signs it
// by the provided signer.
func (e *Executor) PrepareInvocation(t testing.TB, script []byte, signers []Signer, validUntilBlock ...uint32) *transaction.Transaction {
tx := transaction.New(script, 0)
tx.Nonce = Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
if len(validUntilBlock) != 0 {
tx.ValidUntilBlock = validUntilBlock[0]
}
e.SignTx(t, tx, -1, signers...)
return tx
}
// InvokeScriptCheckHALT adds transaction with the specified script to the chain // InvokeScriptCheckHALT adds transaction with the specified script to the chain
// and checks it's HALTed with the specified items on stack. // and checks it's HALTed with the specified items on stack.
func (e *Executor) InvokeScriptCheckHALT(t *testing.T, script []byte, signers []Signer, stack ...stackitem.Item) { func (e *Executor) InvokeScriptCheckHALT(t testing.TB, script []byte, signers []Signer, stack ...stackitem.Item) {
hash := e.InvokeScript(t, script, signers) hash := e.InvokeScript(t, script, signers)
e.CheckHalt(t, hash, stack...) e.CheckHalt(t, hash, stack...)
} }
// InvokeScriptCheckFAULT adds transaction with the specified script to the // InvokeScriptCheckFAULT adds transaction with the specified script to the
// chain and checks it's FAULTed with the specified error. // chain and checks it's FAULTed with the specified error.
func (e *Executor) InvokeScriptCheckFAULT(t *testing.T, script []byte, signers []Signer, errMessage string) { func (e *Executor) InvokeScriptCheckFAULT(t testing.TB, script []byte, signers []Signer, errMessage string) {
hash := e.InvokeScript(t, script, signers) hash := e.InvokeScript(t, script, signers)
e.CheckFault(t, hash, errMessage) e.CheckFault(t, hash, errMessage)
} }
// CheckHalt checks that transaction persisted with HALT state. // CheckHalt checks that transaction persisted with HALT state.
func (e *Executor) CheckHalt(t *testing.T, h util.Uint256, stack ...stackitem.Item) *state.AppExecResult { func (e *Executor) CheckHalt(t testing.TB, h util.Uint256, stack ...stackitem.Item) *state.AppExecResult {
aer, err := e.Chain.GetAppExecResults(h, trigger.Application) aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
@ -194,7 +219,7 @@ func (e *Executor) CheckHalt(t *testing.T, h util.Uint256, stack ...stackitem.It
// CheckFault checks that transaction persisted with FAULT state. // CheckFault checks that transaction persisted with FAULT state.
// Raised exception is also checked to contain s as a substring. // Raised exception is also checked to contain s as a substring.
func (e *Executor) CheckFault(t *testing.T, h util.Uint256, s string) { func (e *Executor) CheckFault(t testing.TB, h util.Uint256, s string) {
aer, err := e.Chain.GetAppExecResults(h, trigger.Application) aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, vm.FaultState, aer[0].VMState) require.Equal(t, vm.FaultState, aer[0].VMState)
@ -204,7 +229,7 @@ func (e *Executor) CheckFault(t *testing.T, h util.Uint256, s string) {
// CheckTxNotificationEvent checks that specified event was emitted at the specified position // CheckTxNotificationEvent checks that specified event was emitted at the specified position
// during transaction script execution. Negative index corresponds to backwards enumeration. // during transaction script execution. Negative index corresponds to backwards enumeration.
func (e *Executor) CheckTxNotificationEvent(t *testing.T, h util.Uint256, index int, expected state.NotificationEvent) { func (e *Executor) CheckTxNotificationEvent(t testing.TB, h util.Uint256, index int, expected state.NotificationEvent) {
aer, err := e.Chain.GetAppExecResults(h, trigger.Application) aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
require.NoError(t, err) require.NoError(t, err)
l := len(aer[0].Events) l := len(aer[0].Events)
@ -216,13 +241,24 @@ func (e *Executor) CheckTxNotificationEvent(t *testing.T, h util.Uint256, index
} }
// CheckGASBalance ensures that provided account owns specified amount of GAS. // CheckGASBalance ensures that provided account owns specified amount of GAS.
func (e *Executor) CheckGASBalance(t *testing.T, acc util.Uint160, expected *big.Int) { func (e *Executor) CheckGASBalance(t testing.TB, acc util.Uint160, expected *big.Int) {
actual := e.Chain.GetUtilityTokenBalance(acc) actual := e.Chain.GetUtilityTokenBalance(acc)
require.Equal(t, expected, actual, fmt.Errorf("invalid GAS balance: expected %s, got %s", expected.String(), actual.String())) require.Equal(t, expected, actual, fmt.Errorf("invalid GAS balance: expected %s, got %s", expected.String(), actual.String()))
} }
// EnsureGASBalance ensures that provided account owns amount of GAS that satisfies provided condition.
func (e *Executor) EnsureGASBalance(t testing.TB, acc util.Uint160, isOk func(balance *big.Int) bool) {
actual := e.Chain.GetUtilityTokenBalance(acc)
require.True(t, isOk(actual), fmt.Errorf("invalid GAS balance: got %s, condition is not satisfied", actual.String()))
}
// NewDeployTx returns new deployment tx for contract signed by committee. // NewDeployTx returns new deployment tx for contract signed by committee.
func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction { func (e *Executor) NewDeployTx(t testing.TB, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction {
return NewDeployTxBy(t, bc, e.Validator, c, data)
}
// NewDeployTxBy returns new deployment tx for contract signed by the specified signer.
func NewDeployTxBy(t testing.TB, bc blockchainer.Blockchainer, signer Signer, c *Contract, data interface{}) *transaction.Transaction {
rawManifest, err := json.Marshal(c.Manifest) rawManifest, err := json.Marshal(c.Manifest)
require.NoError(t, err) require.NoError(t, err)
@ -237,11 +273,11 @@ func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Co
tx.Nonce = Nonce() tx.Nonce = Nonce()
tx.ValidUntilBlock = bc.BlockHeight() + 1 tx.ValidUntilBlock = bc.BlockHeight() + 1
tx.Signers = []transaction.Signer{{ tx.Signers = []transaction.Signer{{
Account: e.Validator.ScriptHash(), Account: signer.ScriptHash(),
Scopes: transaction.Global, Scopes: transaction.Global,
}} }}
addNetworkFee(bc, tx, e.Validator) addNetworkFee(bc, tx, signer)
require.NoError(t, e.Validator.SignTx(netmode.UnitTestNet, tx)) require.NoError(t, signer.SignTx(netmode.UnitTestNet, tx))
return tx return tx
} }
@ -266,7 +302,7 @@ func addNetworkFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, si
} }
// NewUnsignedBlock creates new unsigned block from txs. // NewUnsignedBlock creates new unsigned block from txs.
func (e *Executor) NewUnsignedBlock(t *testing.T, txs ...*transaction.Transaction) *block.Block { func (e *Executor) NewUnsignedBlock(t testing.TB, txs ...*transaction.Transaction) *block.Block {
lastBlock := e.TopBlock(t) lastBlock := e.TopBlock(t)
b := &block.Block{ b := &block.Block{
Header: block.Header{ Header: block.Header{
@ -289,7 +325,7 @@ func (e *Executor) NewUnsignedBlock(t *testing.T, txs ...*transaction.Transactio
} }
// AddNewBlock creates a new block from provided transactions and adds it on bc. // AddNewBlock creates a new block from provided transactions and adds it on bc.
func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *block.Block { func (e *Executor) AddNewBlock(t testing.TB, txs ...*transaction.Transaction) *block.Block {
b := e.NewUnsignedBlock(t, txs...) b := e.NewUnsignedBlock(t, txs...)
e.SignBlock(b) e.SignBlock(b)
require.NoError(t, e.Chain.AddBlock(b)) require.NoError(t, e.Chain.AddBlock(b))
@ -297,10 +333,12 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *b
} }
// GenerateNewBlocks adds specified number of empty blocks to the chain. // GenerateNewBlocks adds specified number of empty blocks to the chain.
func (e *Executor) GenerateNewBlocks(t *testing.T, count int) { func (e *Executor) GenerateNewBlocks(t testing.TB, count int) []*block.Block {
blocks := make([]*block.Block, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
e.AddNewBlock(t) blocks[i] = e.AddNewBlock(t)
} }
return blocks
} }
// SignBlock add validators signature to b. // SignBlock add validators signature to b.
@ -311,7 +349,7 @@ func (e *Executor) SignBlock(b *block.Block) *block.Block {
} }
// AddBlockCheckHalt is a convenient wrapper over AddBlock and CheckHalt. // AddBlockCheckHalt is a convenient wrapper over AddBlock and CheckHalt.
func (e *Executor) AddBlockCheckHalt(t *testing.T, txs ...*transaction.Transaction) *block.Block { func (e *Executor) AddBlockCheckHalt(t testing.TB, txs ...*transaction.Transaction) *block.Block {
b := e.AddNewBlock(t, txs...) b := e.AddNewBlock(t, txs...)
for _, tx := range txs { for _, tx := range txs {
e.CheckHalt(t, tx.Hash()) e.CheckHalt(t, tx.Hash())
@ -344,14 +382,14 @@ func TestInvoke(bc blockchainer.Blockchainer, tx *transaction.Transaction) (*vm.
} }
// GetTransaction returns transaction and its height by the specified hash. // GetTransaction returns transaction and its height by the specified hash.
func (e *Executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Transaction, uint32) { func (e *Executor) GetTransaction(t testing.TB, h util.Uint256) (*transaction.Transaction, uint32) {
tx, height, err := e.Chain.GetTransaction(h) tx, height, err := e.Chain.GetTransaction(h)
require.NoError(t, err) require.NoError(t, err)
return tx, height return tx, height
} }
// GetBlockByIndex returns block by the specified index. // GetBlockByIndex returns block by the specified index.
func (e *Executor) GetBlockByIndex(t *testing.T, idx int) *block.Block { func (e *Executor) GetBlockByIndex(t testing.TB, idx int) *block.Block {
h := e.Chain.GetHeaderHash(idx) h := e.Chain.GetHeaderHash(idx)
require.NotEmpty(t, h) require.NotEmpty(t, h)
b, err := e.Chain.GetBlock(h) b, err := e.Chain.GetBlock(h)
@ -360,7 +398,7 @@ func (e *Executor) GetBlockByIndex(t *testing.T, idx int) *block.Block {
} }
// GetTxExecResult returns application execution results for the specified transaction. // GetTxExecResult returns application execution results for the specified transaction.
func (e *Executor) GetTxExecResult(t *testing.T, h util.Uint256) *state.AppExecResult { func (e *Executor) GetTxExecResult(t testing.TB, h util.Uint256) *state.AppExecResult {
aer, err := e.Chain.GetAppExecResults(h, trigger.Application) aer, err := e.Chain.GetAppExecResults(h, trigger.Application)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(aer)) require.Equal(t, 1, len(aer))

View file

@ -121,15 +121,14 @@ func init() {
// this package. MemoryStore is used as the backend storage, so all of the chain // this package. MemoryStore is used as the backend storage, so all of the chain
// contents is always in RAM. The Signer returned is validator (and committee at // contents is always in RAM. The Signer returned is validator (and committee at
// the same time). // the same time).
func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) { func NewSingle(t testing.TB) (*core.Blockchain, neotest.Signer) {
return NewSingleWithCustomConfig(t, nil) return NewSingleWithCustomConfig(t, nil)
} }
// NewSingleWithCustomConfig is similar to NewSingle, but allows to override the // NewSingleWithCustomConfig is similar to NewSingle, but allows to override the
// default configuration. // default configuration.
func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { func NewSingleWithCustomConfig(t testing.TB, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) {
st := storage.NewMemoryStore() return NewSingleWithCustomConfigAndStore(t, f, nil, true)
return NewSingleWithCustomConfigAndStore(t, f, st, true)
} }
// NewSingleWithCustomConfigAndStore is similar to NewSingleWithCustomConfig, but // NewSingleWithCustomConfigAndStore is similar to NewSingleWithCustomConfig, but
@ -137,7 +136,7 @@ func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguratio
// Run method is called on the Blockchain instance, if not then it's caller's // Run method is called on the Blockchain instance, if not then it's caller's
// responsibility to do that before using the chain and its caller's responsibility // responsibility to do that before using the chain and its caller's responsibility
// also to properly Close the chain when done. // also to properly Close the chain when done.
func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) { func NewSingleWithCustomConfigAndStore(t testing.TB, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) {
protoCfg := config.ProtocolConfiguration{ protoCfg := config.ProtocolConfiguration{
Magic: netmode.UnitTestNet, Magic: netmode.UnitTestNet,
MaxTraceableBlocks: MaxTraceableBlocks, MaxTraceableBlocks: MaxTraceableBlocks,
@ -150,6 +149,9 @@ func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.Protocol
if f != nil { if f != nil {
f(&protoCfg) f(&protoCfg)
} }
if st == nil {
st = storage.NewMemoryStore()
}
log := zaptest.NewLogger(t) log := zaptest.NewLogger(t)
bc, err := core.NewBlockchain(st, protoCfg, log) bc, err := core.NewBlockchain(st, protoCfg, log)
require.NoError(t, err) require.NoError(t, err)
@ -163,13 +165,34 @@ func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.Protocol
// NewMulti creates new blockchain instance with four validators and six // NewMulti creates new blockchain instance with four validators and six
// committee members, otherwise not differring much from NewSingle. The // committee members, otherwise not differring much from NewSingle. The
// second value returned contains validators Signer, the third -- committee one. // second value returned contains validators Signer, the third -- committee one.
func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) { func NewMulti(t testing.TB) (*core.Blockchain, neotest.Signer, neotest.Signer) {
return NewMultiWithCustomConfig(t, nil) return NewMultiWithCustomConfig(t, nil)
} }
// NewMultiWithCustomConfig is similar to NewMulti except it allows to override the // NewMultiWithCustomConfig is similar to NewMulti except it allows to override the
// default configuration. // default configuration.
func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { func NewMultiWithCustomConfig(t testing.TB, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) {
return NewMultiWithCustomConfigAndStore(t, f, nil, true)
}
// NewMultiWithCustomConfigAndStore is similar to NewMultiWithCustomConfig, but
// also allows to override backend Store being used. The last parameter controls if
// Run method is called on the Blockchain instance, if not then it's caller's
// responsibility to do that before using the chain and its caller's responsibility
// also to properly Close the chain when done.
func NewMultiWithCustomConfigAndStore(t testing.TB, f func(*config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer, neotest.Signer) {
bc, validator, committee, err := NewMultiWithCustomConfigAndStoreNoCheck(t, f, st)
require.NoError(t, err)
if run {
go bc.Run()
t.Cleanup(bc.Close)
}
return bc, validator, committee
}
// NewMultiWithCustomConfigAndStoreNoCheck is similar to NewMultiWithCustomConfig,
// but do not perform Blockchain run and do not check Blockchain constructor error.
func NewMultiWithCustomConfigAndStoreNoCheck(t testing.TB, f func(*config.ProtocolConfiguration), st storage.Store) (*core.Blockchain, neotest.Signer, neotest.Signer, error) {
protoCfg := config.ProtocolConfiguration{ protoCfg := config.ProtocolConfiguration{
Magic: netmode.UnitTestNet, Magic: netmode.UnitTestNet,
MaxTraceableBlocks: MaxTraceableBlocks, MaxTraceableBlocks: MaxTraceableBlocks,
@ -182,12 +205,11 @@ func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration
if f != nil { if f != nil {
f(&protoCfg) f(&protoCfg)
} }
if st == nil {
st = storage.NewMemoryStore()
}
st := storage.NewMemoryStore()
log := zaptest.NewLogger(t) log := zaptest.NewLogger(t)
bc, err := core.NewBlockchain(st, protoCfg, log) bc, err := core.NewBlockchain(st, protoCfg, log)
require.NoError(t, err) return bc, neotest.NewMultiSigner(multiValidatorAcc...), neotest.NewMultiSigner(multiCommitteeAcc...), err
go bc.Run()
t.Cleanup(bc.Close)
return bc, neotest.NewMultiSigner(multiValidatorAcc...), neotest.NewMultiSigner(multiCommitteeAcc...)
} }

View file

@ -19,6 +19,15 @@ type ContractInvoker struct {
Signers []Signer Signers []Signer
} }
// NewInvoker creates new ContractInvoker for contract with hash h and specified signers.
func (e *Executor) NewInvoker(h util.Uint160, signers ...Signer) *ContractInvoker {
return &ContractInvoker{
Executor: e,
Hash: h,
Signers: signers,
}
}
// CommitteeInvoker creates new ContractInvoker for contract with hash h and committee multisignature signer. // CommitteeInvoker creates new ContractInvoker for contract with hash h and committee multisignature signer.
func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker { func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker {
return &ContractInvoker{ return &ContractInvoker{
@ -38,7 +47,7 @@ func (e *Executor) ValidatorInvoker(h util.Uint160) *ContractInvoker {
} }
// TestInvoke creates test VM and invokes method with args. // TestInvoke creates test VM and invokes method with args.
func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interface{}) (*vm.Stack, error) { func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...interface{}) (*vm.Stack, error) {
tx := c.PrepareInvokeNoSign(t, method, args...) tx := c.PrepareInvokeNoSign(t, method, args...)
b := c.NewUnsignedBlock(t, tx) b := c.NewUnsignedBlock(t, tx)
ic := c.Chain.GetTestVM(trigger.Application, tx, b) ic := c.Chain.GetTestVM(trigger.Application, tx, b)
@ -57,18 +66,18 @@ func (c *ContractInvoker) WithSigners(signers ...Signer) *ContractInvoker {
} }
// PrepareInvoke creates new invocation transaction. // PrepareInvoke creates new invocation transaction.
func (c *ContractInvoker) PrepareInvoke(t *testing.T, method string, args ...interface{}) *transaction.Transaction { func (c *ContractInvoker) PrepareInvoke(t testing.TB, method string, args ...interface{}) *transaction.Transaction {
return c.Executor.NewTx(t, c.Signers, c.Hash, method, args...) return c.Executor.NewTx(t, c.Signers, c.Hash, method, args...)
} }
// PrepareInvokeNoSign creates new unsigned invocation transaction. // PrepareInvokeNoSign creates new unsigned invocation transaction.
func (c *ContractInvoker) PrepareInvokeNoSign(t *testing.T, method string, args ...interface{}) *transaction.Transaction { func (c *ContractInvoker) PrepareInvokeNoSign(t testing.TB, method string, args ...interface{}) *transaction.Transaction {
return c.Executor.NewUnsignedTx(t, c.Hash, method, args...) return c.Executor.NewUnsignedTx(t, c.Hash, method, args...)
} }
// Invoke invokes method with args, persists transaction and checks the result. // Invoke invokes method with args, persists transaction and checks the result.
// Returns transaction hash. // Returns transaction hash.
func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string, args ...interface{}) util.Uint256 { func (c *ContractInvoker) Invoke(t testing.TB, result interface{}, method string, args ...interface{}) util.Uint256 {
tx := c.PrepareInvoke(t, method, args...) tx := c.PrepareInvoke(t, method, args...)
c.AddNewBlock(t, tx) c.AddNewBlock(t, tx)
c.CheckHalt(t, tx.Hash(), stackitem.Make(result)) c.CheckHalt(t, tx.Hash(), stackitem.Make(result))
@ -77,7 +86,7 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string
// InvokeAndCheck invokes method with args, persists transaction and checks the result // InvokeAndCheck invokes method with args, persists transaction and checks the result
// using provided function. Returns transaction hash. // using provided function. Returns transaction hash.
func (c *ContractInvoker) InvokeAndCheck(t *testing.T, checkResult func(t *testing.T, stack []stackitem.Item), method string, args ...interface{}) util.Uint256 { func (c *ContractInvoker) InvokeAndCheck(t testing.TB, checkResult func(t testing.TB, stack []stackitem.Item), method string, args ...interface{}) util.Uint256 {
tx := c.PrepareInvoke(t, method, args...) tx := c.PrepareInvoke(t, method, args...)
c.AddNewBlock(t, tx) c.AddNewBlock(t, tx)
aer, err := c.Chain.GetAppExecResults(tx.Hash(), trigger.Application) aer, err := c.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
@ -90,7 +99,7 @@ func (c *ContractInvoker) InvokeAndCheck(t *testing.T, checkResult func(t *testi
} }
// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction. // InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction.
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 { func (c *ContractInvoker) InvokeWithFeeFail(t testing.TB, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
tx := c.PrepareInvokeNoSign(t, method, args...) tx := c.PrepareInvokeNoSign(t, method, args...)
c.Executor.SignTx(t, tx, sysFee, c.Signers...) c.Executor.SignTx(t, tx, sysFee, c.Signers...)
c.AddNewBlock(t, tx) c.AddNewBlock(t, tx)
@ -100,7 +109,7 @@ func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee
// InvokeFail invokes method with args, persists transaction and checks the error message. // InvokeFail invokes method with args, persists transaction and checks the error message.
// Returns transaction hash. // Returns transaction hash.
func (c *ContractInvoker) InvokeFail(t *testing.T, message string, method string, args ...interface{}) { func (c *ContractInvoker) InvokeFail(t testing.TB, message string, method string, args ...interface{}) {
tx := c.PrepareInvoke(t, method, args...) tx := c.PrepareInvoke(t, method, args...)
c.AddNewBlock(t, tx) c.AddNewBlock(t, tx)
c.CheckFault(t, tx.Hash(), message) c.CheckFault(t, tx.Hash(), message)

View file

@ -25,7 +25,7 @@ type Contract struct {
var contracts = make(map[string]*Contract) var contracts = make(map[string]*Contract)
// CompileSource compiles contract from reader and returns it's NEF, manifest and hash. // CompileSource compiles contract from reader and returns it's NEF, manifest and hash.
func CompileSource(t *testing.T, sender util.Uint160, src io.Reader, opts *compiler.Options) *Contract { func CompileSource(t testing.TB, sender util.Uint160, src io.Reader, opts *compiler.Options) *Contract {
// nef.NewFile() cares about version a lot. // nef.NewFile() cares about version a lot.
config.Version = "neotest" config.Version = "neotest"
@ -43,7 +43,7 @@ func CompileSource(t *testing.T, sender util.Uint160, src io.Reader, opts *compi
} }
// CompileFile compiles contract from file and returns it's NEF, manifest and hash. // CompileFile compiles contract from file and returns it's NEF, manifest and hash.
func CompileFile(t *testing.T, sender util.Uint160, srcPath string, configPath string) *Contract { func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath string) *Contract {
if c, ok := contracts[srcPath]; ok { if c, ok := contracts[srcPath]; ok {
return c return c
} }
@ -66,6 +66,8 @@ func CompileFile(t *testing.T, sender util.Uint160, srcPath string, configPath s
o.Permissions[i] = manifest.Permission(conf.Permissions[i]) o.Permissions[i] = manifest.Permission(conf.Permissions[i])
} }
o.SafeMethods = conf.SafeMethods o.SafeMethods = conf.SafeMethods
o.Overloads = conf.Overloads
o.SourceURL = conf.SourceURL
m, err := compiler.CreateManifest(di, o) m, err := compiler.CreateManifest(di, o)
require.NoError(t, err) require.NoError(t, err)

View file

@ -162,7 +162,7 @@ func (m multiSigner) Single(n int) SingleSigner {
return NewSingleSigner(wallet.NewAccountFromPrivateKey(m.accounts[n].PrivateKey())) return NewSingleSigner(wallet.NewAccountFromPrivateKey(m.accounts[n].PrivateKey()))
} }
func checkMultiSigner(t *testing.T, s Signer) { func checkMultiSigner(t testing.TB, s Signer) {
ms, ok := s.(multiSigner) ms, ok := s.(multiSigner)
require.True(t, ok, "expected to be a multi-signer") require.True(t, ok, "expected to be a multi-signer")

View file

@ -61,17 +61,17 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const genesisBlockHash = "a4ae00f6ac7496cac14e709fbf8b8ecb4c9831d8a6ee396056af9350fcf22671" const genesisBlockHash = "f42e2ae74bbea6aa1789fdc4efa35ad55b04335442637c091eafb5b0e779dae7"
const testContractHash = "1ab08f5508edafa6f28e3db3227442a9e70aac52" const testContractHash = "2db7d679c538ace5f00495c9e9d8ea95f1e0f5a5"
const deploymentTxHash = "017c9edb217477aeb3e0c35462361209fdb7bf104dc8e285e2385af8713926b4" const deploymentTxHash = "496bccb5cb0a008ef9b7a32c459e508ef24fbb0830f82bac9162afa4ca804839"
const ( const (
verifyContractHash = "7deef31e5c616e157cdf02a5446f36d0a4eead52" verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c"
verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A=" verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A="
verifyWithArgsContractHash = "6df009754ce475a6a5730c9e488f80e8e47bc1f1" verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781"
nnsContractHash = "1a7530a4c6cfdd40ffed40775aa5453febab24c0" nnsContractHash = "ee92563903e4efd53565784080b2dbdc5c37e21f"
nnsToken1ID = "6e656f2e636f6d" nnsToken1ID = "6e656f2e636f6d"
nfsoContractHash = "aaf8913c501e25c42877e79f04cb7c2c1ab47e57" nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486" nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA" invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
) )
@ -279,7 +279,7 @@ var rpcTestCases = map[string][]rpcTestCase{
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
return &map[string]interface{}{ return &map[string]interface{}{
"name": "neo.com", "name": "neo.com",
"expiration": "HrL+G4YB", "expiration": "lhbLRl0B",
} }
}, },
}, },
@ -882,7 +882,7 @@ var rpcTestCases = map[string][]rpcTestCase{
name: "positive, with notifications", name: "positive, with notifications",
params: `["` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`, params: `["` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
script := []byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0x0b, 0x13, 0xc0, 0x1f, 0x0c, 0x08, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x0c, 0x14, 0xc0, 0x24, 0xab, 0xeb, 0x3f, 0x45, 0xa5, 0x5a, 0x77, 0x40, 0xed, 0xff, 0x40, 0xdd, 0xcf, 0xc6, 0xa4, 0x30, 0x75, 0x1a, 0x41, 0x62, 0x7d, 0x5b, 0x52} script := []byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52}
return &result.Invoke{ return &result.Invoke{
State: "HALT", State: "HALT",
GasConsumed: 32167260, GasConsumed: 32167260,
@ -915,7 +915,7 @@ var rpcTestCases = map[string][]rpcTestCase{
chg := []storage.Operation{{ chg := []storage.Operation{{
State: "Changed", State: "Changed",
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb}, Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb},
Value: []byte{0xe8, 0x80, 0x64, 0xcb, 0x53, 0x79, 0x12}, Value: []byte{0x1e, 0xb, 0xca, 0xeb, 0x53, 0x79, 0x12},
}, { }, {
State: "Added", State: "Added",
Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb}, Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb},
@ -927,7 +927,7 @@ var rpcTestCases = map[string][]rpcTestCase{
}, { }, {
State: "Changed", State: "Changed",
Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}, Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2},
Value: []byte{0x41, 0x01, 0x21, 0x05, 0x9e, 0x0b, 0x0b, 0x18, 0x0b}, Value: []byte{0x41, 0x01, 0x21, 0x05, 0xf6, 0x99, 0x28, 0x2d, 0xb},
}} }}
// Can be returned in any order. // Can be returned in any order.
assert.ElementsMatch(t, chg, res.Diagnostics.Changes) assert.ElementsMatch(t, chg, res.Diagnostics.Changes)
@ -937,7 +937,7 @@ var rpcTestCases = map[string][]rpcTestCase{
name: "positive, verbose", name: "positive, verbose",
params: `["` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`, params: `["` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`,
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
script := []byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14, 0xc0, 0x24, 0xab, 0xeb, 0x3f, 0x45, 0xa5, 0x5a, 0x77, 0x40, 0xed, 0xff, 0x40, 0xdd, 0xcf, 0xc6, 0xa4, 0x30, 0x75, 0x1a, 0x41, 0x62, 0x7d, 0x5b, 0x52} script := []byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52}
stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib) stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib)
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib) cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
return &result.Invoke{ return &result.Invoke{
@ -1205,12 +1205,12 @@ var rpcTestCases = map[string][]rpcTestCase{
"sendrawtransaction": { "sendrawtransaction": {
{ {
name: "positive", name: "positive",
params: `["ADQSAADA2KcAAAAAABDiEgAAAAAAgBYAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsgEAYBDAAwDodkgXAAAADBQRJlu0FyUAQb4E6PokDjj1fB5WmwwU7p6iLCfjS9AUj8QQjgj3To9QSLIUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQFCDEBRp0p08GFA2rYC/Xrol8DIhXEMfVMbUJEYer1RqZSatmTjUJE9fnZtDGkQEX/zQ7yOhbnIPAZIrllUTuUBskhUKAwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CQVbnsyc="]`, params: `["ABsAAACWP5gAAAAAAEDaEgAAAAAAFgAAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsoAAXgsDAOh2SBcAAAAMFBEmW7QXJQBBvgTo+iQOOPV8HlabDBTunqIsJ+NL0BSPxBCOCPdOj1BIshTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1IBQgxAOv87rSn7OV7Y/wuVE58QaSz0o0wv37hWY08RZFP2kYYgSPvemZiT69wf6QeAUTABJ1JosxgIUory9vXv0kkpXSgMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwkFW57Mn"]`,
result: func(e *executor) interface{} { return &result.RelayResult{} }, result: func(e *executor) interface{} { return &result.RelayResult{} },
check: func(t *testing.T, e *executor, inv interface{}) { check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.RelayResult) res, ok := inv.(*result.RelayResult)
require.True(t, ok) require.True(t, ok)
expectedHash := "8ea251d812fbbdecaebfc164fb6afbd78b7db94f7dacb69421cd5d4e364522d2" expectedHash := "acc3e13102c211068d06ff64034d6f7e2d4db00c1703d0dec8afa73560664fe1"
assert.Equal(t, expectedHash, res.Hash.StringLE()) assert.Equal(t, expectedHash, res.Hash.StringLE())
}, },
}, },
@ -1945,12 +1945,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
require.NoError(t, json.Unmarshal(res, actual)) require.NoError(t, json.Unmarshal(res, actual))
checkNep17TransfersAux(t, e, actual, sent, rcvd) checkNep17TransfersAux(t, e, actual, sent, rcvd)
} }
t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{14, 15, 16, 17}, []int{3, 4}) }) t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{17, 18, 19, 20}, []int{3, 4}) })
t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) }) t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) })
t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{11, 12}, []int{2}) }) t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{14, 15}, []int{2}) })
t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{14}, []int{3}) }) t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{17}, []int{3}) })
t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{13, 14}, []int{3}) }) t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{16, 17}, []int{3}) })
t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{15, 16}, []int{4}) }) t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{18, 19}, []int{4}) })
}) })
} }
@ -2086,7 +2086,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
}, },
{ {
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),
Amount: "46748035310", Amount: "47102293830",
LastUpdated: 19, LastUpdated: 19,
}}, }},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
@ -2198,7 +2198,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
} }
func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) { func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) {
checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}) checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8})
} }
func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) {
@ -2224,8 +2224,11 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
blockPutNewTestValue, err := e.chain.GetBlock(e.chain.GetHeaderHash(16)) // invoke `put` method of `test_contract.go` with `testkey`, `newtestvalue` args blockPutNewTestValue, err := e.chain.GetBlock(e.chain.GetHeaderHash(16)) // invoke `put` method of `test_contract.go` with `testkey`, `newtestvalue` args
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(blockPutNewTestValue.Transactions)) require.Equal(t, 4, len(blockPutNewTestValue.Transactions))
txPutNewTestValue := blockPutNewTestValue.Transactions[0] txPutNewTestValue := blockPutNewTestValue.Transactions[0]
txPutValue1 := blockPutNewTestValue.Transactions[1] // invoke `put` method of `test_contract.go` with `aa`, `v1` args
txPutValue2 := blockPutNewTestValue.Transactions[2] // invoke `put` method of `test_contract.go` with `aa10`, `v2` args
txPutValue3 := blockPutNewTestValue.Transactions[3] // invoke `put` method of `test_contract.go` with `aa50`, `v3` args
blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(15)) // add type A record to `neo.com` domain via NNS blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(15)) // add type A record to `neo.com` domain via NNS
require.NoError(t, err) require.NoError(t, err)
@ -2336,6 +2339,30 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc
Index: 17, Index: 17,
TxHash: blockDeploy5.Hash(), TxHash: blockDeploy5.Hash(),
}, },
{
Timestamp: blockPutNewTestValue.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutValue3.SystemFee + txPutValue3.NetworkFee).String(),
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{
Timestamp: blockPutNewTestValue.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutValue2.SystemFee + txPutValue2.NetworkFee).String(),
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{
Timestamp: blockPutNewTestValue.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn
Amount: big.NewInt(txPutValue1.SystemFee + txPutValue1.NetworkFee).String(),
Index: 16,
TxHash: blockPutNewTestValue.Hash(),
},
{ {
Timestamp: blockPutNewTestValue.Timestamp, Timestamp: blockPutNewTestValue.Timestamp,
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),

Binary file not shown.

View file

@ -0,0 +1,2 @@
name: "Verify"
sourceurl: https://github.com/nspcc-dev/neo-go/

View file

@ -0,0 +1,2 @@
name: "Verify with args"
sourceurl: https://github.com/nspcc-dev/neo-go/