mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-01-09 11:19:06 +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"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"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/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"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/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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"
|
"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
|
// RPCActor is an interface required from the RPC client to successfully
|
||||||
// create and send transactions.
|
// create and send transactions.
|
||||||
type RPCActor interface {
|
type RPCActor interface {
|
||||||
|
@ -285,3 +294,17 @@ func (a *Actor) SendUncheckedRun(script []byte, sysfee int64, attrs []transactio
|
||||||
func (a *Actor) Sender() util.Uint160 {
|
func (a *Actor) Sender() util.Uint160 {
|
||||||
return a.txSigners[0].Account
|
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/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"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/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"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"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"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/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -295,3 +297,38 @@ func TestSender(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, acc.ScriptHash(), a.Sender())
|
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/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"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/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"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
|
// Actor encapsulates everything needed to create proper notary requests for
|
||||||
// assisted transactions.
|
// assisted transactions.
|
||||||
type Actor struct {
|
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)
|
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)
|
_, err = act.Wait(util.Uint256{}, util.Uint256{}, 0, someErr)
|
||||||
require.ErrorIs(t, err, 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}
|
cont := util.Uint256{1, 2, 3}
|
||||||
ex := state.Execution{
|
ex := state.Execution{
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
|
@ -584,4 +587,22 @@ func TestWait(t *testing.T) {
|
||||||
Container: cont,
|
Container: cont,
|
||||||
Execution: ex,
|
Execution: ex,
|
||||||
}, res)
|
}, 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