Commit f1d72950 authored by duanjinfei's avatar duanjinfei

init grpc project

parent 582d83bf
Pipeline #569 canceled with stages
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ChainGrpcTest.iml" filepath="$PROJECT_DIR$/.idea/ChainGrpcTest.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/MetaTypes" vcs="Git" />
</component>
</project>
\ No newline at end of file
File added
{
"receiveAddr": "0x0Fb196385c8826e3806ebA2cA2cb78B26E08fEEe",
"count": 100,
"type": 1,
"rpcNode": "35.78.87.116:38002",
"amount": 10000000,
"chainId": 100,
"initAccountPrv": "FD5CC6F5E7E2805E920AC5DC83D5AF1106F9C92F0C04F9D5E1FD4261B4B4464A",
"batchTransferContract": "0x9F7777785060CAF169663D46380B756573bEEd2b",
"sleepTime": 0,
"storageAccFileName":"Address"
}
package eg
import (
"ChainGrpcTest/log"
"ChainGrpcTest/types"
"context"
"encoding/hex"
"flag"
basetype "github.com/CaduceusMetaverseProtocol/MetaProtocol/gen/proto/go/base/v1"
metanebula "github.com/CaduceusMetaverseProtocol/MetaProtocol/gen/proto/go/nebula/v1"
metatypes "github.com/CaduceusMetaverseProtocol/MetaTypes/types"
gogotypes "github.com/gogo/protobuf/types"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"math/big"
"strings"
)
func dumpHash(h *metatypes.Hash) string {
if h != nil {
return h.String()
} else {
return "nil"
}
}
func dumpBigInt(n *metatypes.BigInt) string {
if n == nil {
return "nil"
}
return types.ToBigInt(n).String()
}
func dumpBloom(b *basetype.Bloom) string {
if b == nil {
return "nil"
}
return hex.EncodeToString(b.Data)
}
func dumpLogs(txlogs []*basetype.MetaTxLog) string {
if txlogs == nil || len(txlogs) == 0 {
return "empty"
}
return "not empty"
}
func dumpAddress(a *metatypes.Address) string {
if a == nil {
return "nil"
}
return a.String()
}
func PrintReceipt(receipt *basetype.MetaReceipt) {
log.Info("receipt.type=", receipt.Type)
log.Info("receipt.txHash=", dumpHash(receipt.TxHash))
log.Info("receipt.blockNumber=", dumpBigInt(receipt.BlockNumber))
log.Info("receipt.blockHash=", dumpHash(receipt.BlockHash))
log.Info("receipt.Status=", receipt.Status)
log.Info("receipt.TxIndex=", receipt.TxIndex)
log.Info("receipt.ContractAddress=", dumpAddress(receipt.ContractAddress))
log.Info("receipt.Log=", dumpLogs(receipt.Logs))
log.Info("receipt.Root=", hex.EncodeToString(receipt.Root))
log.Info("receipt.CumulativeGasUsed=", receipt.CumulativeGasUsed)
log.Info("receipt.GasUsed=", receipt.GasUsed)
}
func PrintProofTx(tx *basetype.MetaProofTx) {
log.Info("tx.from=", dumpAddress(tx.Base.From))
log.Info("tx.to=", dumpAddress(tx.Base.To))
log.Info("tx.value=", dumpBigInt(tx.Base.Value))
log.Info("tx.hash=", dumpHash(tx.Base.TxHash))
log.Info("tx.type=", tx.Base.TxType)
log.Info("tx.nonce=", tx.Base.Nonce)
log.Info("tx.gasPrice=", dumpBigInt(tx.Base.GasPrice))
log.Info("tx.gasLimit=", tx.Base.Gas)
log.Info("tx.chainId=", dumpBigInt(tx.Base.ChainId))
log.Info("tx.expireBlock=", dumpBigInt(tx.Base.ExpiredBlock))
//log.Info("tx.receiveTime=", tx.Base.ReceiveTime)
log.Info("tx.data=", hex.EncodeToString(tx.Base.Data))
log.Info("tx.V=", dumpBigInt(tx.Base.V))
log.Info("tx.R=", dumpBigInt(tx.Base.R))
log.Info("tx.S=", dumpBigInt(tx.Base.S))
}
func PrintTransaction(transaction *basetype.MetaTransaction) {
prooftx := new(basetype.MetaProofTx)
if gogotypes.Is(transaction.Tx, prooftx) {
gogotypes.UnmarshalAny(transaction.Tx, prooftx)
} else {
log.Errorf("unknown block transaction type:%T\n", transaction.Tx)
}
PrintProofTx(prooftx)
}
func PrintBlock(block *basetype.MetaBlock) {
if block == nil {
log.Info("block is nil")
return
}
if block.Header == nil {
log.Info("block.header is nil")
} else {
h := block.GetHeader()
log.Info("block.BlockHash=", dumpHash(h.BlockHash))
log.Info("block.BlockNumber=", types.ToBigInt(block.Header.BlockNumber))
log.Info("block.TxsRoot=", dumpHash(h.TxsRoot))
log.Info("block.Timestamp=", h.Timestamp)
log.Info("block.ReceiptsRoot=", dumpHash(h.ReceiptsRoot))
log.Info("block.ParentHash=", dumpHash(h.ParentHash))
log.Info("block.Extra=", hex.EncodeToString(h.Extra))
log.Info("block.StateRoot=", dumpHash(h.StateRoot))
log.Info("block.BlockBloom=", dumpBloom(h.BlockBloom))
log.Info("block.GasLimit=", h.GasLimit)
log.Info("block.GasUsed=", h.GasUsed)
log.Info("block.Miner=", h.Miner)
}
if block.Txs == nil {
log.Info("block.txs is nil")
} else {
log.Info("block.txs count=", len(block.Txs))
for i, tx := range block.Txs {
log.Infof("block.txs[%d]:", i)
PrintTransaction(tx)
}
}
}
func featureBlock(client metanebula.NebulaServiceClient, number int64, hash string) {
if len(hash) > 0 {
req := new(metanebula.BlockByHashRequest)
req.BlockHash = (*metatypes.Hash)(types.HexToHash(hash).Bytes())
res, err := client.BlockByHash(context.Background(), req)
if err != nil {
log.Errorf("block by hash failed with err=%s", err)
return
}
PrintBlock(res.Block)
} else {
req := new(metanebula.BlockByNumberRequest)
req.BlockId = types.FromBigInt(big.NewInt(number))
res, err := client.BlockByNumber(context.Background(), req)
if err != nil {
log.Errorf("block by number failed with err=%s", err)
return
}
PrintBlock(res.Block)
}
}
func featureReceipt(client metanebula.NebulaServiceClient, hash string) {
req := new(metanebula.TransactionReceiptRequest)
req.TxHash = (*metatypes.Hash)(types.HexToHash(hash).Bytes())
res, err := client.TransactionReceipt(context.Background(), req)
if err != nil {
log.Errorf("receipt by hash failed with err=%s", err)
return
}
PrintReceipt(res.TxReceipt)
}
func featureTransaction(client metanebula.NebulaServiceClient, hash string) {
req := new(metanebula.TransactionByHashRequest)
req.TxHash = (*metatypes.Hash)(types.HexToHash(hash).Bytes())
res, err := client.TransactionByHash(context.Background(), req)
if err != nil {
log.Errorf("receipt by hash failed with err=%s", err)
return
}
PrintTransaction(res.TxData)
}
func featureTestSendTransaction(client metanebula.NebulaServiceClient) {
prooftx := &basetype.MetaProofTx{
Base: &basetype.MetaTxBase{
TxHash: (*metatypes.Hash)(types.HexToHash("0x7b66e1b27b55febba2407ee31e76245d3a0d4254dafbdaa4dcde36d9efd15226").Bytes()),
TxType: 2,
Value: metatypes.NewBigInt(122222),
},
}
anybase, err := gogotypes.MarshalAny(prooftx)
if err != nil {
log.Errorf("marshal any failed with err:%s", err)
return
}
mtx := &basetype.MetaTransaction{
Tx: anybase,
}
req := new(metanebula.TestSendTransactionRequest)
req.TxData = mtx
res, err := client.TestSendTransaction(context.Background(), req)
if err != nil {
log.Errorf("TestSendTransaction with err=%s", err)
return
}
log.Infof("TestSendTransaction succeed got res.TxHash:%s", res.TxHash)
}
func featureAccountInfo(client metanebula.NebulaServiceClient, address string) {
addr := metatypes.HexToAddress(address)
getbalance := new(metanebula.BalanceRequest)
getbalance.Address = &addr
res, err := client.Balance(context.Background(), getbalance)
if err != nil {
log.Errorf("get balance failed with err=%s", err)
return
}
log.Infof("get account(%s).balance=%s", addr.String(), res.Balance.Text(16))
getnonce := new(metanebula.NonceRequest)
getnonce.Address = &addr
nonceres, err := client.Nonce(context.Background(), getnonce)
if err != nil {
log.Errorf("get nonce failed with err=%s", err)
return
}
log.Infof("get account(%s).nonce=%v", addr.String(), nonceres.Nonce)
}
func featureNonceRepeated(client metanebula.NebulaServiceClient, addressList []string) {
addrs := make([]metatypes.Address, len(addressList))
for i, addr := range addressList {
addrs[i] = metatypes.HexToAddress(addr)
}
getnonces := new(metanebula.RepeatedNonceRequest)
getnonces.Address = addrs
nonceres, err := client.RepeatedNonce(context.Background(), getnonces)
if err != nil {
log.Errorf("get nonce failed with err=%s", err)
return
}
for i := 0; i < len(nonceres.Address); i++ {
log.Infof("get account(%s).nonce=%v", nonceres.Address[i].String(), nonceres.Nonce[i])
}
}
func main() {
node := flag.String("node", "127.0.0.1:38004", "nebula service address")
feature := flag.String("f", "block", "feature name, available value(block, receipt, tx, account)")
number := flag.Int64("n", 10, "block number")
hash := flag.String("hash", "", "request hash value, block/tx/receipt hash")
acc := flag.String("addr", "", "get account info")
flag.Parse()
client, err := grpc.Dial(*node, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("dial nebula failed", "err", err)
return
}
nebula := metanebula.NewNebulaServiceClient(client)
switch *feature {
case "block":
featureBlock(nebula, *number, *hash)
case "receipt":
featureReceipt(nebula, *hash)
case "send":
featureTestSendTransaction(nebula)
case "tx":
featureTransaction(nebula, *hash)
case "account":
featureAccountInfo(nebula, *acc)
case "repeatnonce":
addresslist := strings.Split(*acc, ",")
featureNonceRepeated(nebula, addresslist)
}
return
}
module ChainGrpcTest
go 1.19
require (
github.com/CaduceusMetaverseProtocol/MetaTypes v1.0.0
github.com/ethereum/go-ethereum v1.11.2
github.com/gogo/protobuf v1.3.2
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/xuri/excelize/v2 v2.7.0
)
require (
github.com/deckarep/golang-set/v2 v2.2.0 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.5.0 // indirect
)
require (
github.com/CaduceusMetaverseProtocol/MetaProtocol v0.0.1
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
)
replace (
github.com/CaduceusMetaverseProtocol/MetaCryptor => ../MetaCryptor-main
github.com/CaduceusMetaverseProtocol/MetaProtocol => ../MetaProtocol-main
github.com/CaduceusMetaverseProtocol/MetaTypes => ../MetaTypes-main
)
This diff is collapsed.
package logging
import (
"sync"
"github.com/sirupsen/logrus"
)
// Logger is the interface to our internal logger.
type Logger interface {
Debug(msg string, kvpairs ...interface{})
Info(msg string, kvpairs ...interface{})
Error(msg string, kvpairs ...interface{})
SetField(key string, val interface{})
PushFields()
PopFields()
}
// LogrusLogger is a thread-safe logger whose properties persist and can be modified.
type LogrusLogger struct {
mtx sync.Mutex
logger *logrus.Entry
ctx string
fields map[string]interface{}
pushedFieldSets []map[string]interface{}
}
// NoopLogger implements Logger, but does nothing.
type NoopLogger struct{}
// LogrusLogger implements Logger
var _ Logger = (*LogrusLogger)(nil)
var _ Logger = (*NoopLogger)(nil)
//
// LogrusLogger
//
// NewLogrusLogger will instantiate a logger with the given context.
func NewLogrusLogger(ctx string, kvpairs ...interface{}) Logger {
var logger *logrus.Entry
if len(ctx) > 0 {
logger = logrus.WithField("ctx", ctx)
} else {
logger = logrus.NewEntry(logrus.New())
}
return &LogrusLogger{
logger: logger,
ctx: ctx,
fields: serializeKVPairs(kvpairs),
pushedFieldSets: []map[string]interface{}{},
}
}
func (l *LogrusLogger) withFields() *logrus.Entry {
if len(l.fields) > 0 {
return l.logger.WithFields(l.fields)
}
return l.logger
}
func serializeKVPairs(kvpairs ...interface{}) map[string]interface{} {
res := make(map[string]interface{})
if (len(kvpairs) % 2) == 0 {
for i := 0; i < len(kvpairs); i += 2 {
res[kvpairs[i].(string)] = kvpairs[i+1]
}
}
return res
}
func (l *LogrusLogger) withKVPairs(kvpairs ...interface{}) *logrus.Entry {
fields := serializeKVPairs(kvpairs...)
if len(fields) > 0 {
return l.withFields().WithFields(fields)
}
return l.withFields()
}
func (l *LogrusLogger) Debug(msg string, kvpairs ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.withKVPairs(kvpairs...).Debugln(msg)
}
func (l *LogrusLogger) Info(msg string, kvpairs ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.withKVPairs(kvpairs...).Infoln(msg)
}
func (l *LogrusLogger) Error(msg string, kvpairs ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.withKVPairs(kvpairs...).Errorln(msg)
}
func (l *LogrusLogger) SetField(key string, val interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.fields[key] = val
}
func (l *LogrusLogger) PushFields() {
l.mtx.Lock()
defer l.mtx.Unlock()
l.pushedFieldSets = append(l.pushedFieldSets, l.fields)
}
func (l *LogrusLogger) PopFields() {
l.mtx.Lock()
defer l.mtx.Unlock()
pfsLen := len(l.pushedFieldSets)
if pfsLen > 0 {
l.fields = l.pushedFieldSets[pfsLen-1]
l.pushedFieldSets = l.pushedFieldSets[:pfsLen-1]
}
}
//
// NoopLogger
//
// NewNoopLogger will instantiate a logger that does nothing when called.
func NewNoopLogger() Logger {
return &NoopLogger{}
}
func (l *NoopLogger) Debug(msg string, kvpairs ...interface{}) {}
func (l *NoopLogger) Info(msg string, kvpairs ...interface{}) {}
func (l *NoopLogger) Error(msg string, kvpairs ...interface{}) {}
func (l *NoopLogger) SetField(key string, val interface{}) {}
func (l *NoopLogger) PushFields() {}
func (l *NoopLogger) PopFields() {}
package logging
import (
"reflect"
"testing"
)
func TestKVPairSerialization(t *testing.T) {
testCases := []struct {
kvpairs []interface{}
expected map[string]interface{}
}{
{
[]interface{}{"a", 1, "b", "v"},
map[string]interface{}{
"a": 1,
"b": "v",
},
},
{
[]interface{}{"a"},
map[string]interface{}{},
},
{
[]interface{}{"a", 1, "b"},
map[string]interface{}{},
},
}
for i, tc := range testCases {
actual := serializeKVPairs(tc.kvpairs...)
if !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("Test case %d: Expected result %v, but got %v", i, tc.expected, actual)
}
}
}
package log
import (
"context"
"github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"os"
"path"
"time"
)
var (
mlog = logrus.New()
)
type LogConfig struct {
Save uint `json:"save"`
Path string `json:"path"`
Level string `json:"level"`
}
func InitLog(logConfig LogConfig) {
mlog.Out = os.Stdout
var loglevel logrus.Level
err := loglevel.UnmarshalText([]byte(logConfig.Level))
if err != nil {
mlog.Panicf("set log level failed: %v", err)
}
mlog.SetLevel(loglevel)
mlog.Formatter = &logrus.TextFormatter{FullTimestamp: true, TimestampFormat: "2006-01-2 15:04:05.000"}
localFilesystemLogger(mlog, logConfig.Path, logConfig.Save)
}
func logWriter(logPath string, level string, save uint) *rotatelogs.RotateLogs {
logFullPath := path.Join(logPath, level)
logwriter, err := rotatelogs.New(
logFullPath+".%Y%m%d",
rotatelogs.WithLinkName(logFullPath),
rotatelogs.WithRotationCount(save),
rotatelogs.WithRotationTime(24*time.Hour),
)
if err != nil {
panic(err)
}
return logwriter
}
func localFilesystemLogger(log *logrus.Logger, logPath string, save uint) {
lfHook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: logWriter(logPath, "debug", save), // 为不同级别设置不同的输出目的
logrus.InfoLevel: logWriter(logPath, "info", save),
logrus.WarnLevel: logWriter(logPath, "warn", save),
logrus.ErrorLevel: logWriter(logPath, "error", save),
logrus.FatalLevel: logWriter(logPath, "fatal", save),
logrus.PanicLevel: logWriter(logPath, "panic", save),
}, &logrus.TextFormatter{FullTimestamp: true, TimestampFormat: "2006-01-2 15:04:05.000"})
log.AddHook(lfHook)
}
// WithField allocates a new entry and adds a field to it.
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
// this new returned entry.
// If you want multiple fields, use `WithFields`.
func WithField(key string, value interface{}) *logrus.Entry {
return mlog.WithField(key, value)
}
// Adds a struct of fields to the log entry. All it does is call `WithField` for
// each `Field`.
func WithFields(fields logrus.Fields) *logrus.Entry {
return mlog.WithFields(fields)
}
// Add an error as single field to the log entry. All it does is call
// `WithError` for the given `error`.
func WithError(err error) *logrus.Entry {
return mlog.WithError(err)
}
// Add a context to the log entry.
func WithContext(ctx context.Context) *logrus.Entry {
return mlog.WithContext(ctx)
}
// Overrides the time of the log entry.
func WithTime(t time.Time) *logrus.Entry {
return mlog.WithTime(t)
}
func Logf(level logrus.Level, format string, args ...interface{}) {
mlog.Logf(level, format, args...)
}
func Tracef(format string, args ...interface{}) {
mlog.Tracef(format, args...)
}
func Debugf(format string, args ...interface{}) {
mlog.Debugf(format, args...)
}
func Infof(format string, args ...interface{}) {
mlog.Infof(format, args...)
}
func Printf(format string, args ...interface{}) {
mlog.Printf(format, args...)
}
func Warnf(format string, args ...interface{}) {
mlog.Warnf(format, args...)
}
func Warningf(format string, args ...interface{}) {
mlog.Warningf(format, args...)
}
func Errorf(format string, args ...interface{}) {
mlog.Errorf(format, args)
}
func Fatalf(format string, args ...interface{}) {
mlog.Fatalf(format, args...)
}
func Panicf(format string, args ...interface{}) {
mlog.Panicf(format, args...)
}
func Log(level logrus.Level, args ...interface{}) {
mlog.Log(level, args...)
}
func LogFn(level logrus.Level, fn logrus.LogFunction) {
mlog.LogFn(level, fn)
}
func Trace(args ...interface{}) {
mlog.Trace(args...)
}
func Debug(args ...interface{}) {
mlog.Debug(args...)
}
func Info(args ...interface{}) {
mlog.Info(args...)
}
func Print(args ...interface{}) {
mlog.Print(args...)
}
func Warn(args ...interface{}) {
mlog.Warn(args...)
}
func Warning(args ...interface{}) {
mlog.Warning(args...)
}
func Error(args ...interface{}) {
mlog.Error(args...)
}
func Fatal(args ...interface{}) {
mlog.Fatal(args...)
}
func Panic(args ...interface{}) {
mlog.Panic(args...)
}
func TraceFn(fn logrus.LogFunction) {
mlog.TraceFn(fn)
}
func DebugFn(fn logrus.LogFunction) {
mlog.DebugFn(fn)
}
func InfoFn(fn logrus.LogFunction) {
mlog.InfoFn(fn)
}
func PrintFn(fn logrus.LogFunction) {
mlog.PrintFn(fn)
}
func WarnFn(fn logrus.LogFunction) {
mlog.WarnFn(fn)
}
func WarningFn(fn logrus.LogFunction) {
mlog.WarningFn(fn)
}
func ErrorFn(fn logrus.LogFunction) {
mlog.ErrorFn(fn)
}
func FatalFn(fn logrus.LogFunction) {
mlog.FatalFn(fn)
}
func PanicFn(fn logrus.LogFunction) {
mlog.PanicFn(fn)
}
func Logln(level logrus.Level, args ...interface{}) {
mlog.Logln(level, args...)
}
func Traceln(args ...interface{}) {
mlog.Traceln(args...)
}
func Debugln(args ...interface{}) {
mlog.Debugln(args...)
}
func Infoln(args ...interface{}) {
mlog.Infoln(args...)
}
func Println(args ...interface{}) {
mlog.Println(args...)
}
func Warnln(args ...interface{}) {
mlog.Warnln(args...)
}
func Warningln(args ...interface{}) {
mlog.Warningln(args...)
}
func Errorln(args ...interface{}) {
mlog.Errorln(args...)
}
func Fatalln(args ...interface{}) {
mlog.Fatalln(args...)
}
func Panicln(args ...interface{}) {
mlog.Panicln(args...)
}
error.20230308
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
info.20230308
\ No newline at end of file
time="2023-03-7 18:31:02.351" level=info msg="<nil>"
time="2023-03-7 18:32:56.905" level=info msg="<nil>"
time="2023-03-7 18:35:06.217" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:35:35.040" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:36:50.433" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:36:50.434" level=info msg="Send tran count: 0"
time="2023-03-7 18:36:50.434" level=info msg="Since time: 0 ms"
time="2023-03-7 18:37:42.637" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:41:11.551" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:41:36.827" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:43:28.094" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-7 18:45:21.726" level=info msg="Program start initAccCount:100 tranCount"
time="2023-03-8 10:10:44.976" level=info msg="Program start initAccCount:100 tranCount"
package main
import (
"ChainGrpcTest/log"
"ChainGrpcTest/tool"
"github.com/spf13/cobra"
"os"
"runtime/pprof"
)
var (
initAcc, initCmp, batchSign, batchRecover, batchRecoverTx, batchVerify bool
txCount, goRoutineCount, cmpAmount int
cpuProfile string
cfg *tool.Config
)
func init() {
initCmd.PersistentFlags().IntVar(&cmpAmount, "cmp", 1, "Transfer amount,default: 1CMP")
initCmd.PersistentFlags().BoolVar(&initAcc, "initAcc", false, "Start after initializing the account")
initCmd.PersistentFlags().BoolVar(&initCmp, "initCmp", false, "Start after initializing the account cmp balance")
startCmd.PersistentFlags().StringVar(&cpuProfile, "cpuProfile", "cpuProfile.prof", "Statistics cpu profile")
startCmd.PersistentFlags().BoolVar(&batchSign, "batchSign", false, "test grpc interface -> batchSign")
startCmd.PersistentFlags().BoolVar(&batchRecover, "batchRecover", false, "test grpc interface -> batchRecover")
startCmd.PersistentFlags().BoolVar(&batchRecoverTx, "batchRecoverTx", false, "test grpc interface -> batchRecoverTx")
startCmd.PersistentFlags().BoolVar(&batchVerify, "batchVerify", false, "test grpc interface -> batchVerify")
startCmd.PersistentFlags().IntVar(&txCount, "txCount", 1000, "send tran count")
startCmd.PersistentFlags().IntVar(&goRoutineCount, "goRoutineCount", 100, "send tran engagement count")
startCmd.AddCommand(initCmd)
}
func main() {
log.InitLog(log.LogConfig{Path: "logs", Level: "debug", Save: 3})
// 执行初始化账户操作
Execute()
}
func Execute() {
if err := startCmd.Execute(); err != nil {
log.Errorf("Program execute error: %s", err)
os.Exit(1)
}
}
var initCmd = &cobra.Command{
Use: "init",
Short: "Init your account command and transfer coin",
Run: func(cmd *cobra.Command, args []string) {
if initAcc {
tool.InitAccount(cfg, cfg.Count)
}
if initCmp {
tool.TransferAccount(cfg, int64(cfg.Count))
}
},
}
var startCmd = &cobra.Command{
Use: "start",
Short: "Start the stress test project",
Run: func(cmd *cobra.Command, args []string) {
f, err := os.Create(cpuProfile)
if err != nil {
log.Fatal(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
log.Error("Start cpu profile err:", err)
return
}
startTest()
defer pprof.StopCPUProfile()
},
}
File added
package main
import (
"ChainGrpcTest/log"
"ChainGrpcTest/tool"
"ChainGrpcTest/transaction"
)
var (
SendTxAccountArr [][]string
)
func init() {
var err error
cfg, err = tool.ParseConfig("./config/app.json")
if err != nil {
log.Errorf("Parse config error:%s", err)
return
}
cfg.StorageAccFileName += ".xlsx"
cfg.GoRoutineCount = goRoutineCount
cfg.SignCount = txCount / 100
SendTxAccountArr = tool.ReadExcel(cfg.Count, cfg.StorageAccFileName)
}
func startTest() {
log.Infof("Program start initAccCount:%d tranCount", cfg.Count)
arr := transaction.SignedTxArr(SendTxAccountArr, cfg)
if batchSign {
if err := transaction.BatchSignHandler(arr, cfg); err != nil {
log.Errorf("Bath Send Tran error: %s", err)
return
}
}
if batchRecover {
if err := transaction.BatchRecoverHandler(arr, cfg); err != nil {
log.Errorf("Bath Send Tran error: %s", err)
return
}
}
if batchRecoverTx {
if err := transaction.BatchRecoverTxHandler(arr, cfg); err != nil {
log.Errorf("Bath Send Tran error: %s", err)
return
}
}
if batchVerify {
if err := transaction.BatchVerifyHandler(arr, cfg); err != nil {
log.Errorf("Bath Send Tran error: %s", err)
return
}
}
}
package tool
import (
"ChainGrpcTest/log"
"context"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/xuri/excelize/v2"
"math/big"
"sync/atomic"
"time"
)
func InitAccount(cfg *Config, createAccCount int) {
if createAccCount <= 0 {
return
}
excelFile := excelize.NewFile()
index, _ := excelFile.NewSheet("Sheet1")
// 设置工作簿的默认工作表
excelFile.SetActiveSheet(index)
for i := 1; i <= createAccCount; i++ {
toPrivateKey, err := crypto.GenerateKey()
if err != nil {
log.Error("Gen wallet Err:%r", err)
break
}
toPrivateKeyEncode := hexutil.Encode(crypto.FromECDSA(toPrivateKey))[2:]
toAddress := crypto.PubkeyToAddress(toPrivateKey.PublicKey)
// 设置单元格的值
titleSlice := []interface{}{toAddress, toPrivateKeyEncode}
axis := fmt.Sprintf("A%d", i)
excelFile.SetSheetRow("Sheet1", axis, &titleSlice)
log.Infof("Gen count: %d", i)
}
//根据指定路径保存文件
if err := excelFile.SaveAs(cfg.StorageAccFileName); err != nil {
log.Error("Save address file error: %s", err)
} else {
log.Info("Save address file successful")
}
}
func BathSendTran(cfg *Config, signedTxArr []*types.Transaction, client *ethclient.Client) {
tranTxCh := make(chan *types.Transaction, 1000000)
var bathHandleSendCount int64
startTime := time.Now()
log.Info("start time:", startTime.String())
for count := 0; count < cfg.GoRoutineCount; count++ {
go func() {
for {
select {
case signedTx := <-tranTxCh:
timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*3)
defer cancelFunc()
err := client.SendTransaction(timeout, signedTx)
log.Info("Send tx:", signedTx.Hash())
atomic.AddInt64(&bathHandleSendCount, 1)
if err != nil {
log.Error("sendTranErr:", err)
continue
}
}
}
}()
}
for _, sign := range signedTxArr {
tranTxCh <- sign
}
for {
if bathHandleSendCount == int64(len(signedTxArr)) {
log.Infof("Send tran count: %d", bathHandleSendCount)
log.Info("end time:", time.Now().String())
log.Info("since time:", time.Since(startTime).Milliseconds())
break
}
}
}
func signTxArr(cfg *Config, amount int64) ([]*types.Transaction, *ethclient.Client) {
accounts := ReadExcel(cfg.Count, cfg.StorageAccFileName)
log.Info("Read file successful")
client, err := ethclient.Dial(cfg.RpcNode)
if err != nil {
log.Error("Connect chain error", err)
}
log.Info("Connect client successful")
defer func() {
client.Close()
}()
signerKey, err := crypto.HexToECDSA(cfg.InitAccountPrv)
fromAddress := crypto.PubkeyToAddress(signerKey.PublicKey)
nonce, err := client.NonceAt(context.Background(), fromAddress, nil)
chNonce := big.NewInt(int64(nonce))
signedTxArr := make([]*types.Transaction, 0)
for _, rows := range accounts {
decode, err := hexutil.Decode(rows[0])
if err != nil {
continue
}
address := common.BytesToAddress(decode)
txData := types.LegacyTx{
Nonce: chNonce.Uint64(),
To: &address,
Value: math.BigPow(amount*10, 18),
Gas: 100000,
GasPrice: big.NewInt(1000000001),
Data: nil,
}
newtx := types.NewTx(&txData)
signedTx, err := types.SignTx(newtx, types.NewEIP155Signer(big.NewInt(100)), signerKey)
if err != nil {
log.Error("Signed Error", err)
}
signedTxArr = append(signedTxArr, signedTx)
chNonce = chNonce.Add(chNonce, big.NewInt(1))
}
return signedTxArr, client
}
// TransferAccount : Perform local currency transfer transactions directly through account signatures
func TransferAccount(cfg *Config, amount int64) {
signedTxArr, client := signTxArr(cfg, amount)
BathSendTran(cfg, signedTxArr, client)
}
func ReadExcelOfStartEnd(start int, end int, fileName string) [][]string {
file, err := excelize.OpenFile(fileName)
if err != nil {
log.Error("Open excel err", err.Error())
return nil
}
rows, err := file.GetRows("Sheet1")
if err != nil {
log.Error("Read excel failed: " + err.Error())
return nil
}
res := make([][]string, 0)
for i := start; i <= end; i++ {
res = append(res, rows[i])
}
return res
}
func ReadExcel(count int, fileName string) [][]string {
file, err := excelize.OpenFile(fileName)
if err != nil {
log.Error("Open excel err", err.Error())
return nil
}
rows, err := file.GetRows("Sheet1")
if err != nil {
log.Error("Read excel failed: " + err.Error())
return nil
}
res := make([][]string, 0)
for _, row := range rows {
if len(res) == count {
break
}
res = append(res, row)
}
return res
}
package tool
import (
"ChainGrpcTest/log"
"bufio"
"encoding/json"
"os"
)
type Config struct {
ReceiveAddr string `json:"receiveAddr"`
Count int `json:"count"`
SignCount int `json:"signCount"`
GoRoutineCount int `json:"goRoutineCount"`
Type int `json:"type"`
RpcNode string `json:"rpcNode"`
Amount int64 `json:"amount"`
ChainId int64 `json:"chainId"`
InitAccountPrv string `json:"initAccountPrv"`
BatchTransferContract string `json:"batchTransferContract"`
SleepTime int `json:"sleepTime"`
StorageAccFileName string `json:"storageAccFileName"`
}
var _cfg *Config = nil
func ParseConfig(path string) (*Config, error) {
file, err := os.Open(path)
if err != nil {
panic(err)
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
log.Error("read file close failed:", err)
}
}(file)
reader := bufio.NewReader(file)
decoder := json.NewDecoder(reader)
if err := decoder.Decode(&_cfg); err != nil {
return nil, err
}
return _cfg, nil
}
package transaction
import (
"ChainGrpcTest/log"
"ChainGrpcTest/tool"
"context"
crypterv1 "github.com/CaduceusMetaverseProtocol/MetaProtocol/gen/proto/go/crypter/v1"
"github.com/ethereum/go-ethereum/core/types"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sync/atomic"
"time"
)
var (
batchSignRequest chan *crypterv1.BatchSignRequest
clientMap map[int]crypterv1.CrypterServiceClient
bathHandleSendCount, allSignedTxCount, totalSendTime int64
)
func init() {
batchSignRequest = make(chan *crypterv1.BatchSignRequest, 1000000)
clientMap = make(map[int]crypterv1.CrypterServiceClient)
}
// batchSign 发送的签名交易
func batchSign(client crypterv1.CrypterServiceClient, sleepTime int) {
var beforeSendTxTime time.Time
for {
select {
case batchSign := <-batchSignRequest:
if sleepTime != 0 {
time.Sleep(time.Millisecond * time.Duration(sleepTime))
}
sendTranStartTime := time.Now()
if beforeSendTxTime.UnixMilli() > 0 && time.Since(beforeSendTxTime).Milliseconds() < 1 {
time.Sleep(time.Millisecond * time.Duration(1))
}
sign, err := client.BatchSign(context.Background(), batchSign, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("SendTranErr: %s", err)
sign, err = client.BatchSign(context.Background(), batchSign, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("Send tran twice err: %s", err)
continue
}
}
beforeSendTxTime = time.Now()
atomic.AddInt64(&bathHandleSendCount, 1)
sinceTime := time.Since(sendTranStartTime).Milliseconds()
atomic.AddInt64(&totalSendTime, sinceTime)
log.Infof("Send transaction time: %d ms, sign: %s ", sinceTime, sign.String())
}
}
}
// BatchSignHandler 处理批量发送的签名交易
func BatchSignHandler(tranArr []*types.Transaction, cfg *tool.Config) error {
client, err := grpc.Dial(cfg.RpcNode, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("dial nebula failed", "err", err)
}
for i := 0; i < cfg.GoRoutineCount; i++ {
go batchSign(crypterv1.NewCrypterServiceClient(client), cfg.SleepTime)
}
allSignedTxCount = int64(len(tranArr))
startTime := time.Now()
for i := 0; i < int(allSignedTxCount); i++ {
req := crypterv1.BatchSignRequest{}
batchSignRequest <- &req
}
for {
if bathHandleSendCount == allSignedTxCount {
log.Infof("Send tran count: %d", bathHandleSendCount)
log.Infof("Since time: %d ms", time.Since(startTime).Milliseconds())
break
}
}
return nil
}
// BatchRecover 发送的签名交易
func batchRecover(client crypterv1.CrypterServiceClient, sleepTime int) {
var beforeSendTxTime time.Time
for {
select {
case batchRecover := <-batchSignRequest:
if sleepTime != 0 {
time.Sleep(time.Millisecond * time.Duration(sleepTime))
}
sendTranStartTime := time.Now()
if beforeSendTxTime.UnixMilli() > 0 && time.Since(beforeSendTxTime).Milliseconds() < 1 {
time.Sleep(time.Millisecond * time.Duration(1))
}
sign, err := client.BatchSign(context.Background(), batchRecover, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("SendTranErr: %s", err)
sign, err = client.BatchSign(context.Background(), batchRecover, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("Send tran twice err: %s", err)
continue
}
}
beforeSendTxTime = time.Now()
atomic.AddInt64(&bathHandleSendCount, 1)
sinceTime := time.Since(sendTranStartTime).Milliseconds()
atomic.AddInt64(&totalSendTime, sinceTime)
log.Infof("Send transaction time: %d ms, sign: %s ", sinceTime, sign.String())
}
}
}
// BatchRecoverHandler 处理批量发送的签名交易
func BatchRecoverHandler(tranArr []*types.Transaction, cfg *tool.Config) error {
grpcClient, err := grpc.Dial(cfg.RpcNode, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("dial nebula failed", "err", err)
}
serviceClient := crypterv1.NewCrypterServiceClient(grpcClient)
go batchRecover(serviceClient, cfg.SleepTime)
allSignedTxCount = int64(len(tranArr))
startTime := time.Now()
for i := 0; i < int(allSignedTxCount); i++ {
req := crypterv1.BatchSignRequest{}
batchSignRequest <- &req
}
for {
if bathHandleSendCount == allSignedTxCount {
log.Infof("Send tran count: %d", bathHandleSendCount)
log.Infof("Since time: %d ms", time.Since(startTime).Milliseconds())
break
}
}
return nil
}
// BatchVerify 发送的签名交易
func batchVerify(client crypterv1.CrypterServiceClient, sleepTime int) {
var beforeSendTxTime time.Time
for {
select {
case batchVerify := <-batchSignRequest:
if sleepTime != 0 {
time.Sleep(time.Millisecond * time.Duration(sleepTime))
}
sendTranStartTime := time.Now()
if beforeSendTxTime.UnixMilli() > 0 && time.Since(beforeSendTxTime).Milliseconds() < 1 {
time.Sleep(time.Millisecond * time.Duration(1))
}
sign, err := client.BatchSign(context.Background(), batchVerify, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("SendTranErr: %s", err)
sign, err = client.BatchSign(context.Background(), batchVerify, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("Send tran twice err: %s", err)
continue
}
}
beforeSendTxTime = time.Now()
atomic.AddInt64(&bathHandleSendCount, 1)
sinceTime := time.Since(sendTranStartTime).Milliseconds()
atomic.AddInt64(&totalSendTime, sinceTime)
log.Infof("Send transaction time: %d ms, sign: %s ", sinceTime, sign.String())
}
}
}
// BatchVerifyHandler 处理批量发送的签名交易
func BatchVerifyHandler(tranArr []*types.Transaction, cfg *tool.Config) error {
client, err := grpc.Dial(cfg.RpcNode, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("dial nebula failed", "err", err)
}
go batchVerify(crypterv1.NewCrypterServiceClient(client), cfg.SleepTime)
allSignedTxCount = int64(len(tranArr))
startTime := time.Now()
for i := 0; i < int(allSignedTxCount); i++ {
req := crypterv1.BatchSignRequest{}
batchSignRequest <- &req
}
for {
if bathHandleSendCount == allSignedTxCount {
log.Infof("Send tran count: %d", bathHandleSendCount)
log.Infof("Since time: %d ms", time.Since(startTime).Milliseconds())
break
}
}
return nil
}
// batchRecoverTx 发送的签名交易
func batchRecoverTx(client crypterv1.CrypterServiceClient, sleepTime int) {
var beforeSendTxTime time.Time
for {
select {
case batchRecoverTx := <-batchSignRequest:
if sleepTime != 0 {
time.Sleep(time.Millisecond * time.Duration(sleepTime))
}
sendTranStartTime := time.Now()
if beforeSendTxTime.UnixMilli() > 0 && time.Since(beforeSendTxTime).Milliseconds() < 1 {
time.Sleep(time.Millisecond * time.Duration(1))
}
sign, err := client.BatchSign(context.Background(), batchRecoverTx, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("SendTranErr: %s", err)
sign, err = client.BatchSign(context.Background(), batchRecoverTx, grpc.CallContentSubtype(""))
if err != nil {
log.Errorf("Send tran twice err: %s", err)
continue
}
}
beforeSendTxTime = time.Now()
atomic.AddInt64(&bathHandleSendCount, 1)
sinceTime := time.Since(sendTranStartTime).Milliseconds()
atomic.AddInt64(&totalSendTime, sinceTime)
log.Infof("Send transaction time: %d ms, sign: %s ", sinceTime, sign.String())
}
}
}
// BatchRecoverTxHandler 处理批量发送的签名交易
func BatchRecoverTxHandler(tranArr []*types.Transaction, cfg *tool.Config) error {
client, err := grpc.Dial(cfg.RpcNode, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error("dial nebula failed", "err", err)
}
go batchRecoverTx(crypterv1.NewCrypterServiceClient(client), cfg.SleepTime)
allSignedTxCount = int64(len(tranArr))
startTime := time.Now()
for i := 0; i < int(allSignedTxCount); i++ {
req := crypterv1.BatchSignRequest{}
batchSignRequest <- &req
}
for {
if bathHandleSendCount == allSignedTxCount {
log.Infof("Send tran count: %d", bathHandleSendCount)
log.Infof("Since time: %d ms", time.Since(startTime).Milliseconds())
break
}
}
return nil
}
package transaction
import (
"ChainGrpcTest/log"
"ChainGrpcTest/tool"
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"math/big"
"time"
)
type TranConfig struct {
Amount int64
GoRoutineCount int
ChainId int64
PrivateKey, ReceivedAddr string
Nonce *big.Int
}
type Transactor struct {
config TranConfig
signerKey *ecdsa.PrivateKey
sender common.Address
receivedAddr common.Address
}
func newTransactor(cfg TranConfig) (*Transactor, error) {
signerKey, err := crypto.HexToECDSA(cfg.PrivateKey)
if err != nil {
log.Error("Error crypto HexToECDSA")
return nil, err
}
// through privateKey get account address
publicKey := signerKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
log.Error("Error casting public key to ECDSA")
}
address := crypto.PubkeyToAddress(*publicKeyECDSA)
res := Transactor{
signerKey: signerKey,
receivedAddr: common.HexToAddress(cfg.ReceivedAddr),
config: cfg,
sender: address,
}
return &res, nil
}
// SignedTxArr 获取全部签名数据
func SignedTxArr(sendTxAccountArr [][]string, cfg *tool.Config) []*types.Transaction {
tranArr := make([]*types.Transaction, 0)
var signedTx *types.Transaction
for _, rows := range sendTxAccountArr {
privateKey := rows[1]
nonce := big.NewInt(1)
for signCount := 0; signCount < cfg.SignCount; signCount++ {
tranCfg := TranConfig{
Amount: cfg.Amount,
ChainId: cfg.ChainId,
PrivateKey: privateKey,
ReceivedAddr: cfg.ReceiveAddr,
GoRoutineCount: cfg.GoRoutineCount,
Nonce: nonce,
}
t, err := newTransactor(tranCfg)
signedTx, err = t.signedTx()
nonce = big.NewInt(1).Add(nonce, big.NewInt(1))
if err != nil || signedTx == nil {
log.Errorf("signed tx error %s ", err)
continue
}
tranArr = append(tranArr, signedTx)
}
}
return tranArr
}
// signedTx 签名本币转账交易
func (t *Transactor) signedTx() (*types.Transaction, error) {
txData := types.LegacyTx{
Nonce: t.config.Nonce.Uint64(),
To: &t.receivedAddr,
Value: big.NewInt(t.config.Amount),
Gas: 300000,
GasPrice: big.NewInt(1000000001),
Data: nil,
}
newtx := types.NewTx(&txData)
signedTx, err := types.SignTx(newtx, types.NewEIP155Signer(big.NewInt(t.config.ChainId)), t.signerKey)
if err != nil {
log.Errorf("Send tx nonce: %d , From: %s , to: %s , error: %s", t.config.Nonce, crypto.PubkeyToAddress(t.signerKey.PublicKey), t.receivedAddr, err.Error())
time.Sleep(time.Second)
return nil, err
}
return signedTx, nil
}
package types
import (
metatypes "github.com/CaduceusMetaverseProtocol/MetaTypes/types"
"math/big"
)
func ToBigInt(n *metatypes.BigInt) *big.Int {
return big.NewInt(n.Int64())
}
func HexToHash(hash string) metatypes.Hash {
return metatypes.HexToHash(hash)
}
func FromBigInt(num *big.Int) *metatypes.BigInt {
return metatypes.NewBigInt(num.Int64())
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment