mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-22 19:19:09 +00:00
actor: add a new WaitSuccess API
Most of the time people are interested in successful executions. Unfortunately, unwrap package can't help here because of a different result structure (some interface abstract can help, but it's still mostly stack-oriented and sessions can be a problem), so this additional interface is needed. Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
parent
4ff2063539
commit
a327a82085
4 changed files with 106 additions and 0 deletions
|
@ -14,14 +14,23 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrExecFailed is returned from [Actor.WaitSuccess] when transaction
|
||||
// is accepted into a block, but its execution ended up in non-HALT VM
|
||||
// state.
|
||||
ErrExecFailed = errors.New("execution failed")
|
||||
)
|
||||
|
||||
// RPCActor is an interface required from the RPC client to successfully
|
||||
// create and send transactions.
|
||||
type RPCActor interface {
|
||||
|
@ -285,3 +294,17 @@ func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transactio
|
|||
func (a *Actor) Sender() util.Uint160 {
|
||||
return a.txSigners[0].Account
|
||||
}
|
||||
|
||||
// WaitSuccess is similar to [waiter.Wait], but also checks for the VM state
|
||||
// to be HALT (successful execution). Execution result is still returned (if
|
||||
// HALTed normally) in case you need to examine events or stack.
|
||||
func (a *Actor) WaitSuccess(h util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
|
||||
aer, err := a.Wait(h, vub, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if aer.VMState != vmstate.Halt {
|
||||
return nil, fmt.Errorf("%w: %s", ErrExecFailed, aer.FaultException)
|
||||
}
|
||||
return aer, nil
|
||||
}
|
||||
|
|
|
@ -8,12 +8,14 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -295,3 +297,38 @@ func TestSender(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, acc.ScriptHash(), a.Sender())
|
||||
}
|
||||
|
||||
func TestWaitSuccess(t *testing.T) {
|
||||
client, acc := testRPCAndAccount(t)
|
||||
a, err := NewSimple(client, acc)
|
||||
require.NoError(t, err)
|
||||
|
||||
someErr := errors.New("someErr")
|
||||
_, err = a.WaitSuccess(util.Uint256{}, 0, someErr)
|
||||
require.ErrorIs(t, err, someErr)
|
||||
|
||||
cont := util.Uint256{1, 2, 3}
|
||||
ex := state.Execution{
|
||||
Trigger: trigger.Application,
|
||||
VMState: vmstate.Halt,
|
||||
GasConsumed: 123,
|
||||
Stack: []stackitem.Item{stackitem.Null{}},
|
||||
}
|
||||
applog := &result.ApplicationLog{
|
||||
Container: cont,
|
||||
IsTransaction: true,
|
||||
Executions: []state.Execution{ex},
|
||||
}
|
||||
client.appLog = applog
|
||||
client.appLog.Executions[0].VMState = vmstate.Fault
|
||||
_, err = a.WaitSuccess(util.Uint256{}, 0, nil)
|
||||
require.ErrorIs(t, err, ErrExecFailed)
|
||||
|
||||
client.appLog.Executions[0].VMState = vmstate.Halt
|
||||
res, err := a.WaitSuccess(util.Uint256{}, 0, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &state.AppExecResult{
|
||||
Container: cont,
|
||||
Execution: ex,
|
||||
}, res)
|
||||
}
|
||||
|
|
|
@ -16,9 +16,16 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrFallbackAccepted is returned from [Actor.WaitSuccess] when
|
||||
// fallback transaction enters the chain instead of the main one.
|
||||
ErrFallbackAccepted = errors.New("fallback transaction accepted")
|
||||
)
|
||||
|
||||
// Actor encapsulates everything needed to create proper notary requests for
|
||||
// assisted transactions.
|
||||
type Actor struct {
|
||||
|
@ -332,3 +339,21 @@ func (a *Actor) Wait(mainHash, fbHash util.Uint256, vub uint32, err error) (*sta
|
|||
}
|
||||
return a.WaitAny(context.TODO(), vub, mainHash, fbHash)
|
||||
}
|
||||
|
||||
// WaitSuccess works similar to [Actor.Wait], but checks that the main
|
||||
// transaction was accepted and it has a HALT VM state (executed successfully).
|
||||
// [state.AppExecResult] is still returned (if there is no error) in case you
|
||||
// need some additional event or stack checks.
|
||||
func (a *Actor) WaitSuccess(mainHash, fbHash util.Uint256, vub uint32, err error) (*state.AppExecResult, error) {
|
||||
aer, err := a.Wait(mainHash, fbHash, vub, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if aer.Container != mainHash {
|
||||
return nil, ErrFallbackAccepted
|
||||
}
|
||||
if aer.VMState != vmstate.Halt {
|
||||
return nil, fmt.Errorf("%w: %s", actor.ErrExecFailed, aer.FaultException)
|
||||
}
|
||||
return aer, nil
|
||||
}
|
||||
|
|
|
@ -565,6 +565,9 @@ func TestWait(t *testing.T) {
|
|||
_, err = act.Wait(util.Uint256{}, util.Uint256{}, 0, someErr)
|
||||
require.ErrorIs(t, err, someErr)
|
||||
|
||||
_, err = act.WaitSuccess(util.Uint256{}, util.Uint256{}, 0, someErr)
|
||||
require.ErrorIs(t, err, someErr)
|
||||
|
||||
cont := util.Uint256{1, 2, 3}
|
||||
ex := state.Execution{
|
||||
Trigger: trigger.Application,
|
||||
|
@ -584,4 +587,22 @@ func TestWait(t *testing.T) {
|
|||
Container: cont,
|
||||
Execution: ex,
|
||||
}, res)
|
||||
|
||||
// Not successful since result has a different hash.
|
||||
_, err = act.WaitSuccess(util.Uint256{}, util.Uint256{}, 0, nil)
|
||||
require.ErrorIs(t, err, ErrFallbackAccepted)
|
||||
_, err = act.WaitSuccess(util.Uint256{}, util.Uint256{1, 2, 3}, 0, nil)
|
||||
require.ErrorIs(t, err, ErrFallbackAccepted)
|
||||
|
||||
rc.applog.Executions[0].VMState = vmstate.Fault
|
||||
_, err = act.WaitSuccess(util.Uint256{1, 2, 3}, util.Uint256{}, 0, nil)
|
||||
require.ErrorIs(t, err, actor.ErrExecFailed)
|
||||
|
||||
rc.applog.Executions[0].VMState = vmstate.Halt
|
||||
res, err = act.WaitSuccess(util.Uint256{1, 2, 3}, util.Uint256{}, 0, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &state.AppExecResult{
|
||||
Container: cont,
|
||||
Execution: ex,
|
||||
}, res)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue