From b76f592095c7613c7228c9f3299c2e8a8e07efbd Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 7 Nov 2023 13:57:30 +0300 Subject: [PATCH] [#48] Add commonclient package Signed-off-by: Denis Kirillov --- commonclient/invoker.go | 20 ++++++++++++ commonclient/iterator.go | 35 +++++++++++++++++++++ commonclient/transaction.go | 63 +++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 commonclient/invoker.go create mode 100644 commonclient/iterator.go create mode 100644 commonclient/transaction.go diff --git a/commonclient/invoker.go b/commonclient/invoker.go new file mode 100644 index 0000000..ff2dad0 --- /dev/null +++ b/commonclient/invoker.go @@ -0,0 +1,20 @@ +package commonclient + +import ( + "github.com/google/uuid" + "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/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// Invoker is a subset of methods provided by struct invoker.Invoker. The subset contains only those +// methods that are used by ActorWrapper and clients of the contracts. +type Invoker interface { + Call(contract util.Uint160, method string, params ...any) (*result.Invoke, error) + TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error) + TerminateSession(sessionID uuid.UUID) error +} + +// Ensure the interface is compatible with the invoker.Invoker struct. +var _ Invoker = (*invoker.Invoker)(nil) diff --git a/commonclient/iterator.go b/commonclient/iterator.go new file mode 100644 index 0000000..2ff962c --- /dev/null +++ b/commonclient/iterator.go @@ -0,0 +1,35 @@ +package commonclient + +import ( + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// ReadIteratorItems calls method that returns iterator and traverses the iterator until all items are read into array. +func ReadIteratorItems(inv Invoker, batchSize int, contract util.Uint160, method string, params ...any) ([]stackitem.Item, error) { + if batchSize <= 0 { + panic("batch size must be positive") + } + + sessionID, iter, err := unwrap.SessionIterator(inv.Call(contract, method, params...)) + if err != nil { + return nil, fmt.Errorf("unwrap session iterator: %w", err) + } + + var shouldStop bool + res := make([]stackitem.Item, 0, len(iter.Values)) + for !shouldStop { + items, err := inv.TraverseIterator(sessionID, &iter, batchSize) + if err != nil { + return nil, err + } + + res = append(res, items...) + shouldStop = len(items) < batchSize + } + + return res, nil +} diff --git a/commonclient/transaction.go b/commonclient/transaction.go new file mode 100644 index 0000000..15a33aa --- /dev/null +++ b/commonclient/transaction.go @@ -0,0 +1,63 @@ +package commonclient + +import ( + "errors" + + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" +) + +// Transaction allows to invoke several contract method at once. +type Transaction struct { + writer *io.BufBinWriter + buffer *io.BufBinWriter + contract util.Uint160 +} + +var ErrTransactionTooLarge = errors.New("transaction/script size limit exceeded") + +// NewTransaction creates new transaction to accumulate contract invocations. +func NewTransaction(contractHash util.Uint160) *Transaction { + return &Transaction{ + writer: io.NewBufBinWriter(), + buffer: io.NewBufBinWriter(), + contract: contractHash, + } +} + +// WrapCall accept methods and arguments to invoke. +// Should be used with method on clients like Client.MethodNameCall. +func (t Transaction) WrapCall(method string, args []any) error { + t.buffer.Reset() + emit.AppCall(t.buffer.BinWriter, t.contract, method, callflag.All, args...) + + if t.writer.Len()+t.buffer.Len() > transaction.MaxScriptLength { + return ErrTransactionTooLarge + } + + t.writer.WriteBytes(t.buffer.Bytes()) + + return t.writer.Err +} + +// WrapCallErr accept methods, arguments and error to handle and invoke. +// Should be used with method on clients like *CallErr. +func (t Transaction) WrapCallErr(method string, args []any, err error) error { + if err != nil { + return err + } + + return t.WrapCall(method, args) +} + +// Bytes returns the resulting buffer and makes future writes return an error. +func (t Transaction) Bytes() ([]byte, error) { + if t.writer.Len() > transaction.MaxScriptLength { + return nil, ErrTransactionTooLarge + } + + return t.writer.Bytes(), nil +}