actor: fix event-based tx awaiting

If VUB-th block is received, we still can't guaranty that transaction
wasn't accepted to chain. Back this situation by rolling back to a
poll-based waiter.
This commit is contained in:
Anna Shaleva 2022-11-16 14:59:45 +03:00
parent 6dbae7edc4
commit 95e23c8e46
3 changed files with 62 additions and 2 deletions

View file

@ -304,13 +304,17 @@ func (w *EventWaiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Ui
} }
select { select {
case _, ok := <-bRcvr: case b, ok := <-bRcvr:
if !ok { if !ok {
// We're toast, retry with non-ws client. // We're toast, retry with non-ws client.
wsWaitErr = ErrMissedEvent wsWaitErr = ErrMissedEvent
return return
} }
waitErr = ErrTxNotAccepted // We can easily end up in a situation when subscription was performed too late and
// the desired transaction and VUB-th block have already got accepted before the
// subscription happened. Thus, always retry with non-ws client, it will perform
// AER requests and make sure.
wsWaitErr = fmt.Errorf("block #%d was received by EventWaiter", b.Index)
case aer, ok := <-aerRcvr: case aer, ok := <-aerRcvr:
if !ok { if !ok {
// We're toast, retry with non-ws client. // We're toast, retry with non-ws client.

View file

@ -166,6 +166,7 @@ func TestWSWaiter_Wait(t *testing.T) {
}) })
// Missing AER after VUB. // Missing AER after VUB.
c.RPCClient.appLog = nil
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, ErrTxNotAccepted)

View file

@ -2073,3 +2073,58 @@ func TestWSClient_Wait(t *testing.T) {
} }
require.True(t, faultedChecked, "FAULTed transaction wasn't checked") require.True(t, faultedChecked, "FAULTed transaction wasn't checked")
} }
func TestWSClient_WaitWithLateSubscription(t *testing.T) {
chain, rpcSrv, httpSrv := initClearServerWithServices(t, false, false, true)
defer chain.Close()
defer rpcSrv.Shutdown()
url := "ws" + strings.TrimPrefix(httpSrv.URL, "http") + "/ws"
c, err := rpcclient.NewWS(context.Background(), url, rpcclient.Options{})
require.NoError(t, err)
require.NoError(t, c.Init())
acc, err := wallet.NewAccount()
require.NoError(t, err)
act, err := actor.New(c, []actor.SignerAccount{
{
Signer: transaction.Signer{
Account: acc.ScriptHash(),
},
Account: acc,
},
})
require.NoError(t, err)
// Firstly, accept the block.
blocks := getTestBlocks(t)
b1 := blocks[0]
b2 := blocks[1]
tx := b1.Transactions[0]
require.NoError(t, chain.AddBlock(b1))
// After that, subscribe for AERs/blocks and wait.
rcvr := make(chan *state.AppExecResult)
go func() {
aer, err := act.Wait(tx.Hash(), tx.ValidUntilBlock, nil)
require.NoError(t, err)
rcvr <- aer
}()
// Accept the next block to trigger event-based waiter loop exit and rollback to
// poll-based waiter.
require.NoError(t, chain.AddBlock(b2))
// Wait for the result.
waitloop:
for {
select {
case aer := <-rcvr:
require.Equal(t, tx.Hash(), aer.Container)
require.Equal(t, trigger.Application, aer.Trigger)
require.Equal(t, vmstate.Halt, aer.VMState)
break waitloop
case <-time.NewTimer(time.Duration(chain.GetConfig().SecondsPerBlock) * time.Second).C:
t.Fatal("transaction failed to be awaited")
}
}
}