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 }