2020-07-24 13:54:03 +00:00
|
|
|
package client
|
2020-07-10 14:17:51 +00:00
|
|
|
|
|
|
|
import (
|
2021-02-25 16:50:10 +00:00
|
|
|
"context"
|
2021-05-18 08:12:51 +00:00
|
|
|
"errors"
|
2021-04-09 20:10:59 +00:00
|
|
|
"fmt"
|
2022-03-11 09:28:34 +00:00
|
|
|
"math/big"
|
2022-03-15 11:21:24 +00:00
|
|
|
"sync"
|
2023-05-19 15:06:20 +00:00
|
|
|
"sync/atomic"
|
2021-02-25 16:50:10 +00:00
|
|
|
"time"
|
|
|
|
|
2023-04-12 14:35:10 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
2024-06-24 14:12:26 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/metrics"
|
2023-06-08 13:37:46 +00:00
|
|
|
morphmetrics "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/metrics"
|
2023-03-07 13:38:26 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
2024-01-11 10:47:17 +00:00
|
|
|
"github.com/google/uuid"
|
2022-12-30 09:47:18 +00:00
|
|
|
lru "github.com/hashicorp/golang-lru/v2"
|
2021-04-08 14:40:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
2020-07-10 14:17:51 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
2021-03-09 14:45:35 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2020-12-11 08:33:27 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
2024-10-31 10:13:03 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
2022-07-28 16:22:32 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
2022-08-29 15:32:54 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
2022-11-03 12:44:59 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
2024-01-25 08:21:00 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
2022-08-29 15:32:54 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
2022-11-03 12:44:59 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
2023-03-24 08:54:59 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
2024-01-11 10:47:17 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
2021-04-27 11:07:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
2020-07-10 14:17:51 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-08-27 11:57:13 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2022-07-18 08:33:53 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
2020-07-10 14:17:51 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2021-02-09 17:52:10 +00:00
|
|
|
// Client is a wrapper over web socket neo-go client
|
|
|
|
// that provides smart-contract invocation interface
|
|
|
|
// and notification subscription functionality.
|
2021-08-26 07:59:02 +00:00
|
|
|
//
|
2021-02-09 17:52:10 +00:00
|
|
|
// On connection lost tries establishing new connection
|
|
|
|
// to the next RPC (if any). If no RPC node available,
|
|
|
|
// switches to inactive mode: any RPC call leads to immediate
|
|
|
|
// return with ErrConnectionLost error, notification channel
|
|
|
|
// returned from Client.NotificationChannel is closed.
|
2020-07-24 13:54:03 +00:00
|
|
|
//
|
|
|
|
// Working client must be created via constructor New.
|
|
|
|
// Using the Client that has been created with new(Client)
|
|
|
|
// expression (or just declaring a Client variable) is unsafe
|
|
|
|
// and can lead to panic.
|
|
|
|
type Client struct {
|
2022-03-31 13:12:42 +00:00
|
|
|
cache cache
|
2021-08-26 07:59:02 +00:00
|
|
|
|
2023-06-08 13:37:46 +00:00
|
|
|
logger *logger.Logger // logging component
|
|
|
|
metrics morphmetrics.Register
|
2020-07-10 14:17:51 +00:00
|
|
|
|
2022-08-29 15:32:54 +00:00
|
|
|
client *rpcclient.WSClient // neo-go websocket client
|
|
|
|
rpcActor *actor.Actor // neo-go RPC actor
|
|
|
|
gasToken *nep17.Token // neo-go GAS token wrapper
|
2022-11-03 12:44:59 +00:00
|
|
|
rolemgmt *rolemgmt.Contract // neo-go Designation contract wrapper
|
2020-07-24 13:54:03 +00:00
|
|
|
|
2022-08-29 15:32:54 +00:00
|
|
|
acc *wallet.Account // neo account
|
|
|
|
accAddr util.Uint160 // account's address
|
2020-07-24 13:54:03 +00:00
|
|
|
|
2022-11-03 12:44:59 +00:00
|
|
|
notary *notaryInfo
|
2021-02-09 17:52:10 +00:00
|
|
|
|
|
|
|
cfg cfg
|
|
|
|
|
2022-06-29 13:02:07 +00:00
|
|
|
endpoints endpoints
|
2021-02-09 17:52:10 +00:00
|
|
|
|
2022-06-29 13:02:07 +00:00
|
|
|
// switchLock protects endpoints, inactive, and subscription-related fields.
|
|
|
|
// It is taken exclusively during endpoint switch and locked in shared mode
|
|
|
|
// on every normal call.
|
2023-05-30 07:14:37 +00:00
|
|
|
switchLock sync.RWMutex
|
2021-02-09 17:52:10 +00:00
|
|
|
|
|
|
|
// channel for internal stop
|
|
|
|
closeChan chan struct{}
|
2023-10-27 12:02:14 +00:00
|
|
|
closed atomic.Bool
|
|
|
|
wg sync.WaitGroup
|
2021-02-09 17:52:10 +00:00
|
|
|
|
|
|
|
// indicates that Client is not able to
|
|
|
|
// establish connection to any of the
|
|
|
|
// provided RPC endpoints
|
|
|
|
inactive bool
|
2022-08-22 16:36:41 +00:00
|
|
|
|
|
|
|
// indicates that Client has already started
|
|
|
|
// goroutine that tries to switch to the higher
|
|
|
|
// priority RPC node
|
|
|
|
switchIsActive atomic.Bool
|
2020-07-24 13:54:03 +00:00
|
|
|
}
|
2020-07-10 14:17:51 +00:00
|
|
|
|
2021-02-09 17:52:10 +00:00
|
|
|
type cache struct {
|
2023-05-30 07:14:37 +00:00
|
|
|
m sync.RWMutex
|
2022-03-31 13:12:42 +00:00
|
|
|
|
|
|
|
nnsHash *util.Uint160
|
|
|
|
gKey *keys.PublicKey
|
2022-12-30 09:47:18 +00:00
|
|
|
txHeights *lru.Cache[util.Uint256, uint32]
|
2023-05-26 09:15:50 +00:00
|
|
|
|
|
|
|
metrics metrics.MorphCacheMetrics
|
2021-09-21 14:30:45 +00:00
|
|
|
}
|
|
|
|
|
2023-05-31 12:37:18 +00:00
|
|
|
func (c *cache) nns() *util.Uint160 {
|
2022-03-31 13:12:42 +00:00
|
|
|
c.m.RLock()
|
|
|
|
defer c.m.RUnlock()
|
|
|
|
|
|
|
|
return c.nnsHash
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) setNNSHash(nnsHash util.Uint160) {
|
|
|
|
c.m.Lock()
|
|
|
|
defer c.m.Unlock()
|
|
|
|
|
|
|
|
c.nnsHash = &nnsHash
|
|
|
|
}
|
|
|
|
|
2023-05-31 12:37:18 +00:00
|
|
|
func (c *cache) groupKey() *keys.PublicKey {
|
2022-03-31 13:12:42 +00:00
|
|
|
c.m.RLock()
|
|
|
|
defer c.m.RUnlock()
|
|
|
|
|
|
|
|
return c.gKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) setGroupKey(groupKey *keys.PublicKey) {
|
|
|
|
c.m.Lock()
|
|
|
|
defer c.m.Unlock()
|
|
|
|
|
|
|
|
c.gKey = groupKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cache) invalidate() {
|
|
|
|
c.m.Lock()
|
|
|
|
defer c.m.Unlock()
|
|
|
|
|
|
|
|
c.nnsHash = nil
|
|
|
|
c.gKey = nil
|
|
|
|
c.txHeights.Purge()
|
|
|
|
}
|
|
|
|
|
2021-02-09 17:52:10 +00:00
|
|
|
var (
|
|
|
|
// ErrNilClient is returned by functions that expect
|
|
|
|
// a non-nil Client pointer, but received nil.
|
|
|
|
ErrNilClient = errors.New("client is nil")
|
|
|
|
|
|
|
|
// ErrConnectionLost is returned when client lost web socket connection
|
|
|
|
// to the RPC node and has not been able to establish a new one since.
|
|
|
|
ErrConnectionLost = errors.New("connection to the RPC node has been lost")
|
|
|
|
)
|
2020-07-10 14:17:51 +00:00
|
|
|
|
|
|
|
// HaltState returned if TestInvoke function processed without panic.
|
|
|
|
const HaltState = "HALT"
|
|
|
|
|
2021-06-15 13:47:57 +00:00
|
|
|
type notHaltStateError struct {
|
2021-04-09 20:10:59 +00:00
|
|
|
state, exception string
|
|
|
|
}
|
|
|
|
|
2021-06-15 13:47:57 +00:00
|
|
|
func (e *notHaltStateError) Error() string {
|
2021-04-09 20:10:59 +00:00
|
|
|
return fmt.Sprintf(
|
|
|
|
"chain/client: contract execution finished with state %s; exception: %s",
|
|
|
|
e.state,
|
|
|
|
e.exception,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-01-10 13:07:47 +00:00
|
|
|
// implementation of error interface for FrostFS-specific errors.
|
2022-12-23 17:35:35 +00:00
|
|
|
type frostfsError struct {
|
2021-08-31 10:16:25 +00:00
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2022-12-23 17:35:35 +00:00
|
|
|
func (e frostfsError) Error() string {
|
|
|
|
return fmt.Sprintf("frostfs error: %v", e.err)
|
2021-08-31 10:16:25 +00:00
|
|
|
}
|
|
|
|
|
2023-01-10 13:07:47 +00:00
|
|
|
// wraps FrostFS-specific error into frostfsError. Arg must not be nil.
|
|
|
|
func wrapFrostFSError(err error) error {
|
2022-12-23 17:35:35 +00:00
|
|
|
return frostfsError{err}
|
2021-08-31 10:16:25 +00:00
|
|
|
}
|
|
|
|
|
2020-07-10 14:17:51 +00:00
|
|
|
// Invoke invokes contract method by sending transaction into blockchain.
|
2023-11-07 15:13:26 +00:00
|
|
|
// Returns valid until block value.
|
2020-07-10 14:17:51 +00:00
|
|
|
// Supported args types: int64, string, util.Uint160, []byte and bool.
|
2024-11-14 07:01:59 +00:00
|
|
|
func (c *Client) Invoke(ctx context.Context, contract util.Uint160, fee fixedn.Fixed8, method string, args ...any) (InvokeRes, error) {
|
2023-06-08 13:37:46 +00:00
|
|
|
start := time.Now()
|
|
|
|
success := false
|
|
|
|
defer func() {
|
|
|
|
c.metrics.ObserveInvoke("Invoke", contract.String(), method, success, time.Since(start))
|
|
|
|
}()
|
|
|
|
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
2024-11-14 07:01:59 +00:00
|
|
|
return InvokeRes{}, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
txHash, vub, err := c.rpcActor.SendTunedCall(contract, method, nil, addFeeCheckerModifier(int64(fee)), args...)
|
2020-07-10 14:17:51 +00:00
|
|
|
if err != nil {
|
2024-12-12 12:06:20 +00:00
|
|
|
return InvokeRes{}, fmt.Errorf("invoke %s: %w", method, err)
|
2020-07-10 14:17:51 +00:00
|
|
|
}
|
|
|
|
|
2024-10-21 13:27:28 +00:00
|
|
|
c.logger.Debug(ctx, logs.ClientNeoClientInvoke,
|
2020-07-10 14:17:51 +00:00
|
|
|
zap.String("method", method),
|
2022-08-29 15:32:54 +00:00
|
|
|
zap.Uint32("vub", vub),
|
2020-12-04 16:36:41 +00:00
|
|
|
zap.Stringer("tx_hash", txHash.Reverse()))
|
2020-07-10 14:17:51 +00:00
|
|
|
|
2023-06-08 13:37:46 +00:00
|
|
|
success = true
|
2024-11-14 07:01:59 +00:00
|
|
|
return InvokeRes{Hash: txHash, VUB: vub}, nil
|
2020-07-10 14:17:51 +00:00
|
|
|
}
|
|
|
|
|
2023-03-24 08:54:59 +00:00
|
|
|
// TestInvokeIterator invokes contract method returning an iterator and executes cb on each element.
|
|
|
|
// If cb returns an error, the session is closed and this error is returned as-is.
|
|
|
|
// If the remove neo-go node does not support sessions, `unwrap.ErrNoSessionID` is returned.
|
2024-01-11 10:47:17 +00:00
|
|
|
// batchSize is the number of items to prefetch: if the number of items in the iterator is less than batchSize, no session will be created.
|
2024-01-25 08:21:00 +00:00
|
|
|
// The default batchSize is 100, the default limit from neo-go.
|
2024-01-11 10:47:17 +00:00
|
|
|
func (c *Client) TestInvokeIterator(cb func(stackitem.Item) error, batchSize int, contract util.Uint160, method string, args ...interface{}) error {
|
2023-06-08 13:37:46 +00:00
|
|
|
start := time.Now()
|
|
|
|
success := false
|
|
|
|
defer func() {
|
|
|
|
c.metrics.ObserveInvoke("TestInvokeIterator", contract.String(), method, success, time.Since(start))
|
|
|
|
}()
|
|
|
|
|
2024-01-11 10:47:17 +00:00
|
|
|
if batchSize <= 0 {
|
2024-01-25 08:21:00 +00:00
|
|
|
batchSize = invoker.DefaultIteratorResultItems
|
2024-01-11 10:47:17 +00:00
|
|
|
}
|
|
|
|
|
2023-03-24 08:54:59 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return ErrConnectionLost
|
|
|
|
}
|
|
|
|
|
2024-01-11 10:47:17 +00:00
|
|
|
script, err := smartcontract.CreateCallAndPrefetchIteratorScript(contract, method, batchSize, args...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
val, err := c.rpcActor.Run(script)
|
2023-03-24 08:54:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if val.State != HaltState {
|
|
|
|
return wrapFrostFSError(¬HaltStateError{state: val.State, exception: val.FaultException})
|
|
|
|
}
|
|
|
|
|
2024-01-11 10:47:17 +00:00
|
|
|
arr, sid, r, err := unwrap.ArrayAndSessionIterator(val, err)
|
2023-03-24 08:54:59 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-11 10:47:17 +00:00
|
|
|
for i := range arr {
|
|
|
|
if err := cb(arr[i]); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (sid == uuid.UUID{}) {
|
|
|
|
success = true
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-24 08:54:59 +00:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
_ = c.rpcActor.TerminateSession(sid)
|
|
|
|
}()
|
|
|
|
|
2024-02-21 12:22:50 +00:00
|
|
|
// Batch size for TraverseIterator() can restricted on the server-side.
|
|
|
|
traverseBatchSize := batchSize
|
|
|
|
if invoker.DefaultIteratorResultItems < traverseBatchSize {
|
|
|
|
traverseBatchSize = invoker.DefaultIteratorResultItems
|
|
|
|
}
|
2024-01-11 10:47:17 +00:00
|
|
|
for {
|
2024-02-21 12:22:50 +00:00
|
|
|
items, err := c.rpcActor.TraverseIterator(sid, &r, traverseBatchSize)
|
2024-01-11 10:47:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-03-24 08:54:59 +00:00
|
|
|
for i := range items {
|
2024-01-11 10:47:17 +00:00
|
|
|
if err := cb(items[i]); err != nil {
|
2023-03-24 08:54:59 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2024-02-21 12:22:50 +00:00
|
|
|
if len(items) < traverseBatchSize {
|
2024-01-11 10:47:17 +00:00
|
|
|
break
|
|
|
|
}
|
2023-03-24 08:54:59 +00:00
|
|
|
}
|
2024-01-11 10:47:17 +00:00
|
|
|
success = true
|
|
|
|
return nil
|
2023-03-24 08:54:59 +00:00
|
|
|
}
|
|
|
|
|
2020-07-10 14:17:51 +00:00
|
|
|
// TestInvoke invokes contract method locally in neo-go node. This method should
|
|
|
|
// be used to read data from smart-contract.
|
2023-02-21 11:42:45 +00:00
|
|
|
func (c *Client) TestInvoke(contract util.Uint160, method string, args ...any) (res []stackitem.Item, err error) {
|
2023-06-08 13:37:46 +00:00
|
|
|
start := time.Now()
|
|
|
|
success := false
|
|
|
|
defer func() {
|
|
|
|
c.metrics.ObserveInvoke("TestInvoke", contract.String(), method, success, time.Since(start))
|
|
|
|
}()
|
|
|
|
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return nil, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
val, err := c.rpcActor.Call(contract, method, args...)
|
2020-07-10 14:17:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if val.State != HaltState {
|
2023-01-10 13:07:47 +00:00
|
|
|
return nil, wrapFrostFSError(¬HaltStateError{state: val.State, exception: val.FaultException})
|
2020-07-10 14:17:51 +00:00
|
|
|
}
|
|
|
|
|
2023-06-08 13:37:46 +00:00
|
|
|
success = true
|
2020-07-10 14:17:51 +00:00
|
|
|
return val.Stack, nil
|
|
|
|
}
|
|
|
|
|
2022-10-17 12:03:55 +00:00
|
|
|
// TransferGas to the receiver from local wallet.
|
2020-12-11 08:33:27 +00:00
|
|
|
func (c *Client) TransferGas(receiver util.Uint160, amount fixedn.Fixed8) error {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
txHash, vub, err := c.gasToken.Transfer(c.accAddr, receiver, big.NewInt(int64(amount)), nil)
|
2020-07-10 14:17:51 +00:00
|
|
|
if err != nil {
|
2020-07-24 13:54:03 +00:00
|
|
|
return err
|
2020-07-10 14:17:51 +00:00
|
|
|
}
|
|
|
|
|
2024-10-21 07:22:54 +00:00
|
|
|
c.logger.Debug(context.Background(), logs.ClientNativeGasTransferInvoke,
|
2020-07-24 13:54:03 +00:00
|
|
|
zap.String("to", receiver.StringLE()),
|
2022-08-29 15:32:54 +00:00
|
|
|
zap.Stringer("tx_hash", txHash.Reverse()),
|
|
|
|
zap.Uint32("vub", vub))
|
2020-07-10 14:17:51 +00:00
|
|
|
|
2020-07-24 13:54:03 +00:00
|
|
|
return nil
|
2020-07-10 14:17:51 +00:00
|
|
|
}
|
|
|
|
|
2023-03-09 13:19:39 +00:00
|
|
|
func (c *Client) BatchTransferGas(receivers []util.Uint160, amount fixedn.Fixed8) error {
|
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return ErrConnectionLost
|
|
|
|
}
|
|
|
|
|
|
|
|
transferParams := make([]nep17.TransferParameters, len(receivers))
|
|
|
|
receiversLog := make([]string, len(receivers))
|
|
|
|
|
|
|
|
for i, receiver := range receivers {
|
|
|
|
transferParams[i] = nep17.TransferParameters{
|
|
|
|
From: c.accAddr,
|
|
|
|
To: receiver,
|
|
|
|
Amount: big.NewInt(int64(amount)),
|
|
|
|
Data: nil,
|
|
|
|
}
|
|
|
|
receiversLog[i] = receiver.StringLE()
|
|
|
|
}
|
|
|
|
|
|
|
|
txHash, vub, err := c.gasToken.MultiTransfer(transferParams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-10-21 07:22:54 +00:00
|
|
|
c.logger.Debug(context.Background(), logs.ClientBatchGasTransferInvoke,
|
2023-03-09 13:19:39 +00:00
|
|
|
zap.Strings("to", receiversLog),
|
|
|
|
zap.Stringer("tx_hash", txHash.Reverse()),
|
|
|
|
zap.Uint32("vub", vub))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:50:10 +00:00
|
|
|
// Wait function blocks routing execution until there
|
|
|
|
// are `n` new blocks in the chain.
|
2021-08-26 07:59:02 +00:00
|
|
|
//
|
|
|
|
// Returns only connection errors.
|
|
|
|
func (c *Client) Wait(ctx context.Context, n uint32) error {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2021-02-25 16:50:10 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
height, newHeight uint32
|
|
|
|
)
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
height, err = c.rpcActor.GetBlockCount()
|
2021-02-25 16:50:10 +00:00
|
|
|
if err != nil {
|
2024-10-21 08:20:17 +00:00
|
|
|
c.logger.Error(ctx, logs.ClientCantGetBlockchainHeight,
|
2024-12-13 08:44:56 +00:00
|
|
|
zap.Error(err))
|
2021-08-26 07:59:02 +00:00
|
|
|
return nil
|
2021-02-25 16:50:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
2023-03-28 14:58:28 +00:00
|
|
|
return ctx.Err()
|
2021-02-25 16:50:10 +00:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
newHeight, err = c.rpcActor.GetBlockCount()
|
2021-02-25 16:50:10 +00:00
|
|
|
if err != nil {
|
2024-10-21 08:20:17 +00:00
|
|
|
c.logger.Error(ctx, logs.ClientCantGetBlockchainHeight243,
|
2024-12-13 08:44:56 +00:00
|
|
|
zap.Error(err))
|
2021-08-26 07:59:02 +00:00
|
|
|
return nil
|
2021-02-25 16:50:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if newHeight >= height+n {
|
2021-08-26 07:59:02 +00:00
|
|
|
return nil
|
2021-02-25 16:50:10 +00:00
|
|
|
}
|
|
|
|
|
2021-02-09 17:52:10 +00:00
|
|
|
time.Sleep(c.cfg.waitInterval)
|
2021-02-25 16:50:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GasBalance returns GAS amount in the client's wallet.
|
2021-08-26 07:59:02 +00:00
|
|
|
func (c *Client) GasBalance() (res int64, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return 0, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
bal, err := c.gasToken.BalanceOf(c.accAddr)
|
2022-08-29 15:32:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return bal.Int64(), nil
|
2021-02-25 16:50:10 +00:00
|
|
|
}
|
|
|
|
|
2021-03-09 14:45:35 +00:00
|
|
|
// Committee returns keys of chain committee from neo native contract.
|
2021-08-26 07:59:02 +00:00
|
|
|
func (c *Client) Committee() (res keys.PublicKeys, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return nil, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2021-04-08 14:40:49 +00:00
|
|
|
return c.client.GetCommittee()
|
2021-03-09 14:45:35 +00:00
|
|
|
}
|
|
|
|
|
2021-04-27 11:07:06 +00:00
|
|
|
// TxHalt returns true if transaction has been successfully executed and persisted.
|
2021-08-26 07:59:02 +00:00
|
|
|
func (c *Client) TxHalt(h util.Uint256) (res bool, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return false, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2021-04-27 11:07:06 +00:00
|
|
|
trig := trigger.Application
|
|
|
|
aer, err := c.client.GetApplicationLog(h, &trig)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2022-07-18 08:33:53 +00:00
|
|
|
return len(aer.Executions) > 0 && aer.Executions[0].VMState.HasFlag(vmstate.Halt), nil
|
2021-04-27 11:07:06 +00:00
|
|
|
}
|
|
|
|
|
2024-10-31 10:13:03 +00:00
|
|
|
func (c *Client) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*result.ApplicationLog, error) {
|
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return nil, ErrConnectionLost
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.client.GetApplicationLog(hash, trig)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) GetVersion() (*result.Version, error) {
|
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return nil, ErrConnectionLost
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.client.GetVersion()
|
|
|
|
}
|
|
|
|
|
2022-04-04 07:07:33 +00:00
|
|
|
// TxHeight returns true if transaction has been successfully executed and persisted.
|
|
|
|
func (c *Client) TxHeight(h util.Uint256) (res uint32, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return 0, ErrConnectionLost
|
2022-04-04 07:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.client.GetTransactionHeight(h)
|
|
|
|
}
|
|
|
|
|
2021-03-23 10:03:23 +00:00
|
|
|
// NeoFSAlphabetList returns keys that stored in NeoFS Alphabet role. Main chain
|
2022-04-21 11:28:05 +00:00
|
|
|
// stores alphabet node keys of inner ring there, however the sidechain stores both
|
2021-03-23 10:03:23 +00:00
|
|
|
// alphabet and non alphabet node keys of inner ring.
|
2021-08-26 07:59:02 +00:00
|
|
|
func (c *Client) NeoFSAlphabetList() (res keys.PublicKeys, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return nil, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2021-04-08 14:40:49 +00:00
|
|
|
list, err := c.roleList(noderoles.NeoFSAlphabet)
|
2021-03-23 10:03:23 +00:00
|
|
|
if err != nil {
|
2024-12-12 12:06:20 +00:00
|
|
|
return nil, fmt.Errorf("get alphabet nodes role list: %w", err)
|
2021-03-23 10:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return list, nil
|
|
|
|
}
|
|
|
|
|
2021-05-18 07:40:21 +00:00
|
|
|
// GetDesignateHash returns hash of the native `RoleManagement` contract.
|
2022-11-03 12:44:59 +00:00
|
|
|
func (c *Client) GetDesignateHash() util.Uint160 {
|
|
|
|
return rolemgmt.Hash
|
2021-05-18 07:40:21 +00:00
|
|
|
}
|
|
|
|
|
2021-04-08 14:40:49 +00:00
|
|
|
func (c *Client) roleList(r noderoles.Role) (keys.PublicKeys, error) {
|
2022-08-31 08:58:50 +00:00
|
|
|
height, err := c.rpcActor.GetBlockCount()
|
2021-03-23 10:02:24 +00:00
|
|
|
if err != nil {
|
2024-12-12 12:06:20 +00:00
|
|
|
return nil, fmt.Errorf("get chain height: %w", err)
|
2021-03-23 10:02:24 +00:00
|
|
|
}
|
|
|
|
|
2022-11-03 12:44:59 +00:00
|
|
|
return c.rolemgmt.GetDesignatedByRole(r, height)
|
2021-03-23 10:02:24 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 07:54:26 +00:00
|
|
|
// MagicNumber returns the magic number of the network
|
|
|
|
// to which the underlying RPC node client is connected.
|
2022-03-27 17:30:08 +00:00
|
|
|
func (c *Client) MagicNumber() (uint64, error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return 0, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
return uint64(c.rpcActor.GetNetwork()), nil
|
2021-02-19 07:54:26 +00:00
|
|
|
}
|
2021-07-21 15:12:19 +00:00
|
|
|
|
|
|
|
// BlockCount returns block count of the network
|
|
|
|
// to which the underlying RPC node client is connected.
|
2021-08-26 07:59:02 +00:00
|
|
|
func (c *Client) BlockCount() (res uint32, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return 0, ErrConnectionLost
|
2021-08-26 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
return c.rpcActor.GetBlockCount()
|
2021-07-21 15:12:19 +00:00
|
|
|
}
|
2021-09-20 16:01:11 +00:00
|
|
|
|
|
|
|
// MsPerBlock returns MillisecondsPerBlock network parameter.
|
|
|
|
func (c *Client) MsPerBlock() (res int64, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return 0, ErrConnectionLost
|
2021-09-20 16:01:11 +00:00
|
|
|
}
|
|
|
|
|
2022-08-31 08:58:50 +00:00
|
|
|
v := c.rpcActor.GetVersion()
|
2021-09-20 16:01:11 +00:00
|
|
|
|
|
|
|
return int64(v.Protocol.MillisecondsPerBlock), nil
|
|
|
|
}
|
2021-11-16 16:20:04 +00:00
|
|
|
|
|
|
|
// IsValidScript returns true if invocation script executes with HALT state.
|
2023-02-22 13:06:42 +00:00
|
|
|
func (c *Client) IsValidScript(script []byte, signers []transaction.Signer) (valid bool, err error) {
|
2021-02-09 17:52:10 +00:00
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
if c.inactive {
|
|
|
|
return false, ErrConnectionLost
|
2021-11-16 16:20:04 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 13:06:42 +00:00
|
|
|
res, err := c.client.InvokeScript(script, signers)
|
2021-11-16 16:20:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("invokeScript: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-02-22 13:06:42 +00:00
|
|
|
return res.State == vmstate.Halt.String(), nil
|
2021-11-16 16:20:04 +00:00
|
|
|
}
|
2021-02-09 17:52:10 +00:00
|
|
|
|
2023-06-08 13:37:46 +00:00
|
|
|
func (c *Client) Metrics() morphmetrics.Register {
|
|
|
|
return c.metrics
|
|
|
|
}
|
|
|
|
|
2022-11-03 12:44:59 +00:00
|
|
|
func (c *Client) setActor(act *actor.Actor) {
|
|
|
|
c.rpcActor = act
|
|
|
|
c.gasToken = nep17.New(act, gas.Hash)
|
|
|
|
c.rolemgmt = rolemgmt.New(act)
|
|
|
|
}
|
2023-12-13 12:46:31 +00:00
|
|
|
|
|
|
|
func (c *Client) GetActor() *actor.Actor {
|
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
return c.rpcActor
|
|
|
|
}
|
2024-04-26 14:27:56 +00:00
|
|
|
|
|
|
|
func (c *Client) GetRPCActor() actor.RPCActor {
|
|
|
|
c.switchLock.RLock()
|
|
|
|
defer c.switchLock.RUnlock()
|
|
|
|
|
|
|
|
return c.client
|
|
|
|
}
|