neoneo-go/test.go
2023-07-20 23:30:26 +03:00

407 lines
13 KiB
Go

package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"math/big"
"strings"
"time"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"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/neorpc"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"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/pkg/errors"
)
const (
rpcEndpoint = "http://localhost:30333"
wsEndpoint = "ws://localhost:30333/ws"
walletPath = "./w.json"
accPass = ""
)
var (
isNeoGoServer bool
transferTxH, _ = util.Uint256DecodeStringLE("f359d99a16a93f4c5d838ae9669bc0b6406f783cd71e917c65af6774e9035fd9")
)
func main() {
// Simple RPC client: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient
c, err := rpcclient.New(context.Background(), rpcEndpoint, rpcclient.Options{
Cert: "",
Key: "",
CACert: "",
DialTimeout: 0,
RequestTimeout: 0,
MaxConnsPerHost: 0,
})
check(err, "create RPC client")
defer c.Close()
err = c.Init()
check(err, "init RPC client")
fmt.Printf("Work with simple RPC client:\n\n")
// Simple RPC methods: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient#hdr-Client
v, err := c.GetVersion()
check(err, "retrieve version")
fmt.Printf("RPC node version: %s\n", v.UserAgent)
isNeoGoServer = strings.Contains(v.UserAgent, config.UserAgentPrefix) // NeoGo node offers several useful extensions.
p, err := v.Protocol.MarshalJSON()
check(err, "marshal protocol config")
fmt.Printf("RPC node protocol config: %s\n", p)
bCount, err := c.GetBlockCount()
check(err, "retrieve block height")
fmt.Printf("Block count: %d\n", bCount)
currentB, err := c.GetBlockByIndex(bCount - 1) // Be careful with block count/block index conversions.
check(err, "retrieve currentB block")
transferTx, err := c.GetRawTransactionVerbose(transferTxH)
check(err, "retrieve transfer tx")
fmt.Printf("Transfer tx script: %s\n", base64.StdEncoding.EncodeToString(transferTx.Script))
transferB, err := c.GetBlockByHash(transferTx.TransactionMetadata.Blockhash)
check(err, "retrieve transferB block")
tr := trigger.Application
applog, err := c.GetApplicationLog(transferTxH, &tr) // Various triggers are supported.
check(err, "retrieve applog")
if len(applog.Executions) != 1 {
panic("unexpected executions number")
}
stack, err := json.MarshalIndent(applog.Executions[0].Stack, "", "\t")
check(err, "marshal stack")
fmt.Printf("Transfer applog invocation state: %s\nTransfer applog stack: %s\n", applog.Executions[0].VMState, stack)
// ...
// Work with wallets: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/wallet
w, err := wallet.NewWalletFromFile(walletPath)
check(err, "open wallet")
acc := w.Accounts[0]
err = acc.Decrypt(accPass, w.Scrypt)
check(err, "decrypt account")
// Some of the extensions offered by NeoGo RPC server: https://github.com/nspcc-dev/neo-go/blob/master/docs/rpc.md#extensions
if isNeoGoServer {
// NEP17/NEP11 transfers paging: https://github.com/nspcc-dev/neo-go/blob/master/docs/rpc.md#limits-and-paging-for-getnep11transfers-and-getnep17transfers
var (
start = uint64(0)
stop = currentB.Timestamp
limit = 10
page = 0
)
nep17T, err := c.GetNEP17Transfers(acc.ScriptHash(), &start, &stop, &limit, &page)
check(err, "retrieve NEP17 transfers")
tBytes, err := json.MarshalIndent(nep17T, "", "\t")
check(err, "marshal NEP17 transfers")
fmt.Printf("NEP17 transfers of %s:\n%s\n", acc.Address, tBytes)
// `getblocksysfee` RPC call: https://github.com/nspcc-dev/neo-go/blob/master/docs/rpc.md#getblocksysfee-call
sysfee, err := c.GetBlockSysFee(nep17T.Received[0].Index)
check(err, "getblocksysfee")
fmt.Printf("Block #%d system fee: %d\n", nep17T.Received[0].Index, sysfee)
// ...
}
// Web-socket client and server: https://github.com/nspcc-dev/neo-go/blob/master/docs/rpc.md#websocket-server
ctx, cancel := context.WithCancel(context.Background())
wsC, err := rpcclient.NewWS(ctx, wsEndpoint, rpcclient.WSOptions{
Options: rpcclient.Options{},
CloseNotificationChannelIfFull: false,
})
// Do not use `defer wsC.Close()`, we'll close the client manually later.
err = wsC.Init()
check(err, "init WS RPC client")
fmt.Printf("\nWork with WS RPC client:\n\n")
// Same simple RPC methods over web-socket:
gasState, err := wsC.GetContractStateByAddressOrName(nativenames.Gas)
check(err, "getcontractstate")
fmt.Printf("GAS contract ID: %d\n", gasState.ID)
// Unwrap helper package: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap
dec, err := unwrap.BigInt(wsC.InvokeFunction(gasState.Hash, "decimals", []smartcontract.Parameter{}, nil))
check(err, "invokefunction")
fmt.Printf("GAS contract decimals: %s\n", dec)
// ...
// Notifications subsystem: https://github.com/nspcc-dev/neo-go/blob/master/docs/notifications.md
bCh := make(chan *block.Block, 5) // Add some buffer to prevent WSC from blocking even regular requests.
ntfCh := make(chan *state.ContainedNotificationEvent, 5)
aerCh := make(chan *state.AppExecResult, 5)
dispatcherToMainCh := make(chan struct{})
go func(ctx context.Context, bCh chan *block.Block, ntfCh chan *state.ContainedNotificationEvent, exCh chan *state.AppExecResult, exitCh chan struct{}) { // TODO: move to a separate function
dispatcherLoop:
for {
select {
case b := <-bCh:
fmt.Printf("Block was received:\n\tIndex: %d\n\tPrimary: %d\n\tTransactions: %d\n",
b.Index, b.PrimaryIndex, len(b.Transactions))
case ntf := <-ntfCh:
params, _ := ntf.Item.MarshalJSON()
fmt.Printf("Notification from execution was received:\n\tContainer: %s\n\tContract: %s\n\tName: %s\n\tParams: %s\n",
ntf.Container.StringLE(), ntf.ScriptHash.StringLE(), ntf.Name, params)
case aer := <-aerCh:
st, _ := json.Marshal(aer.Stack)
fmt.Printf("Application execution result was received:\n\tContainer: %s\n\tTrigger: %s\n\tVM state: %s\n\tStack:%s\n\tFault exception: %s\n", aer.Container.StringLE(), aer.Trigger, aer.VMState, st, aer.FaultException)
case <-ctx.Done():
break dispatcherLoop
}
}
drainLoop:
for {
select {
case <-bCh:
case <-ntfCh:
case <-aerCh:
default:
break drainLoop
}
}
close(bCh)
close(ntfCh)
close(aerCh)
// Send notification to the main routine.
close(dispatcherToMainCh)
}(ctx, bCh, ntfCh, aerCh, dispatcherToMainCh)
primary := 0
till := bCount + 5
fltB := &neorpc.BlockFilter{
Primary: &primary,
Since: nil,
Till: &till,
}
bSubID, err := wsC.ReceiveBlocks(fltB, bCh)
check(err, "subscribe for blocks notifications")
ctr := gasState.Hash
name := "Transfer"
fltNtf := &neorpc.NotificationFilter{
Contract: &ctr,
Name: &name,
}
ntfSubID, err := wsC.ReceiveExecutionNotifications(fltNtf, ntfCh)
check(err, "subscribe for execution notifications")
st := vmstate.Halt.String()
fltEx := &neorpc.ExecutionFilter{
State: &st,
}
aerSubId, err := wsC.ReceiveExecutions(fltEx, aerCh)
check(err, "subscribe for AppExecResults")
// Pretend our app is running and performing some useful work.
t := time.NewTimer(10 * time.Second)
var exitErr error
mainLoop:
for {
select {
case <-t.C:
break mainLoop
case <-ctx.Done():
exitErr = wsC.GetError()
break mainLoop
}
}
// End of the app work.
if exitErr != nil {
fmt.Printf("WS RPC client closing error: %s\n", exitErr)
} else {
// We can use wsC.UnsubscribeAll(), but let us show the ID-based unsubscriptions:
err = wsC.Unsubscribe(bSubID)
check(err, "unsubscribe from blocks")
err = wsC.Unsubscribe(ntfSubID)
check(err, "unsubscribe from execution notifications")
wsC.Unsubscribe(aerSubId)
check(err, "unsubscribe from application execution results")
}
cancel()
<-dispatcherToMainCh // Wait for the dispatcher routine to properly finish.
wsC.Close()
err = wsC.GetError() // Any error if the closing process wasn't initiated by user via wsC.Close().
if err != nil {
fmt.Printf("WS RPC client closing error: %s\n", err)
}
// Invoker functionality: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker
ctx, cancel = context.WithCancel(context.Background())
wsC, err = rpcclient.NewWS(ctx, wsEndpoint, rpcclient.WSOptions{})
check(err, "create WS client")
signers := []transaction.Signer{
{
Account: acc.ScriptHash(),
Scopes: transaction.CalledByEntry,
},
}
inv := invoker.New(wsC, signers)
// Test invocation of method "transfer" of the GAS contract.
var (
from = acc.ScriptHash()
to = random.Uint160()
amount = 5
data = smartcontract.Parameter{Type: smartcontract.AnyType}
)
res, err := inv.Call(gasState.Hash, "transfer", from, to, amount, data)
ok, err := unwrap.Bool(res, err)
check(err, "perform `transfer` testinvoke")
fmt.Printf("`transfer` result: %t\n", ok)
// Historic invocation functionality: https://github.com/nspcc-dev/neo-go/blob/master/docs/rpc.md#invokecontractverifyhistoric-invokefunctionhistoric-and-invokescripthistoric-calls
invH := invoker.NewHistoricAtHeight(transferB.Index-1, wsC, signers)
resH, err := invH.Call(gasState.Hash, "transfer", from, to, amount, data)
ok, err = unwrap.Bool(resH, err)
check(err, "invoke historic `transfer`")
fmt.Printf("`transfer` historic result: %t\n", ok)
// Painful manual events unwrapping...
var tEvent nep17.TransferEvent
arr := res.Notifications[0].Item.Value().([]stackitem.Item)
fromB, _ := arr[0].TryBytes()
tEvent.From, _ = util.Uint160DecodeBytesBE(fromB)
toB, _ := arr[1].TryBytes()
tEvent.To, _ = util.Uint160DecodeBytesBE(toB)
tEvent.Amount, _ = arr[2].TryInteger()
// A set of other invoker methods: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker
// Actor functionality: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient/actor
actSigners := []actor.SignerAccount{
{
Signer: signers[0],
Account: acc, // Provide the decrypted account if possible. It can be signture/multisignature/contract/dummy.
},
}
act, err := actor.New(wsC, actSigners)
check(err, "create actor")
aer, err := act.Wait(act.SendCall(gasState.Hash, "transfer", from, to, amount, data))
check(err, "send `transfer` call via actor")
if aer.VMState != vmstate.Halt {
panic("unexpected `transfer` result")
}
aer, err = act.Wait(act.SendTunedCall(gasState.Hash, "transfer", []transaction.Attribute{}, func(r *result.Invoke, t *transaction.Transaction) error {
err := actor.DefaultCheckerModifier(r, t)
if err != nil {
return err
}
// Perform some additional checks...
if len(r.Stack) != 1 {
return fmt.Errorf("unexpected result stack len: %d", len(r.Stack))
}
ok, err := r.Stack[0].TryBool()
if err != nil {
return fmt.Errorf("unexpected result stack content: %s", r.Stack[0].Type())
}
if !ok {
return errors.New("false `transfer` result")
}
// Change the transaction...
t.NetworkFee = r.GasConsumed + 1
return nil
}, from, to, amount, data))
check(err, "send `transfer` tuned call via actor")
if aer.VMState != vmstate.Halt {
panic("unexpected `transfer` tuned result")
}
// A set of other Actor methods: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient/actor
// Customizable Actor.
// NEP17 package: https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17
nep17Act := nep17.New(act, gasState.Hash)
d, err := nep17Act.Decimals()
check(err, "retrieve GAS decimals via NEP17 actor")
fmt.Printf("GAS decimals: %d\n", d)
aer, err = act.Wait(nep17Act.MultiTransfer([]nep17.TransferParameters{
{
From: from,
To: from,
Amount: big.NewInt(5),
Data: nil,
},
{
From: from,
To: to,
Amount: big.NewInt(5),
Data: nil,
},
}))
check(err, "GAS multitransfer")
applogTr, err := wsC.GetApplicationLog(aer.Container, nil)
check(err, "retrieve applog for multitransfer")
transferEvents, err := nep17.TransferEventsFromApplicationLog(applogTr)
check(err, "retrieve events from multitransfer applog")
transferEventsBytes, _ := json.MarshalIndent(transferEvents, "", "\t")
fmt.Printf("GAS multitransfer events:\n%s\n", transferEventsBytes)
// Native-specific actor packages:
gasAct := gas.New(act)
fromBalance, err := gasAct.BalanceOf(from)
check(err, "retrieve `acc` balance")
fmt.Printf("`acc` balance: %d\n", fromBalance)
policyAct := policy.New(act)
feePerByte, err := policyAct.GetFeePerByte()
check(err, "retrieve FeePerByte")
fmt.Printf("FeePerByte value: %d", feePerByte)
// ...
// Contract deployment example:
// TODO: deploy storage contract with iterator inside, put some values, show how iterator sessions work.
// Iterator sessions:
// Usage of RPC autogenerated wrappers:
// TODO: generate wrappers for the same storage contract.
}
func check(err error, msg string) {
if err != nil {
panic(fmt.Errorf("failed to %s: %w", msg, err))
}
}