actor: allow providing default attributes/hooks to be used
Which expands Actor use cases greatly.
This commit is contained in:
parent
fe50879bb7
commit
a95984febf
4 changed files with 158 additions and 37 deletions
|
@ -48,16 +48,35 @@ type Actor struct {
|
||||||
invoker.Invoker
|
invoker.Invoker
|
||||||
|
|
||||||
client RPCActor
|
client RPCActor
|
||||||
|
opts Options
|
||||||
signers []SignerAccount
|
signers []SignerAccount
|
||||||
txSigners []transaction.Signer
|
txSigners []transaction.Signer
|
||||||
version *result.Version
|
version *result.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Options are used to create Actor with non-standard transaction checkers or
|
||||||
|
// additional attributes to be applied for all transactions.
|
||||||
|
type Options struct {
|
||||||
|
// Attributes are set as is into every transaction created by Actor,
|
||||||
|
// unless they're explicitly set in a method call that accepts
|
||||||
|
// attributes (like MakeTuned* or MakeUnsigned*).
|
||||||
|
Attributes []transaction.Attribute
|
||||||
|
// CheckerModifier is used by any method that creates and signs a
|
||||||
|
// transaction inside (some of them provide ways to override this
|
||||||
|
// default, some don't).
|
||||||
|
CheckerModifier TransactionCheckerModifier
|
||||||
|
// Modifier is used only by MakeUncheckedRun to modify transaction
|
||||||
|
// before it's signed (other methods that perform test invocations
|
||||||
|
// use CheckerModifier). MakeUnsigned* methods do not run it.
|
||||||
|
Modifier TransactionModifier
|
||||||
|
}
|
||||||
|
|
||||||
// New creates an Actor instance using the specified RPC interface and the set of
|
// New creates an Actor instance using the specified RPC interface and the set of
|
||||||
// signers with corresponding accounts. Every transaction created by this Actor
|
// signers with corresponding accounts. Every transaction created by this Actor
|
||||||
// will have this set of signers and all communication will be performed via this
|
// will have this set of signers and all communication will be performed via this
|
||||||
// RPC. Upon Actor instance creation a GetVersion call is made and the result of
|
// RPC. Upon Actor instance creation a GetVersion call is made and the result of
|
||||||
// it is cached forever (and used for internal purposes).
|
// it is cached forever (and used for internal purposes). The actor will use
|
||||||
|
// default Options (which can be overridden using NewTuned).
|
||||||
func New(ra RPCActor, signers []SignerAccount) (*Actor, error) {
|
func New(ra RPCActor, signers []SignerAccount) (*Actor, error) {
|
||||||
if len(signers) < 1 {
|
if len(signers) < 1 {
|
||||||
return nil, errors.New("at least one signer (sender) is required")
|
return nil, errors.New("at least one signer (sender) is required")
|
||||||
|
@ -81,6 +100,7 @@ func New(ra RPCActor, signers []SignerAccount) (*Actor, error) {
|
||||||
return &Actor{
|
return &Actor{
|
||||||
Invoker: *inv,
|
Invoker: *inv,
|
||||||
client: ra,
|
client: ra,
|
||||||
|
opts: NewDefaultOptions(),
|
||||||
signers: signers,
|
signers: signers,
|
||||||
txSigners: invSigners,
|
txSigners: invSigners,
|
||||||
version: version,
|
version: version,
|
||||||
|
@ -100,6 +120,34 @@ func NewSimple(ra RPCActor, acc *wallet.Account) (*Actor, error) {
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDefaultOptions returns Options that have no attributes and use the default
|
||||||
|
// TransactionCheckerModifier function (that checks for the invocation result to
|
||||||
|
// be in HALT state) and TransactionModifier (that does nothing).
|
||||||
|
func NewDefaultOptions() Options {
|
||||||
|
return Options{
|
||||||
|
CheckerModifier: DefaultCheckerModifier,
|
||||||
|
Modifier: DefaultModifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTuned creates an Actor that will use the specified Options as defaults when
|
||||||
|
// creating new transactions. If checker/modifier callbacks are not provided
|
||||||
|
// (nil), then default ones (from NewDefaultOptions) are used.
|
||||||
|
func NewTuned(ra RPCActor, signers []SignerAccount, opts Options) (*Actor, error) {
|
||||||
|
a, err := New(ra, signers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.opts.Attributes = opts.Attributes
|
||||||
|
if opts.CheckerModifier != nil {
|
||||||
|
a.opts.CheckerModifier = opts.CheckerModifier
|
||||||
|
}
|
||||||
|
if opts.Modifier != nil {
|
||||||
|
a.opts.Modifier = opts.Modifier
|
||||||
|
}
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
|
||||||
// CalculateNetworkFee wraps RPCActor's CalculateNetworkFee, making it available
|
// CalculateNetworkFee wraps RPCActor's CalculateNetworkFee, making it available
|
||||||
// to Actor users directly. It returns network fee value for the given
|
// to Actor users directly. It returns network fee value for the given
|
||||||
// transaction.
|
// transaction.
|
||||||
|
|
|
@ -78,6 +78,9 @@ func TestNew(t *testing.T) {
|
||||||
_, err = New(client, []SignerAccount{})
|
_, err = New(client, []SignerAccount{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
|
_, err = NewTuned(client, []SignerAccount{}, NewDefaultOptions())
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
// Good simple.
|
// Good simple.
|
||||||
a, err := NewSimple(client, acc)
|
a, err := NewSimple(client, acc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -140,6 +143,14 @@ func TestNew(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(a.signers))
|
require.Equal(t, 2, len(a.signers))
|
||||||
require.Equal(t, 2, len(a.txSigners))
|
require.Equal(t, 2, len(a.txSigners))
|
||||||
|
|
||||||
|
// Good tuned
|
||||||
|
opts := Options{
|
||||||
|
Attributes: []transaction.Attribute{{Type: transaction.HighPriority}},
|
||||||
|
}
|
||||||
|
a, err = NewTuned(client, signers, opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(a.opts.Attributes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleWrappers(t *testing.T) {
|
func TestSimpleWrappers(t *testing.T) {
|
||||||
|
|
|
@ -33,37 +33,53 @@ type TransactionCheckerModifier func(r *result.Invoke, t *transaction.Transactio
|
||||||
// successfully accepted and executed.
|
// successfully accepted and executed.
|
||||||
type TransactionModifier func(t *transaction.Transaction) error
|
type TransactionModifier func(t *transaction.Transaction) error
|
||||||
|
|
||||||
|
// DefaultModifier is the default modifier, it does nothing.
|
||||||
|
func DefaultModifier(t *transaction.Transaction) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultCheckerModifier is the default TransactionCheckerModifier, it checks
|
||||||
|
// for HALT state in the invocation result given to it and does nothing else.
|
||||||
|
func DefaultCheckerModifier(r *result.Invoke, t *transaction.Transaction) error {
|
||||||
|
if r.State != vmstate.Halt.String() {
|
||||||
|
return fmt.Errorf("script failed (%s state) due to an error: %s", r.State, r.FaultException)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MakeCall creates a transaction that calls the given method of the given
|
// MakeCall creates a transaction that calls the given method of the given
|
||||||
// contract with the given parameters. Test call is performed and checked for
|
// contract with the given parameters. Test call is performed and filtered through
|
||||||
// HALT status, if more checks are needed or transaction should have some
|
// Actor-configured TransactionCheckerModifier. The resulting transaction has
|
||||||
// additional attributes use MakeTunedCall.
|
// Actor-configured attributes added as well. If you need to override attributes
|
||||||
|
// and/or TransactionCheckerModifier use MakeTunedCall.
|
||||||
func (a *Actor) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
|
func (a *Actor) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
|
||||||
return a.MakeTunedCall(contract, method, nil, nil, params...)
|
return a.MakeTunedCall(contract, method, nil, nil, params...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeTunedCall creates a transaction with the given attributes that calls the
|
// MakeTunedCall creates a transaction with the given attributes (or Actor default
|
||||||
// given method of the given contract with the given parameters. It's filtered
|
// ones if nil) that calls the given method of the given contract with the given
|
||||||
// through the provided callback (see TransactionCheckerModifier documentation),
|
// parameters. It's filtered through the provided callback (or Actor default
|
||||||
// so the process can be aborted and transaction can be modified before signing.
|
// one's if nil, see TransactionCheckerModifier documentation also), so the
|
||||||
// If no callback is given then the result is checked for HALT state.
|
// process can be aborted and transaction can be modified before signing.
|
||||||
func (a *Actor) MakeTunedCall(contract util.Uint160, method string, attrs []transaction.Attribute, txHook TransactionCheckerModifier, params ...interface{}) (*transaction.Transaction, error) {
|
func (a *Actor) MakeTunedCall(contract util.Uint160, method string, attrs []transaction.Attribute, txHook TransactionCheckerModifier, params ...interface{}) (*transaction.Transaction, error) {
|
||||||
r, err := a.Call(contract, method, params...)
|
r, err := a.Call(contract, method, params...)
|
||||||
return a.makeUncheckedWrapper(r, err, attrs, txHook)
|
return a.makeUncheckedWrapper(r, err, attrs, txHook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeRun creates a transaction with the given executable script. Test
|
// MakeRun creates a transaction with the given executable script. Test
|
||||||
// invocation of this script is performed and expected to end up in HALT
|
// invocation of this script is performed and filtered through Actor's
|
||||||
// state. If more checks are needed or transaction should have some additional
|
// TransactionCheckerModifier. The resulting transaction has attributes that are
|
||||||
// attributes use MakeTunedRun.
|
// configured for current Actor. If you need to override them or use a different
|
||||||
|
// TransactionCheckerModifier use MakeTunedRun.
|
||||||
func (a *Actor) MakeRun(script []byte) (*transaction.Transaction, error) {
|
func (a *Actor) MakeRun(script []byte) (*transaction.Transaction, error) {
|
||||||
return a.MakeTunedRun(script, nil, nil)
|
return a.MakeTunedRun(script, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeTunedRun creates a transaction with the given attributes that executes
|
// MakeTunedRun creates a transaction with the given attributes (or Actor default
|
||||||
// the given script. It's filtered through the provided callback (see
|
// ones if nil) that executes the given script. It's filtered through the
|
||||||
// TransactionCheckerModifier documentation), so the process can be aborted and
|
// provided callback (if not nil, otherwise Actor default one is used, see
|
||||||
// transaction can be modified before signing. If no callback is given then the
|
// TransactionCheckerModifier documentation also), so the process can be aborted
|
||||||
// result is checked for HALT state.
|
// and transaction can be modified before signing.
|
||||||
func (a *Actor) MakeTunedRun(script []byte, attrs []transaction.Attribute, txHook TransactionCheckerModifier) (*transaction.Transaction, error) {
|
func (a *Actor) MakeTunedRun(script []byte, attrs []transaction.Attribute, txHook TransactionCheckerModifier) (*transaction.Transaction, error) {
|
||||||
r, err := a.Run(script)
|
r, err := a.Run(script)
|
||||||
return a.makeUncheckedWrapper(r, err, attrs, txHook)
|
return a.makeUncheckedWrapper(r, err, attrs, txHook)
|
||||||
|
@ -75,32 +91,31 @@ func (a *Actor) makeUncheckedWrapper(r *result.Invoke, err error, attrs []transa
|
||||||
}
|
}
|
||||||
return a.MakeUncheckedRun(r.Script, r.GasConsumed, attrs, func(tx *transaction.Transaction) error {
|
return a.MakeUncheckedRun(r.Script, r.GasConsumed, attrs, func(tx *transaction.Transaction) error {
|
||||||
if txHook == nil {
|
if txHook == nil {
|
||||||
if r.State != vmstate.Halt.String() {
|
txHook = a.opts.CheckerModifier
|
||||||
return fmt.Errorf("script failed (%s state) due to an error: %s", r.State, r.FaultException)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return txHook(r, tx)
|
return txHook(r, tx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeUncheckedRun creates a transaction with the given attributes that executes
|
// MakeUncheckedRun creates a transaction with the given attributes (or Actor
|
||||||
// the given script and is expected to use up to sysfee GAS for its execution.
|
// default ones if nil) that executes the given script and is expected to use
|
||||||
// The transaction is filtered through the provided callback (see
|
// up to sysfee GAS for its execution. The transaction is filtered through the
|
||||||
// TransactionModifier documentation), so the process can be aborted and
|
// provided callback (or Actor default one, see TransactionModifier documentation
|
||||||
// transaction can be modified before signing. This method is mostly useful when
|
// also), so the process can be aborted and transaction can be modified before
|
||||||
// test invocation is already performed and the script and required system fee
|
// signing. This method is mostly useful when test invocation is already
|
||||||
// values are already known.
|
// performed and the script and required system fee values are already known.
|
||||||
func (a *Actor) MakeUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (*transaction.Transaction, error) {
|
func (a *Actor) MakeUncheckedRun(script []byte, sysfee int64, attrs []transaction.Attribute, txHook TransactionModifier) (*transaction.Transaction, error) {
|
||||||
tx, err := a.MakeUnsignedUncheckedRun(script, sysfee, attrs)
|
tx, err := a.MakeUnsignedUncheckedRun(script, sysfee, attrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if txHook != nil {
|
|
||||||
err = txHook(tx)
|
if txHook == nil {
|
||||||
if err != nil {
|
txHook = a.opts.Modifier
|
||||||
return nil, err
|
}
|
||||||
}
|
err = txHook(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
err = a.Sign(tx)
|
err = a.Sign(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -113,6 +128,8 @@ func (a *Actor) MakeUncheckedRun(script []byte, sysfee int64, attrs []transactio
|
||||||
// that calls the given method of the given contract with the given parameters.
|
// that calls the given method of the given contract with the given parameters.
|
||||||
// Test-invocation is performed and is expected to end up in HALT state, the
|
// Test-invocation is performed and is expected to end up in HALT state, the
|
||||||
// transaction returned has correct SystemFee and NetworkFee values.
|
// transaction returned has correct SystemFee and NetworkFee values.
|
||||||
|
// TransactionModifier is not applied to the result of this method, but default
|
||||||
|
// attributes are used if attrs is nil.
|
||||||
func (a *Actor) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
|
func (a *Actor) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
|
||||||
r, err := a.Call(contract, method, params...)
|
r, err := a.Call(contract, method, params...)
|
||||||
return a.makeUnsignedWrapper(r, err, attrs)
|
return a.makeUnsignedWrapper(r, err, attrs)
|
||||||
|
@ -121,7 +138,8 @@ func (a *Actor) MakeUnsignedCall(contract util.Uint160, method string, attrs []t
|
||||||
// MakeUnsignedRun creates an unsigned transaction with the given attributes
|
// MakeUnsignedRun creates an unsigned transaction with the given attributes
|
||||||
// that executes the given script. Test-invocation is performed and is expected
|
// that executes the given script. Test-invocation is performed and is expected
|
||||||
// to end up in HALT state, the transaction returned has correct SystemFee and
|
// to end up in HALT state, the transaction returned has correct SystemFee and
|
||||||
// NetworkFee values.
|
// NetworkFee values. TransactionModifier is not applied to the result of this
|
||||||
|
// method, but default attributes are used if attrs is nil.
|
||||||
func (a *Actor) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
func (a *Actor) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||||
r, err := a.Run(script)
|
r, err := a.Run(script)
|
||||||
return a.makeUnsignedWrapper(r, err, attrs)
|
return a.makeUnsignedWrapper(r, err, attrs)
|
||||||
|
@ -131,8 +149,9 @@ func (a *Actor) makeUnsignedWrapper(r *result.Invoke, err error, attrs []transac
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to test-invoke: %w", err)
|
return nil, fmt.Errorf("failed to test-invoke: %w", err)
|
||||||
}
|
}
|
||||||
if r.State != vmstate.Halt.String() {
|
err = DefaultCheckerModifier(r, nil) // We know it doesn't care about transaction anyway.
|
||||||
return nil, fmt.Errorf("test invocation faulted (%s): %s", r.State, r.FaultException)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return a.MakeUnsignedUncheckedRun(r.Script, r.GasConsumed, attrs)
|
return a.MakeUnsignedUncheckedRun(r.Script, r.GasConsumed, attrs)
|
||||||
}
|
}
|
||||||
|
@ -144,7 +163,8 @@ func (a *Actor) makeUnsignedWrapper(r *result.Invoke, err error, attrs []transac
|
||||||
// Signers with Actor's signers, calculates proper ValidUntilBlock and NetworkFee
|
// Signers with Actor's signers, calculates proper ValidUntilBlock and NetworkFee
|
||||||
// values. The resulting transaction can be changed in its Nonce, SystemFee,
|
// values. The resulting transaction can be changed in its Nonce, SystemFee,
|
||||||
// NetworkFee and ValidUntilBlock values and then be signed and sent or
|
// NetworkFee and ValidUntilBlock values and then be signed and sent or
|
||||||
// exchanged via context.ParameterContext.
|
// exchanged via context.ParameterContext. TransactionModifier is not applied to
|
||||||
|
// the result of this method, but default attributes are used if attrs is nil.
|
||||||
func (a *Actor) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
func (a *Actor) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -155,6 +175,9 @@ func (a *Actor) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []tr
|
||||||
return nil, errors.New("negative system fee")
|
return nil, errors.New("negative system fee")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if attrs == nil {
|
||||||
|
attrs = a.opts.Attributes // Might as well be nil, but it's OK.
|
||||||
|
}
|
||||||
tx := transaction.New(script, sysFee)
|
tx := transaction.New(script, sysFee)
|
||||||
tx.Signers = a.txSigners
|
tx.Signers = a.txSigners
|
||||||
tx.Attributes = attrs
|
tx.Attributes = attrs
|
||||||
|
|
|
@ -90,6 +90,17 @@ func TestMakeUnsigned(t *testing.T) {
|
||||||
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
|
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
|
||||||
_, err = a.MakeUnsignedRun(script, nil)
|
_, err = a.MakeUnsignedRun(script, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Tuned.
|
||||||
|
opts := Options{
|
||||||
|
Attributes: []transaction.Attribute{{Type: transaction.HighPriority}},
|
||||||
|
}
|
||||||
|
a, err = NewTuned(client, a.signers, opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, err = a.MakeUnsignedRun(script, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, tx.HasAttribute(transaction.HighPriority))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMakeSigned(t *testing.T) {
|
func TestMakeSigned(t *testing.T) {
|
||||||
|
@ -127,6 +138,20 @@ func TestMakeSigned(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint32(777), tx.ValidUntilBlock)
|
require.Equal(t, uint32(777), tx.ValidUntilBlock)
|
||||||
|
|
||||||
|
// Tuned.
|
||||||
|
opts := Options{
|
||||||
|
Modifier: func(t *transaction.Transaction) error {
|
||||||
|
t.ValidUntilBlock = 888
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
at, err := NewTuned(client, a.signers, opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, err = at.MakeUncheckedRun(script, 0, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint32(888), tx.ValidUntilBlock)
|
||||||
|
|
||||||
// Checked
|
// Checked
|
||||||
|
|
||||||
// Bad, invocation fails.
|
// Bad, invocation fails.
|
||||||
|
@ -175,4 +200,18 @@ func TestMakeSigned(t *testing.T) {
|
||||||
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
|
client.invRes = &result.Invoke{State: "HALT", GasConsumed: 3, Script: script}
|
||||||
_, err = a.MakeCall(util.Uint160{}, "method", 1)
|
_, err = a.MakeCall(util.Uint160{}, "method", 1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Tuned.
|
||||||
|
opts = Options{
|
||||||
|
CheckerModifier: func(r *result.Invoke, t *transaction.Transaction) error {
|
||||||
|
t.ValidUntilBlock = 888
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
at, err = NewTuned(client, a.signers, opts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, err = at.MakeRun(script)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint32(888), tx.ValidUntilBlock)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue