85 lines
2.7 KiB
Go
85 lines
2.7 KiB
Go
|
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)
|
||
|
}
|