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>
This commit is contained in:
Ekaterina Pavlova 2023-12-26 13:04:45 +03:00
parent 28f1beff64
commit 4c6dca876c
5 changed files with 91 additions and 27 deletions

View file

@ -17,6 +17,7 @@ import (
"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/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
@ -72,7 +73,7 @@ type SignerAccount struct {
// and ErrTxNotAccepted is returned if transaction wasn't accepted by this moment.
type Actor struct {
invoker.Invoker
Waiter
waiter.Waiter
client RPCActor
opts Options
@ -126,7 +127,7 @@ func New(ra RPCActor, signers []SignerAccount) (*Actor, error) {
}
return &Actor{
Invoker: *inv,
Waiter: NewWaiter(ra, version),
Waiter: waiter.New(ra, version),
client: ra,
opts: NewDefaultOptions(),
signers: signers,

View file

@ -11,9 +11,3 @@ func TestRPCActorRPCClientCompat(t *testing.T) {
_ = actor.RPCActor(&rpcclient.WSClient{})
_ = actor.RPCActor(&rpcclient.Client{})
}
func TestRPCWaiterRPCClientCompat(t *testing.T) {
_ = actor.RPCPollingWaiter(&rpcclient.Client{})
_ = actor.RPCPollingWaiter(&rpcclient.WSClient{})
_ = actor.RPCEventWaiter(&rpcclient.WSClient{})
}

View file

@ -15,6 +15,7 @@ import (
"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"
@ -78,7 +79,7 @@ func (r *RPCClient) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*r
return r.applog, nil
}
var _ = actor.RPCPollingWaiter(&RPCClient{})
var _ = waiter.RPCPollingWaiter(&RPCClient{})
func TestNewActor(t *testing.T) {
rc := &RPCClient{

View file

@ -1,4 +1,4 @@
package actor
package waiter
import (
"context"
@ -100,12 +100,12 @@ func errIsAlreadyExists(err error) bool {
return strings.Contains(strings.ToLower(err.Error()), "already exists")
}
// NewWaiter creates Waiter instance. It can be either websocket-based or
// New creates Waiter instance. It can be either websocket-based or
// polling-base, otherwise Waiter stub is returned. As a first argument
// it accepts RPCEventWaiter implementation, RPCPollingWaiter implementation
// or not an implementation of these two interfaces. It returns websocket-based
// waiter, polling-based waiter or a stub correspondingly.
func NewWaiter(base any, v *result.Version) Waiter {
func New(base any, v *result.Version) Waiter {
if eventW, ok := base.(RPCEventWaiter); ok {
return &EventWaiter{
ws: eventW,

View file

@ -1,20 +1,82 @@
package actor
package waiter_test
import (
"context"
"errors"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/google/uuid"
"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/transaction"
"github.com/nspcc-dev/neo-go/pkg/neorpc"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"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/stackitem"
"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")
}
type AwaitableRPCClient struct {
RPCClient
@ -38,16 +100,16 @@ func (c *AwaitableRPCClient) ReceiveExecutions(flt *neorpc.ExecutionFilter, rcvr
func (c *AwaitableRPCClient) Unsubscribe(id string) error { return nil }
func TestNewWaiter(t *testing.T) {
w := NewWaiter((RPCActor)(nil), nil)
_, ok := w.(NullWaiter)
w := waiter.New((actor.RPCActor)(nil), nil)
_, ok := w.(waiter.NullWaiter)
require.True(t, ok)
w = NewWaiter(&RPCClient{}, &result.Version{})
_, ok = w.(*PollingWaiter)
w = waiter.New(&RPCClient{}, &result.Version{})
_, ok = w.(*waiter.PollingWaiter)
require.True(t, ok)
w = NewWaiter(&AwaitableRPCClient{RPCClient: RPCClient{}}, &result.Version{})
_, ok = w.(*EventWaiter)
w = waiter.New(&AwaitableRPCClient{RPCClient: RPCClient{}}, &result.Version{})
_, ok = w.(*waiter.EventWaiter)
require.True(t, ok)
}
@ -58,8 +120,8 @@ func TestPollingWaiter_Wait(t *testing.T) {
expected := &state.AppExecResult{Container: h, Execution: state.Execution{}}
c := &RPCClient{appLog: appLog}
c.bCount.Store(bCount)
w := NewWaiter(c, &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}) // reduce testing time.
_, ok := w.(*PollingWaiter)
w := waiter.New(c, &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}) // reduce testing time.
_, ok := w.(*waiter.PollingWaiter)
require.True(t, ok)
// Wait with error.
@ -75,7 +137,7 @@ func TestPollingWaiter_Wait(t *testing.T) {
// Missing AER after VUB.
c.appLog = nil
_, err = w.Wait(h, bCount-2, nil)
require.ErrorIs(t, ErrTxNotAccepted, err)
require.ErrorIs(t, waiter.ErrTxNotAccepted, err)
checkErr := func(t *testing.T, trigger func(), target error) {
errCh := make(chan error)
@ -106,14 +168,14 @@ func TestPollingWaiter_Wait(t *testing.T) {
// Tx is accepted before VUB.
c.appLog = nil
c.bCount.Store(bCount)
checkErr(t, func() { c.bCount.Store(bCount + 1) }, ErrTxNotAccepted)
checkErr(t, func() { c.bCount.Store(bCount + 1) }, waiter.ErrTxNotAccepted)
// Context is cancelled.
c.appLog = nil
c.bCount.Store(bCount)
ctx, cancel := context.WithCancel(context.Background())
c.context = ctx
checkErr(t, cancel, ErrContextDone)
checkErr(t, cancel, waiter.ErrContextDone)
}
func TestWSWaiter_Wait(t *testing.T) {
@ -123,8 +185,8 @@ func TestWSWaiter_Wait(t *testing.T) {
expected := &state.AppExecResult{Container: h, Execution: state.Execution{}}
c := &AwaitableRPCClient{RPCClient: RPCClient{appLog: appLog}}
c.bCount.Store(bCount)
w := NewWaiter(c, &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}) // reduce testing time.
_, ok := w.(*EventWaiter)
w := waiter.New(c, &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}) // reduce testing time.
_, ok := w.(*waiter.EventWaiter)
require.True(t, ok)
// Wait with error.
@ -176,7 +238,7 @@ func TestWSWaiter_Wait(t *testing.T) {
// Missing AER after VUB.
go func() {
_, err = w.Wait(h, bCount-2, nil)
require.ErrorIs(t, err, ErrTxNotAccepted)
require.ErrorIs(t, err, waiter.ErrTxNotAccepted)
doneCh <- struct{}{}
}()
check(t, func() {
@ -185,3 +247,9 @@ func TestWSWaiter_Wait(t *testing.T) {
c.subBlockCh <- &block.Block{}
})
}
func TestRPCWaiterRPCClientCompat(t *testing.T) {
_ = waiter.RPCPollingWaiter(&rpcclient.Client{})
_ = waiter.RPCPollingWaiter(&rpcclient.WSClient{})
_ = waiter.RPCEventWaiter(&rpcclient.WSClient{})
}