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:
parent
28f1beff64
commit
4c6dca876c
5 changed files with 91 additions and 27 deletions
|
@ -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,
|
||||||
|
|
|
@ -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{})
|
|
||||||
}
|
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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,
|
|
@ -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{})
|
||||||
|
}
|
Loading…
Reference in a new issue