neoneo-go/pkg/rpcclient/notary/actor_test.go
Ekaterina Pavlova 4c6dca876c rpcclient: move Waiter to a separate package
There are use-cases when not only Actor, but also Invoker and even
simple RPC client must wait (e.g. sendtx or dumptx CLI commands). Actor
requires optional signers in constructor, and it's not always
appropriate to create Actor only to be able to use Waiter, sometimes
it's needed to use only Waiter without Actor.

Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
2023-12-28 15:30:15 +03:00

587 lines
16 KiB
Go

package notary
import (
"context"
"errors"
"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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"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/opcode"
"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 uint32
version *result.Version
hash util.Uint256
nhash util.Uint256
mirror bool
applog *result.ApplicationLog
}
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, 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) SubmitP2PNotaryRequest(req *payload.P2PNotaryRequest) (util.Uint256, error) {
if r.mirror {
return req.FallbackTransaction.Hash(), nil
}
return r.nhash, 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 {
return context.Background()
}
func (r *RPCClient) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*result.ApplicationLog, error) {
return r.applog, nil
}
var _ = waiter.RPCPollingWaiter(&RPCClient{})
func TestNewActor(t *testing.T) {
rc := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
}
_, err := NewActor(rc, nil, nil)
require.Error(t, err)
var (
keyz [4]*keys.PrivateKey
accs [4]*wallet.Account
faccs [4]*wallet.Account
pkeys [4]*keys.PublicKey
)
for i := range accs {
keyz[i], err = keys.NewPrivateKey()
require.NoError(t, err)
accs[i] = wallet.NewAccountFromPrivateKey(keyz[i])
pkeys[i] = keyz[i].PublicKey()
faccs[i] = FakeSimpleAccount(pkeys[i])
}
var multiAccs [4]*wallet.Account
for i := range accs {
multiAccs[i] = &wallet.Account{}
*multiAccs[i] = *accs[i]
require.NoError(t, multiAccs[i].ConvertMultisig(smartcontract.GetDefaultHonestNodeCount(len(pkeys)), pkeys[:]))
}
// nil Contract
badMultiAcc0 := &wallet.Account{}
*badMultiAcc0 = *multiAccs[0]
badMultiAcc0.Contract = nil
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: multiAccs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: badMultiAcc0,
}}, accs[0])
require.Error(t, err)
// Non-standard script.
badMultiAcc0.Contract = &wallet.Contract{}
*badMultiAcc0.Contract = *multiAccs[0].Contract
badMultiAcc0.Contract.Script = append(badMultiAcc0.Contract.Script, byte(opcode.NOP))
badMultiAcc0.Address = address.Uint160ToString(badMultiAcc0.Contract.ScriptHash())
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: badMultiAcc0.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: badMultiAcc0,
}}, accs[0])
require.Error(t, err)
// Too many keys
var (
manyKeys [256]*keys.PrivateKey
manyPkeys [256]*keys.PublicKey
)
for i := range manyKeys {
manyKeys[i], err = keys.NewPrivateKey()
require.NoError(t, err)
manyPkeys[i] = manyKeys[i].PublicKey()
}
bigMultiAcc := &wallet.Account{}
*bigMultiAcc = *wallet.NewAccountFromPrivateKey(manyKeys[0])
require.NoError(t, bigMultiAcc.ConvertMultisig(129, manyPkeys[:]))
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: bigMultiAcc.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: bigMultiAcc,
}}, wallet.NewAccountFromPrivateKey(manyKeys[0]))
require.Error(t, err)
// No contract in the simple account.
badSimple0 := &wallet.Account{}
*badSimple0 = *accs[0]
badSimple0.Contract = nil
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: multiAccs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: multiAccs[0],
}}, badSimple0)
require.Error(t, err)
// Simple account that can't sign.
badSimple0 = FakeSimpleAccount(pkeys[0])
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: multiAccs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: multiAccs[0],
}}, badSimple0)
require.Error(t, err)
// Multisig account instead of simple one.
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: multiAccs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: multiAccs[0],
}}, multiAccs[0])
require.Error(t, err)
// Main actor freaking out on hash mismatch.
_, err = NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: accs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: multiAccs[0],
}}, accs[0])
require.Error(t, err)
// FB actor freaking out on hash mismatch.
opts := NewDefaultActorOptions(NewReader(invoker.New(rc, nil)), accs[0])
opts.FbSigner.Signer.Account = multiAccs[0].Contract.ScriptHash()
_, err = NewTunedActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: multiAccs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: multiAccs[0],
}}, opts)
require.Error(t, err)
// Good, one multisig.
multi0, err := NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: multiAccs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: multiAccs[0],
}}, accs[0])
require.NoError(t, err)
script := []byte{byte(opcode.RET)}
rc.invRes = &result.Invoke{
State: "HALT",
GasConsumed: 3,
Script: script,
Stack: []stackitem.Item{stackitem.Make(42)},
}
tx, err := multi0.MakeRun(script)
require.NoError(t, err)
require.Equal(t, 1, len(tx.Attributes))
require.Equal(t, transaction.NotaryAssistedT, tx.Attributes[0].Type)
require.Equal(t, &transaction.NotaryAssisted{NKeys: 4}, tx.Attributes[0].Value)
// Good, 4 single sigs with one that can sign and one contract.
single4, err := NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: accs[0].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: accs[0],
}, {
Signer: transaction.Signer{
Account: faccs[1].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: faccs[1],
}, {
Signer: transaction.Signer{
Account: faccs[2].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: faccs[2],
}, {
Signer: transaction.Signer{
Account: accs[3].Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: faccs[3],
}, {
Signer: transaction.Signer{
Account: util.Uint160{1, 2, 3},
Scopes: transaction.CalledByEntry,
},
Account: FakeContractAccount(util.Uint160{1, 2, 3}),
}}, accs[0])
require.NoError(t, err)
tx, err = single4.MakeRun(script)
require.NoError(t, err)
require.Equal(t, 1, len(tx.Attributes))
require.Equal(t, transaction.NotaryAssistedT, tx.Attributes[0].Type)
require.Equal(t, &transaction.NotaryAssisted{NKeys: 4}, tx.Attributes[0].Value) // One account can sign, three need to collect additional sigs.
}
func TestSendRequestExactly(t *testing.T) {
rc := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
}
key0, err := keys.NewPrivateKey()
require.NoError(t, err)
key1, err := keys.NewPrivateKey()
require.NoError(t, err)
acc0 := wallet.NewAccountFromPrivateKey(key0)
facc1 := FakeSimpleAccount(key1.PublicKey())
act, err := NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: acc0.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc0,
}, {
Signer: transaction.Signer{
Account: facc1.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: facc1,
}}, acc0)
require.NoError(t, err)
script := []byte{byte(opcode.RET)}
mainTx := transaction.New(script, 1)
fbTx := transaction.New(script, 1)
// Hashes mismatch
_, _, _, err = act.SendRequestExactly(mainTx, fbTx)
require.Error(t, err)
// Error returned
rc.err = errors.New("")
_, _, _, err = act.SendRequestExactly(mainTx, fbTx)
require.Error(t, err)
// OK returned
rc.err = nil
rc.nhash = fbTx.Hash()
mHash, fbHash, vub, err := act.SendRequestExactly(mainTx, fbTx)
require.NoError(t, err)
require.Equal(t, mainTx.Hash(), mHash)
require.Equal(t, fbTx.Hash(), fbHash)
require.Equal(t, mainTx.ValidUntilBlock, vub)
}
func TestSendRequest(t *testing.T) {
rc := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
bCount: 42,
}
key0, err := keys.NewPrivateKey()
require.NoError(t, err)
key1, err := keys.NewPrivateKey()
require.NoError(t, err)
acc0 := wallet.NewAccountFromPrivateKey(key0)
facc0 := FakeSimpleAccount(key0.PublicKey())
facc1 := FakeSimpleAccount(key1.PublicKey())
act, err := NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: acc0.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc0,
}, {
Signer: transaction.Signer{
Account: facc1.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: facc1,
}}, acc0)
require.NoError(t, err)
script := []byte{byte(opcode.RET)}
rc.invRes = &result.Invoke{
State: "HALT",
GasConsumed: 3,
Script: script,
Stack: []stackitem.Item{stackitem.Make(42)},
}
mainTx, err := act.MakeRun(script)
require.NoError(t, err)
// No attributes.
fbTx, err := act.FbActor.MakeUnsignedRun(script, nil)
require.NoError(t, err)
fbTx.Attributes = nil
_, _, _, err = act.SendRequest(mainTx, fbTx)
require.Error(t, err)
// Bad NVB.
fbTx, err = act.FbActor.MakeUnsignedRun(script, nil)
require.NoError(t, err)
fbTx.Attributes[1].Type = transaction.HighPriority
fbTx.Attributes[1].Value = nil
_, _, _, err = act.SendRequest(mainTx, fbTx)
require.Error(t, err)
// Bad Conflicts.
fbTx, err = act.FbActor.MakeUnsignedRun(script, nil)
require.NoError(t, err)
fbTx.Attributes[2].Type = transaction.HighPriority
fbTx.Attributes[2].Value = nil
_, _, _, err = act.SendRequest(mainTx, fbTx)
require.Error(t, err)
// GetBlockCount error.
fbTx, err = act.FbActor.MakeUnsignedRun(script, nil)
require.NoError(t, err)
rc.err = errors.New("")
_, _, _, err = act.SendRequest(mainTx, fbTx)
require.Error(t, err)
// Can't sign suddenly.
rc.err = nil
acc0Backup := &wallet.Account{}
*acc0Backup = *acc0
*acc0 = *facc0
fbTx, err = act.FbActor.MakeUnsignedRun(script, nil)
require.NoError(t, err)
_, _, _, err = act.SendRequest(mainTx, fbTx)
require.Error(t, err)
// Good.
*acc0 = *acc0Backup
fbTx, err = act.FbActor.MakeUnsignedRun(script, nil)
require.NoError(t, err)
_, _, _, err = act.SendRequest(mainTx, fbTx)
require.Error(t, err)
}
func TestNotarize(t *testing.T) {
rc := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
bCount: 42,
}
key0, err := keys.NewPrivateKey()
require.NoError(t, err)
key1, err := keys.NewPrivateKey()
require.NoError(t, err)
acc0 := wallet.NewAccountFromPrivateKey(key0)
facc1 := FakeSimpleAccount(key1.PublicKey())
act, err := NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: acc0.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc0,
}, {
Signer: transaction.Signer{
Account: facc1.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: facc1,
}}, acc0)
require.NoError(t, err)
script := []byte{byte(opcode.RET)}
// Immediate error from MakeRun.
rc.invRes = &result.Invoke{
State: "FAULT",
GasConsumed: 3,
Script: script,
Stack: []stackitem.Item{stackitem.Make(42)},
}
_, _, _, err = act.Notarize(act.MakeRun(script))
require.Error(t, err)
// Explicitly good transaction. but failure to create a fallback.
rc.invRes.State = "HALT"
tx, err := act.MakeRun(script)
require.NoError(t, err)
rc.invRes.State = "FAULT"
_, _, _, err = act.Notarize(tx, nil)
require.Error(t, err)
// FB hash mismatch from SendRequestExactly.
rc.invRes.State = "HALT"
_, _, _, err = act.Notarize(act.MakeRun(script))
require.Error(t, err)
// Good.
rc.mirror = true
mHash, fbHash, vub, err := act.Notarize(act.MakeRun(script))
require.NoError(t, err)
require.NotEqual(t, util.Uint256{}, mHash)
require.NotEqual(t, util.Uint256{}, fbHash)
require.Equal(t, uint32(92), vub)
}
func TestDefaultActorOptions(t *testing.T) {
rc := &RPCClient{
version: &result.Version{
Protocol: result.Protocol{
Network: netmode.UnitTestNet,
MillisecondsPerBlock: 1000,
ValidatorsCount: 7,
},
},
}
acc, err := wallet.NewAccount()
require.NoError(t, err)
opts := NewDefaultActorOptions(NewReader(invoker.New(rc, nil)), acc)
rc.invRes = &result.Invoke{
State: "HALT",
GasConsumed: 3,
Script: opts.FbScript,
Stack: []stackitem.Item{stackitem.Make(42)},
}
tx := transaction.New(opts.FbScript, 1)
require.Error(t, opts.MainCheckerModifier(&result.Invoke{State: "FAULT"}, tx))
rc.invRes.State = "FAULT"
require.Error(t, opts.MainCheckerModifier(&result.Invoke{State: "HALT"}, tx))
rc.invRes.State = "HALT"
require.NoError(t, opts.MainCheckerModifier(&result.Invoke{State: "HALT"}, tx))
require.Equal(t, uint32(42), tx.ValidUntilBlock)
}
func TestWait(t *testing.T) {
rc := &RPCClient{version: &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}}
key0, err := keys.NewPrivateKey()
require.NoError(t, err)
key1, err := keys.NewPrivateKey()
require.NoError(t, err)
acc0 := wallet.NewAccountFromPrivateKey(key0)
facc1 := FakeSimpleAccount(key1.PublicKey())
act, err := NewActor(rc, []actor.SignerAccount{{
Signer: transaction.Signer{
Account: acc0.Contract.ScriptHash(),
Scopes: transaction.None,
},
Account: acc0,
}, {
Signer: transaction.Signer{
Account: facc1.Contract.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: facc1,
}}, acc0)
require.NoError(t, err)
someErr := errors.New("someErr")
_, err = act.Wait(util.Uint256{}, 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},
}
rc.applog = applog
res, err := act.Wait(util.Uint256{}, util.Uint256{}, 0, nil)
require.NoError(t, err)
require.Equal(t, &state.AppExecResult{
Container: cont,
Execution: ex,
}, res)
}