From 4c6dca876cba754291dd00a7dbca58442fada5c3 Mon Sep 17 00:00:00 2001 From: Ekaterina Pavlova Date: Tue, 26 Dec 2023 13:04:45 +0300 Subject: [PATCH] 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 --- pkg/rpcclient/actor/actor.go | 5 +- pkg/rpcclient/actor/compat_test.go | 6 -- pkg/rpcclient/notary/actor_test.go | 3 +- pkg/rpcclient/{actor => waiter}/waiter.go | 6 +- .../{actor => waiter}/waiter_test.go | 98 ++++++++++++++++--- 5 files changed, 91 insertions(+), 27 deletions(-) rename pkg/rpcclient/{actor => waiter}/waiter.go (98%) rename pkg/rpcclient/{actor => waiter}/waiter_test.go (55%) diff --git a/pkg/rpcclient/actor/actor.go b/pkg/rpcclient/actor/actor.go index bf3ec334e..e81a581da 100644 --- a/pkg/rpcclient/actor/actor.go +++ b/pkg/rpcclient/actor/actor.go @@ -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, diff --git a/pkg/rpcclient/actor/compat_test.go b/pkg/rpcclient/actor/compat_test.go index b8e356898..874025d14 100644 --- a/pkg/rpcclient/actor/compat_test.go +++ b/pkg/rpcclient/actor/compat_test.go @@ -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{}) -} diff --git a/pkg/rpcclient/notary/actor_test.go b/pkg/rpcclient/notary/actor_test.go index 16fc7ce4c..ccda87c5b 100644 --- a/pkg/rpcclient/notary/actor_test.go +++ b/pkg/rpcclient/notary/actor_test.go @@ -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{ diff --git a/pkg/rpcclient/actor/waiter.go b/pkg/rpcclient/waiter/waiter.go similarity index 98% rename from pkg/rpcclient/actor/waiter.go rename to pkg/rpcclient/waiter/waiter.go index 183660846..dd17e2a19 100644 --- a/pkg/rpcclient/actor/waiter.go +++ b/pkg/rpcclient/waiter/waiter.go @@ -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, diff --git a/pkg/rpcclient/actor/waiter_test.go b/pkg/rpcclient/waiter/waiter_test.go similarity index 55% rename from pkg/rpcclient/actor/waiter_test.go rename to pkg/rpcclient/waiter/waiter_test.go index 495e62815..1eaff101a 100644 --- a/pkg/rpcclient/actor/waiter_test.go +++ b/pkg/rpcclient/waiter/waiter_test.go @@ -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{}) +}