github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/rpcclient/waiter/waiter_test.go (about)

     1  package waiter_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  	"sync/atomic"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/google/uuid"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/block"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
    15  	"github.com/nspcc-dev/neo-go/pkg/neorpc"
    16  	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
    17  	"github.com/nspcc-dev/neo-go/pkg/rpcclient"
    18  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
    19  	"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
    20  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    21  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
    22  	"github.com/nspcc-dev/neo-go/pkg/util"
    23  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  type RPCClient struct {
    28  	err     error
    29  	invRes  *result.Invoke
    30  	netFee  int64
    31  	bCount  atomic.Uint32
    32  	version *result.Version
    33  	hash    util.Uint256
    34  	appLog  *result.ApplicationLog
    35  	context context.Context
    36  }
    37  
    38  var _ = waiter.RPCPollingBased(&RPCClient{})
    39  
    40  func (r *RPCClient) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
    41  	return r.invRes, r.err
    42  }
    43  func (r *RPCClient) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
    44  	return r.invRes, r.err
    45  }
    46  func (r *RPCClient) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
    47  	return r.invRes, r.err
    48  }
    49  func (r *RPCClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
    50  	return r.netFee, r.err
    51  }
    52  func (r *RPCClient) GetBlockCount() (uint32, error) {
    53  	return r.bCount.Load(), r.err
    54  }
    55  func (r *RPCClient) GetVersion() (*result.Version, error) {
    56  	verCopy := *r.version
    57  	return &verCopy, r.err
    58  }
    59  func (r *RPCClient) SendRawTransaction(tx *transaction.Transaction) (util.Uint256, error) {
    60  	return r.hash, r.err
    61  }
    62  func (r *RPCClient) TerminateSession(sessionID uuid.UUID) (bool, error) {
    63  	return false, nil // Just a stub, unused by actor.
    64  }
    65  func (r *RPCClient) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
    66  	return nil, nil // Just a stub, unused by actor.
    67  }
    68  func (r *RPCClient) Context() context.Context {
    69  	if r.context == nil {
    70  		return context.Background()
    71  	}
    72  	return r.context
    73  }
    74  
    75  func (r *RPCClient) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*result.ApplicationLog, error) {
    76  	if r.appLog != nil {
    77  		return r.appLog, nil
    78  	}
    79  	return nil, errors.New("not found")
    80  }
    81  
    82  type AwaitableRPCClient struct {
    83  	RPCClient
    84  
    85  	chLock      sync.RWMutex
    86  	subHeaderCh chan<- *block.Header
    87  	subBlockCh  chan<- *block.Block
    88  	subTxCh     chan<- *state.AppExecResult
    89  }
    90  
    91  var _ = waiter.RPCEventBased(&AwaitableRPCClient{})
    92  
    93  func (c *AwaitableRPCClient) ReceiveBlocks(flt *neorpc.BlockFilter, rcvr chan<- *block.Block) (string, error) {
    94  	c.chLock.Lock()
    95  	defer c.chLock.Unlock()
    96  	c.subBlockCh = rcvr
    97  	return "1", nil
    98  }
    99  func (c *AwaitableRPCClient) ReceiveExecutions(flt *neorpc.ExecutionFilter, rcvr chan<- *state.AppExecResult) (string, error) {
   100  	c.chLock.Lock()
   101  	defer c.chLock.Unlock()
   102  	c.subTxCh = rcvr
   103  	return "2", nil
   104  }
   105  func (c *AwaitableRPCClient) ReceiveHeadersOfAddedBlocks(flt *neorpc.BlockFilter, rcvr chan<- *block.Header) (string, error) {
   106  	c.chLock.Lock()
   107  	defer c.chLock.Unlock()
   108  	c.subHeaderCh = rcvr
   109  	return "3", nil
   110  }
   111  func (c *AwaitableRPCClient) Unsubscribe(id string) error { return nil }
   112  
   113  func TestNewWaiter(t *testing.T) {
   114  	w := waiter.New((actor.RPCActor)(nil), nil)
   115  	_, ok := w.(waiter.Null)
   116  	require.True(t, ok)
   117  
   118  	w = waiter.New(&RPCClient{}, &result.Version{})
   119  	_, ok = w.(*waiter.PollingBased)
   120  	require.True(t, ok)
   121  
   122  	w = waiter.New(&AwaitableRPCClient{RPCClient: RPCClient{}}, &result.Version{})
   123  	_, ok = w.(*waiter.EventBased)
   124  	require.True(t, ok)
   125  }
   126  
   127  func TestPollingWaiter_Wait(t *testing.T) {
   128  	h := util.Uint256{1, 2, 3}
   129  	bCount := uint32(5)
   130  	appLog := &result.ApplicationLog{Container: h, Executions: []state.Execution{{}}}
   131  	expected := &state.AppExecResult{Container: h, Execution: state.Execution{}}
   132  	c := &RPCClient{appLog: appLog}
   133  	c.bCount.Store(bCount)
   134  	w := waiter.New(c, &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}) // reduce testing time.
   135  	_, ok := w.(*waiter.PollingBased)
   136  	require.True(t, ok)
   137  
   138  	// Wait with error.
   139  	someErr := errors.New("some error")
   140  	_, err := w.Wait(h, bCount, someErr)
   141  	require.ErrorIs(t, err, someErr)
   142  
   143  	// AER is in chain immediately.
   144  	aer, err := w.Wait(h, bCount-1, nil)
   145  	require.NoError(t, err)
   146  	require.Equal(t, expected, aer)
   147  
   148  	// Missing AER after VUB.
   149  	c.appLog = nil
   150  	_, err = w.Wait(h, bCount-2, nil)
   151  	require.ErrorIs(t, waiter.ErrTxNotAccepted, err)
   152  
   153  	checkErr := func(t *testing.T, trigger func(), target error) {
   154  		errCh := make(chan error)
   155  		go func() {
   156  			_, err = w.Wait(h, bCount, nil)
   157  			errCh <- err
   158  		}()
   159  		timer := time.NewTimer(time.Second)
   160  		var triggerFired bool
   161  	waitloop:
   162  		for {
   163  			select {
   164  			case err = <-errCh:
   165  				require.ErrorIs(t, err, target)
   166  				break waitloop
   167  			case <-timer.C:
   168  				if triggerFired {
   169  					t.Fatal("failed to await result")
   170  				}
   171  				trigger()
   172  				triggerFired = true
   173  				timer.Reset(time.Second * 2)
   174  			}
   175  		}
   176  		require.True(t, triggerFired)
   177  	}
   178  
   179  	// Tx is accepted before VUB.
   180  	c.appLog = nil
   181  	c.bCount.Store(bCount)
   182  	checkErr(t, func() { c.bCount.Store(bCount + 1) }, waiter.ErrTxNotAccepted)
   183  
   184  	// Context is cancelled.
   185  	c.appLog = nil
   186  	c.bCount.Store(bCount)
   187  	ctx, cancel := context.WithCancel(context.Background())
   188  	c.context = ctx
   189  	checkErr(t, cancel, waiter.ErrContextDone)
   190  }
   191  
   192  func TestWSWaiter_Wait(t *testing.T) {
   193  	h := util.Uint256{1, 2, 3}
   194  	bCount := uint32(5)
   195  	appLog := &result.ApplicationLog{Container: h, Executions: []state.Execution{{}}}
   196  	expected := &state.AppExecResult{Container: h, Execution: state.Execution{}}
   197  	c := &AwaitableRPCClient{RPCClient: RPCClient{appLog: appLog}}
   198  	c.bCount.Store(bCount)
   199  	w := waiter.New(c, &result.Version{Protocol: result.Protocol{MillisecondsPerBlock: 1}}) // reduce testing time.
   200  	_, ok := w.(*waiter.EventBased)
   201  	require.True(t, ok)
   202  
   203  	// Wait with error.
   204  	someErr := errors.New("some error")
   205  	_, err := w.Wait(h, bCount, someErr)
   206  	require.ErrorIs(t, err, someErr)
   207  
   208  	// AER is in chain immediately.
   209  	aer, err := w.Wait(h, bCount-1, nil)
   210  	require.NoError(t, err)
   211  	require.Equal(t, expected, aer)
   212  
   213  	// Auxiliary things for asynchronous tests.
   214  	doneCh := make(chan struct{})
   215  	check := func(t *testing.T, trigger func()) {
   216  		timer := time.NewTimer(time.Second)
   217  		var triggerFired bool
   218  	waitloop:
   219  		for {
   220  			select {
   221  			case <-doneCh:
   222  				break waitloop
   223  			case <-timer.C:
   224  				if triggerFired {
   225  					t.Fatal("failed to await result")
   226  				}
   227  				trigger()
   228  				triggerFired = true
   229  				timer.Reset(time.Second * 2)
   230  			}
   231  		}
   232  		require.True(t, triggerFired)
   233  	}
   234  
   235  	// AER received after the subscription.
   236  	c.RPCClient.appLog = nil
   237  	go func() {
   238  		aer, err = w.Wait(h, bCount-1, nil)
   239  		require.NoError(t, err)
   240  		require.Equal(t, expected, aer)
   241  		doneCh <- struct{}{}
   242  	}()
   243  	check(t, func() {
   244  		c.chLock.RLock()
   245  		defer c.chLock.RUnlock()
   246  		c.subTxCh <- expected
   247  	})
   248  
   249  	// Missing AER after VUB.
   250  	go func() {
   251  		_, err = w.Wait(h, bCount-2, nil)
   252  		require.ErrorIs(t, err, waiter.ErrTxNotAccepted)
   253  		doneCh <- struct{}{}
   254  	}()
   255  	check(t, func() {
   256  		c.chLock.RLock()
   257  		defer c.chLock.RUnlock()
   258  		c.subHeaderCh <- &block.Header{}
   259  	})
   260  }
   261  
   262  func TestRPCWaiterRPCClientCompat(t *testing.T) {
   263  	_ = waiter.RPCPollingBased(&rpcclient.Client{})
   264  	_ = waiter.RPCPollingBased(&rpcclient.WSClient{})
   265  	_ = waiter.RPCEventBased(&rpcclient.WSClient{})
   266  }