neo-go/pkg/rpcclient/actor/actor_test.go
Roman Khimov a327a82085 actor: add a new WaitSuccess API
Most of the time people are interested in successful executions. Unfortunately,
unwrap package can't help here because of a different result structure (some
interface abstract can help, but it's still mostly stack-oriented and sessions
can be a problem), so this additional interface is needed.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2024-06-21 18:39:02 +03:00

334 lines
9.2 KiB
Go

package actor
import (
"context"
"errors"
"sync/atomic"
"testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"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/neorpc/result"
"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/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
type RPCClient struct {
err error
invRes *result.Invoke
netFee int64
bCount atomic.Uint32
version *result.Version
hash util.Uint256
appLog *result.ApplicationLog
context context.Context
}
func (r *RPCClient) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
return r.invRes, r.err
}
func (r *RPCClient) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
return r.invRes, r.err
}
func (r *RPCClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
return r.invRes, r.err
}
func (r *RPCClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
return r.netFee, r.err
}
func (r *RPCClient) GetBlockCount() (uint32, error) {
return r.bCount.Load(), r.err
}
func (r *RPCClient) GetVersion() (*result.Version, error) {
verCopy := *r.version
return &verCopy, r.err
}
func (r *RPCClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
return r.hash, r.err
}
func (r *RPCClient) TerminateSession(sessionID uuid.UUID) (bool, error) {
return false, nil // Just a stub, unused by actor.
}
func (r *RPCClient) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
return nil, nil // Just a stub, unused by actor.
}
func (r *RPCClient) Context() context.Context {
if r.context == nil {
return context.Background()
}
return r.context
}
func (r *RPCClient) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*result.ApplicationLog, error) {
if r.appLog != nil {
return r.appLog, nil
}
return nil, errors.New("not found")
}
func testRPCAndAccount(t *testing.T) (*RPCClient, *wallet.Account) {
client := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
}
acc, err := wallet.NewAccount()
require.NoError(t, err)
return client, acc
}
func TestNew(t *testing.T) {
client, acc := testRPCAndAccount(t)
// No signers.
_, err := New(client, nil)
require.Error(t, err)
_, err = New(client, []SignerAccount{})
require.Error(t, err)
_, err = NewTuned(client, []SignerAccount{}, NewDefaultOptions())
require.Error(t, err)
// Good simple.
a, err := NewSimple(client, acc)
require.NoError(t, err)
require.Equal(t, 1, len(a.signers))
require.Equal(t, 1, len(a.txSigners))
require.Equal(t, transaction.CalledByEntry, a.signers[0].Signer.Scopes)
require.Equal(t, transaction.CalledByEntry, a.txSigners[0].Scopes)
// Contractless account.
badAcc, err := wallet.NewAccount()
require.NoError(t, err)
badAccHash := badAcc.Contract.ScriptHash()
badAcc.Contract = nil
signers := []SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc,
}, {
Signer: transaction.Signer{
Account: badAccHash,
Scopes: transaction.CalledByEntry,
},
Account: badAcc,
}}
_, err = New(client, signers)
require.Error(t, err)
// GetVersion returning error.
client.err = errors.New("bad")
_, err = NewSimple(client, acc)
require.Error(t, err)
client.err = nil
// Account mismatch.
acc2, err := wallet.NewAccount()
require.NoError(t, err)
signers = []SignerAccount{{
Signer: transaction.Signer{
Account: acc2.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc,
}, {
Signer: transaction.Signer{
Account: acc2.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: acc2,
}}
_, err = New(client, signers)
require.Error(t, err)
// Good multiaccount.
signers[0].Signer.Account = acc.Contract.ScriptHash()
a, err = New(client, signers)
require.NoError(t, err)
require.Equal(t, 2, len(a.signers))
require.Equal(t, 2, len(a.txSigners))
// Good tuned
opts := Options{
Attributes: []transaction.Attribute{{Type: transaction.HighPriority}},
}
a, err = NewTuned(client, signers, opts)
require.NoError(t, err)
require.Equal(t, 1, len(a.opts.Attributes))
}
func TestSimpleWrappers(t *testing.T) {
client, acc := testRPCAndAccount(t)
origVer := *client.version
a, err := NewSimple(client, acc)
require.NoError(t, err)
client.netFee = 42
nf, err := a.CalculateNetworkFee(new(transaction.Transaction))
require.NoError(t, err)
require.Equal(t, int64(42), nf)
client.bCount.Store(100500)
bc, err := a.GetBlockCount()
require.NoError(t, err)
require.Equal(t, uint32(100500), bc)
require.Equal(t, netmode.UnitTestNet, a.GetNetwork())
client.version.Protocol.Network = netmode.TestNet
require.Equal(t, netmode.UnitTestNet, a.GetNetwork())
require.Equal(t, origVer, a.GetVersion())
a, err = NewSimple(client, acc)
require.NoError(t, err)
require.Equal(t, netmode.TestNet, a.GetNetwork())
require.Equal(t, *client.version, a.GetVersion())
client.hash = util.Uint256{1, 2, 3}
h, vub, err := a.Send(&transaction.Transaction{ValidUntilBlock: 123})
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(123), vub)
}
func TestSign(t *testing.T) {
client, acc := testRPCAndAccount(t)
acc2, err := wallet.NewAccount()
require.NoError(t, err)
a, err := New(client, []SignerAccount{{
Signer: transaction.Signer{
Account: acc.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc,
}, {
Signer: transaction.Signer{
Account: acc2.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: &wallet.Account{ // Looks like acc2, but has no private key.
Address: acc2.Address,
EncryptedWIF: acc2.EncryptedWIF,
Contract: acc2.Contract,
},
}})
require.NoError(t, err)
script := []byte{1, 2, 3}
client.hash = util.Uint256{2, 5, 6}
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
tx, err := a.MakeUnsignedRun(script, nil)
require.NoError(t, err)
require.Error(t, a.Sign(tx))
_, _, err = a.SignAndSend(tx)
require.Error(t, err)
}
func TestSenders(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
script := []byte{1, 2, 3}
// Bad.
client.invRes = &result.Invoke{State: "FAULT", GasConsumed: 3, Script: script}
_, _, err = a.SendCall(util.Uint160{1}, "method", 42)
require.Error(t, err)
_, _, err = a.SendTunedCall(util.Uint160{1}, "method", nil, nil, 42)
require.Error(t, err)
_, _, err = a.SendRun(script)
require.Error(t, err)
_, _, err = a.SendTunedRun(script, nil, nil)
require.Error(t, err)
_, _, err = a.SendUncheckedRun(script, 1, nil, func(t *transaction.Transaction) error {
return errors.New("bad")
})
require.Error(t, err)
// Good.
client.hash = util.Uint256{2, 5, 6}
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
h, vub, err := a.SendCall(util.Uint160{1}, "method", 42)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendTunedCall(util.Uint160{1}, "method", nil, nil, 42)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendRun(script)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendTunedRun(script, nil, nil)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
h, vub, err = a.SendUncheckedRun(script, 1, nil, nil)
require.NoError(t, err)
require.Equal(t, client.hash, h)
require.Equal(t, uint32(8), vub)
}
func TestSender(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
require.Equal(t, acc.ScriptHash(), a.Sender())
}
func TestWaitSuccess(t *testing.T) {
client, acc := testRPCAndAccount(t)
a, err := NewSimple(client, acc)
require.NoError(t, err)
someErr := errors.New("someErr")
_, err = a.WaitSuccess(util.Uint256{}, 0, someErr)
require.ErrorIs(t, err, someErr)
cont := util.Uint256{1, 2, 3}
ex := state.Execution{
Trigger: trigger.Application,
VMState: vmstate.Halt,
GasConsumed: 123,
Stack: []stackitem.Item{stackitem.Null{}},
}
applog := &result.ApplicationLog{
Container: cont,
IsTransaction: true,
Executions: []state.Execution{ex},
}
client.appLog = applog
client.appLog.Executions[0].VMState = vmstate.Fault
_, err = a.WaitSuccess(util.Uint256{}, 0, nil)
require.ErrorIs(t, err, ErrExecFailed)
client.appLog.Executions[0].VMState = vmstate.Halt
res, err := a.WaitSuccess(util.Uint256{}, 0, nil)
require.NoError(t, err)
require.Equal(t, &state.AppExecResult{
Container: cont,
Execution: ex,
}, res)
}