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

View file

@ -11,9 +11,3 @@ func TestRPCActorRPCClientCompat(t *testing.T) {
_ = actor.RPCActor(&rpcclient.WSClient{}) _ = actor.RPCActor(&rpcclient.WSClient{})
_ = actor.RPCActor(&rpcclient.Client{}) _ = 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/network/payload"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "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/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"
"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"
@ -78,7 +79,7 @@ func (r *RPCClient) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*r
return r.applog, nil return r.applog, nil
} }
var _ = actor.RPCPollingWaiter(&RPCClient{}) var _ = waiter.RPCPollingWaiter(&RPCClient{})
func TestNewActor(t *testing.T) { func TestNewActor(t *testing.T) {
rc := &RPCClient{ rc := &RPCClient{

View file

@ -1,4 +1,4 @@
package actor package waiter
import ( import (
"context" "context"
@ -100,12 +100,12 @@ func errIsAlreadyExists(err error) bool {
return strings.Contains(strings.ToLower(err.Error()), "already exists") 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 // polling-base, otherwise Waiter stub is returned. As a first argument
// it accepts RPCEventWaiter implementation, RPCPollingWaiter implementation // it accepts RPCEventWaiter implementation, RPCPollingWaiter implementation
// or not an implementation of these two interfaces. It returns websocket-based // or not an implementation of these two interfaces. It returns websocket-based
// waiter, polling-based waiter or a stub correspondingly. // 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 { if eventW, ok := base.(RPCEventWaiter); ok {
return &EventWaiter{ return &EventWaiter{
ws: eventW, ws: eventW,

View file

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