6c0faa4ea3
We need to define network fee for each of cosigners, and the only way to do it is to access the cosigner's script.
233 lines
6.2 KiB
Go
233 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"math"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nspcc-dev/neo-go/cli/input"
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
|
"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/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
"github.com/nspcc-dev/neo-go/pkg/network"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpc/server"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/urfave/cli"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zaptest"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
const (
|
|
validatorWIF = "KxyjQ8eUa4FHt3Gvioyt1Wz29cTUrE4eTqX3yFSk1YFCsPL8uNsY"
|
|
validatorAddr = "NVNvVRW5Q5naSx2k2iZm7xRgtRNGuZppAK"
|
|
multisigAddr = "NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY"
|
|
|
|
validatorWallet = "testdata/wallet1_solo.json"
|
|
)
|
|
|
|
var (
|
|
validatorHash, _ = address.StringToUint160(validatorAddr)
|
|
validatorPriv, _ = keys.NewPrivateKeyFromWIF(validatorWIF)
|
|
)
|
|
|
|
// executor represents context for a test instance.
|
|
// It can be safely used in multiple tests, but not in parallel.
|
|
type executor struct {
|
|
// CLI is a cli application to test.
|
|
CLI *cli.App
|
|
// Chain is a blockchain instance (can be empty).
|
|
Chain *core.Blockchain
|
|
// RPC is an RPC server to query (can be empty).
|
|
RPC *server.Server
|
|
// NetSrv is a network server (can be empty).
|
|
NetSrv *network.Server
|
|
// Out contains command output.
|
|
Out *bytes.Buffer
|
|
// Err contains command errors.
|
|
Err *bytes.Buffer
|
|
// In contains command input.
|
|
In *bytes.Buffer
|
|
}
|
|
|
|
func newTestChain(t *testing.T, f func(*config.Config)) (*core.Blockchain, *server.Server, *network.Server) {
|
|
configPath := "../config/protocol.unit_testnet.single.yml"
|
|
cfg, err := config.LoadFile(configPath)
|
|
require.NoError(t, err, "could not load config")
|
|
if f != nil {
|
|
f(&cfg)
|
|
}
|
|
|
|
memoryStore := storage.NewMemoryStore()
|
|
logger := zaptest.NewLogger(t)
|
|
chain, err := core.NewBlockchain(memoryStore, cfg.ProtocolConfiguration, logger)
|
|
require.NoError(t, err, "could not create chain")
|
|
|
|
go chain.Run()
|
|
|
|
serverConfig := network.NewServerConfig(cfg)
|
|
netSrv, err := network.NewServer(serverConfig, chain, zap.NewNop())
|
|
require.NoError(t, err)
|
|
go netSrv.Start(make(chan error, 1))
|
|
rpcServer := server.New(chain, cfg.ApplicationConfiguration.RPC, netSrv, nil, logger)
|
|
errCh := make(chan error, 2)
|
|
rpcServer.Start(errCh)
|
|
|
|
return chain, &rpcServer, netSrv
|
|
}
|
|
|
|
func newExecutor(t *testing.T, needChain bool) *executor {
|
|
return newExecutorWithConfig(t, needChain, nil)
|
|
}
|
|
|
|
func newExecutorWithConfig(t *testing.T, needChain bool, f func(*config.Config)) *executor {
|
|
e := &executor{
|
|
CLI: newApp(),
|
|
Out: bytes.NewBuffer(nil),
|
|
Err: bytes.NewBuffer(nil),
|
|
In: bytes.NewBuffer(nil),
|
|
}
|
|
e.CLI.Writer = e.Out
|
|
e.CLI.ErrWriter = e.Err
|
|
if needChain {
|
|
e.Chain, e.RPC, e.NetSrv = newTestChain(t, f)
|
|
}
|
|
t.Cleanup(func() {
|
|
e.Close(t)
|
|
})
|
|
return e
|
|
}
|
|
|
|
func (e *executor) Close(t *testing.T) {
|
|
input.Terminal = nil
|
|
if e.RPC != nil {
|
|
require.NoError(t, e.RPC.Shutdown())
|
|
}
|
|
if e.NetSrv != nil {
|
|
e.NetSrv.Shutdown()
|
|
}
|
|
if e.Chain != nil {
|
|
e.Chain.Close()
|
|
}
|
|
}
|
|
|
|
// GetTransaction returns tx with hash h after it has persisted.
|
|
// If it is in mempool, we can just wait for the next block, otherwise
|
|
// it must be already in chain. 1 second is time per block in a unittest chain.
|
|
func (e *executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Transaction, uint32) {
|
|
var tx *transaction.Transaction
|
|
var height uint32
|
|
require.Eventually(t, func() bool {
|
|
var err error
|
|
tx, height, err = e.Chain.GetTransaction(h)
|
|
return err == nil && height != math.MaxUint32
|
|
}, time.Second*2, time.Millisecond*100, "too long time waiting for block")
|
|
return tx, height
|
|
}
|
|
|
|
func (e *executor) getNextLine(t *testing.T) string {
|
|
line, err := e.Out.ReadString('\n')
|
|
require.NoError(t, err)
|
|
return strings.TrimSuffix(line, "\n")
|
|
}
|
|
|
|
func (e *executor) checkNextLine(t *testing.T, expected string) {
|
|
line := e.getNextLine(t)
|
|
e.checkLine(t, line, expected)
|
|
}
|
|
|
|
func (e *executor) checkLine(t *testing.T, line, expected string) {
|
|
require.Regexp(t, expected, line)
|
|
}
|
|
|
|
func (e *executor) checkEOF(t *testing.T) {
|
|
_, err := e.Out.ReadString('\n')
|
|
require.True(t, errors.Is(err, io.EOF))
|
|
}
|
|
|
|
func setExitFunc() <-chan int {
|
|
ch := make(chan int, 1)
|
|
cli.OsExiter = func(code int) {
|
|
ch <- code
|
|
}
|
|
return ch
|
|
}
|
|
|
|
func checkExit(t *testing.T, ch <-chan int, code int) {
|
|
select {
|
|
case c := <-ch:
|
|
require.Equal(t, code, c)
|
|
default:
|
|
if code != 0 {
|
|
require.Fail(t, "no exit was called")
|
|
}
|
|
}
|
|
}
|
|
|
|
// RunWithError runs command and checks that is exits with error.
|
|
func (e *executor) RunWithError(t *testing.T, args ...string) {
|
|
ch := setExitFunc()
|
|
require.Error(t, e.run(args...))
|
|
checkExit(t, ch, 1)
|
|
}
|
|
|
|
// Run runs command and checks that there were no errors.
|
|
func (e *executor) Run(t *testing.T, args ...string) {
|
|
ch := setExitFunc()
|
|
require.NoError(t, e.run(args...))
|
|
checkExit(t, ch, 0)
|
|
}
|
|
func (e *executor) run(args ...string) error {
|
|
e.Out.Reset()
|
|
e.Err.Reset()
|
|
input.Terminal = term.NewTerminal(input.ReadWriter{
|
|
Reader: e.In,
|
|
Writer: ioutil.Discard,
|
|
}, "")
|
|
err := e.CLI.Run(args)
|
|
input.Terminal = nil
|
|
e.In.Reset()
|
|
return err
|
|
}
|
|
|
|
func (e *executor) checkTxPersisted(t *testing.T, prefix ...string) (*transaction.Transaction, uint32) {
|
|
line, err := e.Out.ReadString('\n')
|
|
require.NoError(t, err)
|
|
|
|
line = strings.TrimSpace(line)
|
|
if len(prefix) > 0 {
|
|
line = strings.TrimPrefix(line, prefix[0])
|
|
}
|
|
h, err := util.Uint256DecodeStringLE(line)
|
|
require.NoError(t, err, "can't decode tx hash: %s", line)
|
|
|
|
tx, height := e.GetTransaction(t, h)
|
|
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(aer))
|
|
require.Equal(t, vm.HaltState, aer[0].VMState)
|
|
return tx, height
|
|
}
|
|
|
|
func generateKeys(t *testing.T, n int) ([]*keys.PrivateKey, keys.PublicKeys) {
|
|
privs := make([]*keys.PrivateKey, n)
|
|
pubs := make(keys.PublicKeys, n)
|
|
for i := range privs {
|
|
var err error
|
|
privs[i], err = keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
pubs[i] = privs[i].PublicKey()
|
|
}
|
|
return privs, pubs
|
|
}
|