mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-25 13:47:19 +00:00
*: add wokshop file
This commit is contained in:
parent
862c2e4ed3
commit
5ca0d508b6
1 changed files with 407 additions and 0 deletions
407
test.go
Normal file
407
test.go
Normal file
|
@ -0,0 +1,407 @@
|
|||
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))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue