Skip to content

Commit

Permalink
Merge pull request #3491 from nspcc-dev/wait-success
Browse files Browse the repository at this point in the history
actor: add a new WaitSuccess API
  • Loading branch information
AnnaShaleva authored Jun 21, 2024
2 parents 4ff2063 + a327a82 commit 3889224
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 0 deletions.
23 changes: 23 additions & 0 deletions pkg/rpcclient/actor/actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
37 changes: 37 additions & 0 deletions pkg/rpcclient/actor/actor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}
25 changes: 25 additions & 0 deletions pkg/rpcclient/notary/actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
21 changes: 21 additions & 0 deletions pkg/rpcclient/notary/actor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}

0 comments on commit 3889224

Please sign in to comment.