Merge pull request #2090 from nspcc-dev/new-query-commands

New query commands
This commit is contained in:
Roman Khimov 2021-07-23 10:39:57 +03:00 committed by GitHub
commit 6e2eddbeb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 330 additions and 85 deletions

View file

@ -15,6 +15,8 @@ import (
func TestRegisterCandidate(t *testing.T) {
e := newExecutor(t, true)
validatorHex := hex.EncodeToString(validatorPriv.PublicKey().Bytes())
e.In.WriteString("one\r")
e.Run(t, "neo-go", "wallet", "nep17", "multitransfer",
"--rpc-endpoint", "http://"+e.RPC.Addr,
@ -24,6 +26,15 @@ func TestRegisterCandidate(t *testing.T) {
"GAS:"+validatorPriv.Address()+":10000")
e.checkTxPersisted(t)
e.Run(t, "neo-go", "query", "committee",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*"+validatorHex)
e.Run(t, "neo-go", "query", "candidates",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*Key.+$") // Header.
e.checkEOF(t)
// missing address
e.RunWithError(t, "neo-go", "wallet", "candidate", "register",
"--rpc-endpoint", "http://"+e.RPC.Addr,
@ -48,7 +59,7 @@ func TestRegisterCandidate(t *testing.T) {
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet,
"--address", validatorPriv.Address(),
"--candidate", hex.EncodeToString(validatorPriv.PublicKey().Bytes()))
"--candidate", validatorHex)
_, index := e.checkTxPersisted(t)
vs, err = e.Chain.GetEnrollments()
@ -57,11 +68,21 @@ func TestRegisterCandidate(t *testing.T) {
b, _ := e.Chain.GetGoverningTokenBalance(validatorPriv.GetScriptHash())
require.Equal(t, b, vs[0].Votes)
e.Run(t, "neo-go", "query", "committee",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*"+validatorHex)
e.Run(t, "neo-go", "query", "candidates",
"--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, "^\\s*Key.+$") // Header.
e.checkNextLine(t, "^\\s*"+validatorHex+"\\s*"+b.String()+"\\s*true\\s*true$")
e.checkEOF(t)
// check state
e.Run(t, "neo-go", "wallet", "candidate", "getstate",
e.Run(t, "neo-go", "query", "voter",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--address", validatorPriv.Address())
e.checkNextLine(t, "^\\s*Voted:\\s+"+validatorPriv.Address())
validatorPriv.Address())
e.checkNextLine(t, "^\\s*Voted:\\s+"+validatorHex+"\\s+\\("+validatorPriv.Address()+"\\)$")
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$")
e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
e.checkEOF(t)
@ -80,9 +101,9 @@ func TestRegisterCandidate(t *testing.T) {
require.Equal(t, big.NewInt(0), vs[0].Votes)
// check state
e.Run(t, "neo-go", "wallet", "candidate", "getstate",
e.Run(t, "neo-go", "query", "voter",
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--address", validatorPriv.Address())
validatorPriv.Address())
e.checkNextLine(t, "^\\s*Voted:\\s+"+"null") // no vote.
e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$")
e.checkNextLine(t, "^\\s*Block\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
@ -104,6 +125,6 @@ func TestRegisterCandidate(t *testing.T) {
vs, err = e.Chain.GetEnrollments()
require.Equal(t, 0, len(vs))
// getstate: missing address
e.RunWithError(t, "neo-go", "wallet", "candidate", "getstate")
// query voter: missing address
e.RunWithError(t, "neo-go", "query", "voter")
}

View file

@ -3,17 +3,24 @@ package query
import (
"bytes"
"encoding/base64"
"encoding/hex"
"fmt"
"sort"
"strconv"
"strings"
"text/tabwriter"
"github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/urfave/cli"
)
@ -29,12 +36,36 @@ func NewCommands() []cli.Command {
Name: "query",
Usage: "Query data from RPC node",
Subcommands: []cli.Command{
{
Name: "candidates",
Usage: "Get candidates and votes",
Action: queryCandidates,
Flags: options.RPC,
},
{
Name: "committee",
Usage: "Get committee list",
Action: queryCommittee,
Flags: options.RPC,
},
{
Name: "height",
Usage: "Get node height",
Action: queryHeight,
Flags: options.RPC,
},
{
Name: "tx",
Usage: "Query transaction status",
Action: queryTx,
Flags: queryTxFlags,
},
{
Name: "voter",
Usage: "Print NEO holder account state",
Action: queryVoter,
Flags: options.RPC,
},
},
}}
}
@ -113,3 +144,150 @@ func dumpApplicationLog(ctx *cli.Context, res *result.ApplicationLog, tx *result
_ = tw.Flush()
fmt.Fprint(ctx.App.Writer, buf.String())
}
func queryCandidates(ctx *cli.Context) error {
var err error
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}
vals, err := c.GetNextBlockValidators()
if err != nil {
return cli.NewExitError(err, 1)
}
comm, err := c.GetCommittee()
if err != nil {
return cli.NewExitError(err, 1)
}
sort.Slice(vals, func(i, j int) bool {
if vals[i].Active != vals[j].Active {
return vals[i].Active
}
if vals[i].Votes != vals[j].Votes {
return vals[i].Votes > vals[j].Votes
}
return vals[i].PublicKey.Cmp(&vals[j].PublicKey) == -1
})
buf := bytes.NewBuffer(nil)
tw := tabwriter.NewWriter(buf, 0, 2, 2, ' ', 0)
_, _ = tw.Write([]byte("Key\tVotes\tCommittee\tConsensus\n"))
for _, val := range vals {
_, _ = tw.Write([]byte(fmt.Sprintf("%s\t%d\t%t\t%t\n", hex.EncodeToString(val.PublicKey.Bytes()), val.Votes, comm.Contains(&val.PublicKey), val.Active)))
}
_ = tw.Flush()
fmt.Fprint(ctx.App.Writer, buf.String())
return nil
}
func queryCommittee(ctx *cli.Context) error {
var err error
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}
comm, err := c.GetCommittee()
if err != nil {
return cli.NewExitError(err, 1)
}
for _, k := range comm {
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(k.Bytes()))
}
return nil
}
func queryHeight(ctx *cli.Context) error {
var err error
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, err := options.GetRPCClient(gctx, ctx)
if err != nil {
return cli.NewExitError(err, 1)
}
blockCount, err := c.GetBlockCount()
if err != nil {
return cli.NewExitError(err, 1)
}
blockHeight := blockCount - 1 // GetBlockCount returns block count (including 0), not the highest block index.
fmt.Fprintf(ctx.App.Writer, "Latest block: %d\n", blockHeight)
stateHeight, err := c.GetStateHeight()
if err == nil { // We can be talking to a node without getstateheight request support.
fmt.Fprintf(ctx.App.Writer, "Validated state: %d\n", stateHeight.Validated)
}
return nil
}
func queryVoter(ctx *cli.Context) error {
args := ctx.Args()
if len(args) == 0 {
return cli.NewExitError("No address specified", 1)
}
addr, err := flags.ParseAddress(args[0])
if err != nil {
return cli.NewExitError(fmt.Sprintf("wrong address: %s", args[0]), 1)
}
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, exitErr := options.GetRPCClient(gctx, ctx)
if exitErr != nil {
return exitErr
}
neoHash, err := c.GetNativeContractHash(nativenames.Neo)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to get NEO contract hash: %w", err), 1)
}
res, err := c.InvokeFunction(neoHash, "getAccountState", []smartcontract.Parameter{
{
Type: smartcontract.Hash160Type,
Value: addr,
},
}, nil)
if err != nil {
return cli.NewExitError(err, 1)
}
if res.State != "HALT" {
return cli.NewExitError(fmt.Errorf("invocation failed: %s", res.FaultException), 1)
}
if len(res.Stack) == 0 {
return cli.NewExitError("result stack is empty", 1)
}
st := new(state.NEOBalance)
if _, ok := res.Stack[0].(stackitem.Null); !ok {
err = st.FromStackItem(res.Stack[0])
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to convert account state from stackitem: %w", err), 1)
}
}
dec, err := c.NEP17Decimals(neoHash)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1)
}
voted := "null"
if st.VoteTo != nil {
voted = fmt.Sprintf("%s (%s)", hex.EncodeToString(st.VoteTo.Bytes()), address.Uint160ToString(st.VoteTo.GetScriptHash()))
}
fmt.Fprintf(ctx.App.Writer, "\tVoted: %s\n", voted)
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", fixedn.ToString(&st.Balance, int(dec)))
fmt.Fprintf(ctx.App.Writer, "\tBlock: %d\n", st.BalanceHeight)
return nil
}

View file

@ -130,3 +130,12 @@ func (e *executor) compareQueryTxVerbose(t *testing.T, tx *transaction.Transacti
}
e.checkEOF(t)
}
func TestQueryHeight(t *testing.T) {
e := newExecutor(t, true)
e.Run(t, "neo-go", "query", "height", "--rpc-endpoint", "http://"+e.RPC.Addr)
e.checkNextLine(t, `^Latest block: [0-9]+$`)
e.checkNextLine(t, `^Validated state: [0-9]+$`)
e.checkEOF(t)
}

View file

@ -7,14 +7,11 @@ import (
"github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"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"
@ -74,18 +71,6 @@ func newValidatorCommands() []cli.Command {
},
}, options.RPC...),
},
{
Name: "getstate",
Usage: "print NEO holder account state",
UsageText: "getstate -a <addr>",
Action: getAccountState,
Flags: append([]cli.Flag{
flags.AddressFlag{
Name: "address, a",
Usage: "Address to get state of",
},
}, options.RPC...),
},
}
}
@ -227,54 +212,3 @@ func getDecryptedAccount(ctx *cli.Context, wall *wallet.Wallet, addr util.Uint16
}
return acc, nil
}
func getAccountState(ctx *cli.Context) error {
addrFlag := ctx.Generic("address").(*flags.Address)
if !addrFlag.IsSet {
return cli.NewExitError("address was not provided", 1)
}
gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel()
c, exitErr := options.GetRPCClient(gctx, ctx)
if exitErr != nil {
return exitErr
}
neoHash, err := c.GetNativeContractHash(nativenames.Neo)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to get NEO contract hash: %w", err), 1)
}
res, err := c.InvokeFunction(neoHash, "getAccountState", []smartcontract.Parameter{
{
Type: smartcontract.Hash160Type,
Value: addrFlag.Uint160(),
},
}, nil)
if err != nil {
return cli.NewExitError(err, 1)
}
if res.State != "HALT" {
return cli.NewExitError(fmt.Errorf("invocation failed: %s", res.FaultException), 1)
}
if len(res.Stack) == 0 {
return cli.NewExitError("result stack is empty", 1)
}
st := new(state.NEOBalance)
err = st.FromStackItem(res.Stack[0])
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to convert account state from stackitem: %w", err), 1)
}
dec, err := c.NEP17Decimals(neoHash)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1)
}
voted := "null"
if st.VoteTo != nil {
voted = address.Uint160ToString(st.VoteTo.GetScriptHash())
}
fmt.Fprintf(ctx.App.Writer, "\tVoted: %s\n", voted)
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", fixedn.ToString(&st.Balance, int(dec)))
fmt.Fprintf(ctx.App.Writer, "\tBlock: %d\n", st.BalanceHeight)
return nil
}

View file

@ -464,7 +464,17 @@ You can also vote for candidates if you own NEO:
./bin/neo-go wallet candidate vote -a NMe64G6j6nkPZby26JAgpaCNrn1Ee4wW6E -w wallet.json -r http://localhost:20332 -c 03cecd63d7d8120c3b194c3b2880dd4aafe1475c57e40c852872d7305615258140
```
### Querying transaction status
### Getting data from chain
#### Node height/validated height
`query height` returns the latest block and validated state height:
```
$ ./bin/neo-go query height -r http://localhost:20332
Latest block: 11926
Validated state: 11926
```
#### Transaction status
`query tx` provides convenient wrapper over RPC calls to query transaction status.
```
./bin/neo-go query tx --rpc-endpoint http://localhost:20332 aaf87628851e0c03ee086ff88596bc24de87082e9e5c73d75bb1c740d1d68088
@ -476,6 +486,72 @@ Success: true
`OnChain` is true if transaction was included in block and `Success` is true
if it was executed successfully.
#### Committee members
`query commitee` returns a list of current committee members:
```
$ ./bin/neo-go query committee -r http://localhost:20332
03009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a2
030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba
0207da870cedb777fceff948641021714ec815110ca111ccc7a54c168e065bda70
02147c1b1d5728e1954958daff2f88ee2fa50a06890a8a9db3fa9e972b66ae559f
0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01
03184b018d6b2bc093e535519732b3fd3f7551c8cffaf4621dd5a0b89482ca66c9
0231edee3978d46c335e851c76059166eb8878516f459e085c0dd092f0f1d51c21
023e9b32ea89b94d066e649b124fd50e396ee91369e8e2a6ae1b11c170d022256d
03408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a259477806
035056669864feea401d8c31e447fb82dd29f342a9476cfd449584ce2a6165e4d7
025831cee3708e87d78211bec0d1bfee9f4c85ae784762f042e7f31c0d40c329b8
026328aae34f149853430f526ecaa9cf9c8d78a4ea82d08bdf63dd03c4d0693be6
0370c75c54445565df62cfe2e76fbec4ba00d1298867972213530cae6d418da636
03840415b0a0fcf066bcc3dc92d8349ebd33a6ab1402ef649bae00e5d9f5840828
03957af9e77282ae3263544b7b2458903624adc3f5dee303957cb6570524a5f254
02a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b
02ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd
03c609bea5a4825908027e4ab217e7efc06e311f19ecad9d417089f14927a173d5
02c69a8d084ee7319cfecf5161ff257aa2d1f53e79bf6c6f164cff5d94675c38b3
02cf9dc6e85d581480d91e88e8cbeaa0c153a046e89ded08b4cefd851e1d7325b5
03d84d22b8753cf225d263a3a782a4e16ca72ef323cfde04977c74f14873ab1e4c
```
#### Candidate/voting data
`query candidates` returns all current candidates, number of votes for them
and their committee/consensus status:
```
$ ./bin/neo-go query candidates -r http://localhost:20332
Key Votes Committee Consensus
03009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a2 2000000 true true
030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba 2000000 true true
0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01 2000000 true true
023e9b32ea89b94d066e649b124fd50e396ee91369e8e2a6ae1b11c170d022256d 2000000 true true
03408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a259477806 2000000 true true
02a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b 2000000 true true
02ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd 2000000 true true
025664cef0abcba7787ad5fb12f3af31c5cdc7a479068aa2ad8ee78804768bffe9 1000000 false false
03650a684461a64bf46bee561d9981a4c57adc6ccbd3a9512b83701480b30218ab 1000000 false false
026a10aa2b4d7639c5deafa4ff081467db10b5d00432749a2a5ee1d2bfed23e1c0 1000000 false false
02d5786a9214a8a3f1757d7596fd10f5241205e2c0d68362f4766579bac6189249 1000000 false false
033d8e35f8cd9a33852280b6d93093c7292ed5ce90d90f149fa2da50ba6168dfce 100000 false false
0349c7ef0b4aaf181f0a3e1350c527b136cc5b42498cb83ab8880c05ed95167e1c 100000 false false
035b4f9be2b853e06eb5a09c167e038b96b4804235961510423252f2ee3dbba583 100000 false false
027e459b264b6f7e325ab4b0bb0fa641081fb68517fd613ebd7a94cb79d3081e4f 100000 false false
0288cad442a877960c76b4f688f4be30f768256d9a3da2492b0180b91243918b4f 100000 false false
02a40c552798f79636095817ec88924fc6cb7094e5a3cb059a9b3bc91ea3bf0d3d 100000 false false
02db79e69c518ae9254e314b6f5f4b63e914cdd4b2574dc2f9236c01c1fc1d8973 100000 false false
02ec143f00b88524caf36a0121c2de09eef0519ddbe1c710a00f0e2663201ee4c0 100000 false false
03d8d58d2257ca6cb14522b76513d4783f7d481801695893794c2186515c6de76f 0 false false
```
#### Voter data
`query voter` returns additional data about NEO holder: amount of NEO he has,
candidate he voted for (if any) and block number of the last transactions
involving NEO on this account:
```
$ ./bin/neo-go query voter -r http://localhost:20332 Nj91C8TxQSxW1jCE1ytFre6mg5qxTypg1Y
Voted: 0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01 (Nj91C8TxQSxW1jCE1ytFre6mg5qxTypg1Y)
Amount : 2000000
Block: 3970
```
### NEP-17 token functions
`wallet nep17` contains a set of commands to use for NEP-17 tokens.

View file

@ -92,16 +92,16 @@ use.
This command will create and send appropriate transaction to the network and
you should then wait for it to settle in a block. If all goes well it'll end
with "HALT" state and your registration will be completed. You can use
`query tx` command to see transaction status or
`getnextblockvalidators` to see if your candidate was added.
`query tx` command to see transaction status or `query candidates` to see if
your candidate was added.
### Voting
After registration completion if you own some NEO you can also vote for your
candidate to help it become CN and receive additional voter GAS. To do that
you need to know the public key of your candidate, which can either be seen in
`getnextblockvalidators` RPC call output or extracted from wallet `wallet
dump-keys` command:
`query candidates` command output or extracted from wallet `wallet dump-keys`
command:
```
$ neo-go wallet dump-keys -w wallet.json

View file

@ -374,6 +374,18 @@ func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.Transactio
return resp, nil
}
// GetStateHeight returns current validated and local node state height.
func (c *Client) GetStateHeight() (*result.StateHeight, error) {
var (
params = request.NewRawParams()
resp = new(result.StateHeight)
)
if err := c.performRequest("getstateheight", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetStorageByID returns the stored value, according to the contract ID and the stored key.
func (c *Client) GetStorageByID(id int32, key []byte) ([]byte, error) {
return c.getStorage(request.NewRawParams(id, base64.StdEncoding.EncodeToString(key)))

View file

@ -680,6 +680,21 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
},
},
},
"getstateheight": {
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
return c.GetStateHeight()
},
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"localrootindex":11646,"validatedrootindex":11645}}`,
result: func(c *Client) interface{} {
return &result.StateHeight{
Local: 11646,
Validated: 11645,
}
},
},
},
"getstorage": {
{
name: "by hash, positive",

View file

@ -10,8 +10,8 @@ import (
// StateHeight is a result of getstateheight RPC.
type StateHeight struct {
BlockHeight uint32 `json:"blockHeight"`
StateHeight uint32 `json:"stateHeight"`
Local uint32 `json:"localrootindex"`
Validated uint32 `json:"validatedrootindex"`
}
// ProofWithKey represens key-proof pair.

View file

@ -969,8 +969,8 @@ func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error)
stateHeight = height - 1
}
return &result.StateHeight{
BlockHeight: height,
StateHeight: stateHeight,
Local: height,
Validated: stateHeight,
}, nil
}

View file

@ -324,8 +324,8 @@ var rpcTestCases = map[string][]rpcTestCase{
sh, ok := res.(*result.StateHeight)
require.True(t, ok)
require.Equal(t, e.chain.BlockHeight(), sh.BlockHeight)
require.Equal(t, uint32(0), sh.StateHeight)
require.Equal(t, e.chain.BlockHeight(), sh.Local)
require.Equal(t, uint32(0), sh.Validated)
},
},
},