actor: don't abort waiter on "already exists" error

It can happen in many cases of distributed tx generation/submission, we can
just wait normally in this case and there will be some proper result.
This commit is contained in:
Roman Khimov 2022-11-22 17:12:24 +03:00
parent cd6bb68246
commit fd04b2befd
2 changed files with 21 additions and 5 deletions

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/nspcc-dev/neo-go/pkg/core/block"
@ -41,7 +42,11 @@ type (
// Wait allows to wait until transaction will be accepted to the chain. It can be
// used as a wrapper for Send or SignAndSend and accepts transaction hash,
// ValidUntilBlock value and an error. It returns transaction execution result
// or an error if transaction wasn't accepted to the chain.
// or an error if transaction wasn't accepted to the chain. Notice that "already
// exists" err value is not treated as an error by this routine because it
// means that the transactions given might be already accepted or soon going
// to be accepted. Such transaction can be waited for in a usual way, potentially
// with positive result, so that's what will happen.
Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error)
// WaitAny waits until at least one of the specified transactions will be accepted
// to the chain until vub (including). It returns execution result of this
@ -89,6 +94,12 @@ type EventWaiter struct {
polling Waiter
}
// errIsAlreadyExists is a temporary helper until we have #2248 solved. Both C#
// and Go nodes return this string (possibly among other data).
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
// polling-base, otherwise Waiter stub is returned.
func newWaiter(ra RPCActor, v *result.Version) Waiter {
@ -139,7 +150,7 @@ func NewPollingWaiter(waiter RPCPollingWaiter) (*PollingWaiter, error) {
// Wait implements Waiter interface.
func (w *PollingWaiter) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
if err != nil {
if err != nil && !errIsAlreadyExists(err) {
return nil, err
}
return w.WaitAny(context.TODO(), vub, h)
@ -209,7 +220,7 @@ func NewEventWaiter(waiter RPCEventWaiter) (*EventWaiter, error) {
// Wait implements Waiter interface.
func (w *EventWaiter) Wait(h util.Uint256, vub uint32, err error) (res *state.AppExecResult, waitErr error) {
if err != nil {
if err != nil && !errIsAlreadyExists(err) {
return nil, err
}
return w.WaitAny(context.TODO(), vub, h)

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -320,9 +321,13 @@ func (a *Actor) SendRequestExactly(mainTx *transaction.Transaction, fbTx *transa
// the resulting application execution result or actor.ErrTxNotAccepted if both transactions
// failed to persist. Wait can be used if underlying Actor supports transaction awaiting,
// see actor.Actor and actor.Waiter documentation for details. Wait may be used as a wrapper
// for Notarize, SendRequest or SendRequestExactly.
// for Notarize, SendRequest or SendRequestExactly. Notice that "already exists" or "already
// on chain" answers are not treated as errors by this routine because they mean that some
// of the transactions given might be already accepted or soon going to be accepted. These
// transactions can be waited for in a usual way potentially with positive result.
func (a *Actor) Wait(mainHash, fbHash util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
if err != nil {
// #2248 will eventually remove this garbage from the code.
if err != nil && !(strings.Contains(strings.ToLower(err.Error()), "already exists") || strings.Contains(strings.ToLower(err.Error()), "already on chain")) {
return nil, err
}
return a.WaitAny(context.TODO(), vub, mainHash, fbHash)