frostfs-contract/commonclient/waiter.go

85 lines
2.7 KiB
Go
Raw Normal View History

package commonclient
import (
"context"
"fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
const alreadyExistsError = "already exists"
type WaiterOptions struct {
// IgnoreAlreadyExistsError controls behavior for "already exists" error:
// - If set to true, it indicates that "already exists" error is not a problem, we should
// wait for transaction as usual (this is the behavior of neo-go [waiter.PollingBased]).
// - If set to false, it indicates that "already exists" should be reported as an error.
IgnoreAlreadyExistsError bool
// VerifyExecResults controls whether waiter should ensure that transaction successfully
// enters blockchain block.
VerifyExecResults bool
}
// Waiter is a decorator on top of the standard [waiter.Waiter].
// It provides additional behavior (controlled by [WaiterOptions]) on top of the standard
// functionality of awaiting transactions.
type Waiter struct {
waiter waiter.Waiter
options WaiterOptions
}
var _ waiter.Waiter = (*Waiter)(nil)
// NewWaiter decorates the specified waiter in a new [Waiter] instance.
func NewWaiter(waiter waiter.Waiter, options WaiterOptions) *Waiter {
return &Waiter{
waiter: waiter,
options: options,
}
}
// Wait allows to wait until transaction will be accepted to the chain.
func (w *Waiter) Wait(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
if !w.options.IgnoreAlreadyExistsError && errIsAlreadyExists(err) {
return nil, err
}
result, err := w.waiter.Wait(h, vub, err)
return w.examineExecResult(result, err)
}
// WaitAny waits until at least one of the specified transactions will be accepted
// to the chain.
func (w *Waiter) WaitAny(ctx context.Context, vub uint32, hashes ...util.Uint256) (*state.AppExecResult, error) {
result, err := w.waiter.WaitAny(ctx, vub, hashes...)
return w.examineExecResult(result, err)
}
func (w *Waiter) examineExecResult(result *state.AppExecResult, err error) (*state.AppExecResult, error) {
if !w.options.VerifyExecResults || err != nil {
return result, err
}
if result.Execution.VMState != vmstate.Fault {
// Transaction didn't fail, so we just return result "as is"
return result, nil
}
// Transaction failed, we extract VM exception from it and report as an error
if result.FaultException != "" {
return result, fmt.Errorf("%s", result.FaultException)
}
return result, fmt.Errorf("transaction failed, stack=%v", result.Stack)
}
func errIsAlreadyExists(err error) bool {
if err == nil {
return false
}
return strings.Contains(strings.ToLower(err.Error()), alreadyExistsError)
}