From fd04b2befd5ed0593ea9195ad0978cfff38c2c08 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 22 Nov 2022 17:12:24 +0300 Subject: [PATCH] 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. --- pkg/rpcclient/actor/waiter.go | 17 ++++++++++++++--- pkg/rpcclient/notary/actor.go | 9 +++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pkg/rpcclient/actor/waiter.go b/pkg/rpcclient/actor/waiter.go index 424c24f55..ceb722a9a 100644 --- a/pkg/rpcclient/actor/waiter.go +++ b/pkg/rpcclient/actor/waiter.go @@ -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) diff --git a/pkg/rpcclient/notary/actor.go b/pkg/rpcclient/notary/actor.go index 2a71f5750..925ef9417 100644 --- a/pkg/rpcclient/notary/actor.go +++ b/pkg/rpcclient/notary/actor.go @@ -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)