Commit 7d9726f6 authored by 张振华's avatar 张振华

Merge branch 'master' into guess

parents d6f96f30 9ae6ffa1
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
package commands
import (
"encoding/json"
"fmt"
"os"
"github.com/33cn/chain33/rpc/jsonclient"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
"github.com/spf13/cobra"
)
// EchoCmd 本执行器的命令行初始化总入口
func EchoCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "echo",
Short: "echo commandline interface",
Args: cobra.MinimumNArgs(1),
}
cmd.AddCommand(
QueryCmd(), // 查询消息记录
// 如果有其它命令,在这里加入
)
return cmd
}
// QueryCmd query 命令
func QueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Short: "query message history",
Run: queryMesage,
}
addPingPangFlags(cmd)
return cmd
}
func addPingPangFlags(cmd *cobra.Command) {
// type参数,指定查询的消息类型,为uint32类型,默认值为1,通过-t参数指定
cmd.Flags().Uint32P("type", "t", 1, "message type, 1:ping 2:pang")
//cmd.MarkFlagRequired("type")
// message参数,执行消息内容,为string类型,默认值为空,通过-m参数制定
cmd.Flags().StringP("message", "m", "", "message content")
cmd.MarkFlagRequired("message")
}
func queryMesage(cmd *cobra.Command, args []string) {
// 这个是命令行的默认参数,可以制定调用哪一个服务地址
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
echoType, _ := cmd.Flags().GetUint32("type")
msg, _ := cmd.Flags().GetString("message")
// 创建RPC客户端,调用我们实现的QueryPing服务接口
client, err := jsonclient.NewJSONClient(rpcLaddr)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
// 初始化查询参数结构
var action = &echotypes.Query{Msg: msg}
if echoType != 1 {
fmt.Fprintln(os.Stderr, "not support")
return
}
var result echotypes.QueryResult
err = client.Call("echo.QueryPing", action, &result)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
data, err := json.MarshalIndent(result, "", " ")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(string(data))
}
package doc
// An easy to learn executor example references:
// 1. https://chain.33.cn/document/79#4%20%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91
// 2. https://chain.33.cn/document/82
package executor
import "github.com/33cn/chain33/types"
// CheckTx 本执行器不做任何校验
func (h *Echo) CheckTx(tx *types.Transaction, index int) error {
return nil
}
package executor
import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
var (
// KeyPrefixPing ping 前缀
KeyPrefixPing = "mavl-echo-ping:%s"
// KeyPrefixPang pang 前缀
KeyPrefixPang = "mavl-echo-pang:%s"
// KeyPrefixPingLocal local ping 前缀
KeyPrefixPingLocal = "LODB-echo-ping:%s"
// KeyPrefixPangLocal local pang 前缀
KeyPrefixPangLocal = "LODB-echo-pang:%s"
)
// init 初始化时通过反射获取本执行器的方法列表
func init() {
ety := types.LoadExecutorType(echotypes.EchoX)
ety.InitFuncList(types.ListMethod(&Echo{}))
}
// Init 本执行器的初始化动作,向系统注册本执行器,这里生效高度暂写为0
func Init(name string, sub []byte) {
dapp.Register(echotypes.EchoX, newEcho, 0)
}
// Echo 定义执行器对象
type Echo struct {
dapp.DriverBase
}
// 执行器对象初始化包装逻辑,后面的两步设置子对象和设置执行器类型必不可少
func newEcho() dapp.Driver {
c := &Echo{}
c.SetChild(c)
c.SetExecutorType(types.LoadExecutorType(echotypes.EchoX))
return c
}
// GetDriverName 返回本执行器驱动名称
func (h *Echo) GetDriverName() string {
return echotypes.EchoX
}
package executor
import (
"fmt"
"github.com/33cn/chain33/types"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
// Exec_Ping 执行 ping 类型交易
func (h *Echo) Exec_Ping(ping *echotypes.Ping, tx *types.Transaction, index int) (*types.Receipt, error) {
msg := ping.Msg
res := fmt.Sprintf("%s, ping ping ping!", msg)
xx := &echotypes.PingLog{Msg: msg, Echo: res}
logs := []*types.ReceiptLog{{Ty: echotypes.TyLogPing, Log: types.Encode(xx)}}
kv := []*types.KeyValue{{Key: []byte(fmt.Sprintf(KeyPrefixPing, msg)), Value: []byte(res)}}
receipt := &types.Receipt{Ty: types.ExecOk, KV: kv, Logs: logs}
return receipt, nil
}
// Exec_Pang 执行 pang 类型交易
func (h *Echo) Exec_Pang(ping *echotypes.Pang, tx *types.Transaction, index int) (*types.Receipt, error) {
msg := ping.Msg
res := fmt.Sprintf("%s, pang pang pang!", msg)
xx := &echotypes.PangLog{Msg: msg, Echo: res}
logs := []*types.ReceiptLog{{Ty: echotypes.TyLogPang, Log: types.Encode(xx)}}
kv := []*types.KeyValue{{Key: []byte(fmt.Sprintf(KeyPrefixPang, msg)), Value: []byte(res)}}
receipt := &types.Receipt{Ty: types.ExecOk, KV: kv, Logs: logs}
return receipt, nil
}
package executor
import (
"fmt"
"github.com/33cn/chain33/types"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
// ExecDelLocal_Ping 交易执行成功,将本消息对应的数值减1
func (h *Echo) ExecDelLocal_Ping(ping *echotypes.Ping, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
// 这里简化处理,不做基本的零值及错误检查了
var pingLog echotypes.PingLog
types.Decode(receipt.Logs[0].Log, &pingLog)
localKey := []byte(fmt.Sprintf(KeyPrefixPingLocal, pingLog.Msg))
oldValue, err := h.GetLocalDB().Get(localKey)
if err != nil {
return nil, err
}
types.Decode(oldValue, &pingLog)
if pingLog.Count > 0 {
pingLog.Count--
}
val := types.Encode(&pingLog)
if pingLog.Count == 0 {
val = nil
}
kv := []*types.KeyValue{{Key: localKey, Value: val}}
return &types.LocalDBSet{KV: kv}, nil
}
// ExecDelLocal_Pang 交易执行成功,将本消息对应的数值减1
func (h *Echo) ExecDelLocal_Pang(ping *echotypes.Pang, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
// 这里简化处理,不做基本的零值及错误检查了
var pangLog echotypes.PangLog
types.Decode(receipt.Logs[0].Log, &pangLog)
localKey := []byte(fmt.Sprintf(KeyPrefixPangLocal, pangLog.Msg))
oldValue, err := h.GetLocalDB().Get(localKey)
if err != nil {
return nil, err
}
types.Decode(oldValue, &pangLog)
if pangLog.Count > 0 {
pangLog.Count--
}
val := types.Encode(&pangLog)
if pangLog.Count == 0 {
val = nil
}
kv := []*types.KeyValue{{Key: localKey, Value: val}}
return &types.LocalDBSet{KV: kv}, nil
}
package executor
import (
"fmt"
"github.com/33cn/chain33/types"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
// ExecLocal_Ping 交易执行成功,将本消息对应的数值加1
func (h *Echo) ExecLocal_Ping(ping *echotypes.Ping, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
// 这里简化处理,不做基本的零值及错误检查了
var pingLog echotypes.PingLog
types.Decode(receipt.Logs[0].Log, &pingLog)
localKey := []byte(fmt.Sprintf(KeyPrefixPingLocal, pingLog.Msg))
oldValue, err := h.GetLocalDB().Get(localKey)
if err != nil && err != types.ErrNotFound {
return nil, err
}
if err == nil {
types.Decode(oldValue, &pingLog)
}
pingLog.Count++
kv := []*types.KeyValue{{Key: localKey, Value: types.Encode(&pingLog)}}
return &types.LocalDBSet{KV: kv}, nil
}
// ExecLocal_Pang 交易执行成功,将本消息对应的数值加1
func (h *Echo) ExecLocal_Pang(ping *echotypes.Pang, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
// 这里简化处理,不做基本的零值及错误检查了
var pangLog echotypes.PangLog
types.Decode(receipt.Logs[0].Log, &pangLog)
localKey := []byte(fmt.Sprintf(KeyPrefixPangLocal, pangLog.Msg))
oldValue, err := h.GetLocalDB().Get(localKey)
if err != nil && err != types.ErrNotFound {
return nil, err
}
if err == nil {
types.Decode(oldValue, &pangLog)
}
pangLog.Count++
kv := []*types.KeyValue{{Key: localKey, Value: types.Encode(&pangLog)}}
return &types.LocalDBSet{KV: kv}, nil
}
package executor
import (
"fmt"
"github.com/33cn/chain33/types"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
// Query_GetPing 查询 ping 次数
func (h *Echo) Query_GetPing(in *echotypes.Query) (types.Message, error) {
var pingLog echotypes.PingLog
localKey := []byte(fmt.Sprintf(KeyPrefixPingLocal, in.Msg))
value, err := h.GetLocalDB().Get(localKey)
if err != nil {
return nil, err
}
types.Decode(value, &pingLog)
res := echotypes.QueryResult{Msg: in.Msg, Count: pingLog.Count}
return &res, nil
}
// Query_GetPang 查询 pang 次数
func (h *Echo) Query_GetPang(in *echotypes.Query) (types.Message, error) {
var pangLog echotypes.PangLog
localKey := []byte(fmt.Sprintf(KeyPrefixPangLocal, in.Msg))
value, err := h.GetLocalDB().Get(localKey)
if err != nil {
return nil, err
}
types.Decode(value, &pangLog)
res := echotypes.QueryResult{Msg: in.Msg, Count: pangLog.Count}
return &res, nil
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package echo
import (
"github.com/33cn/chain33/pluginmgr"
"github.com/33cn/plugin/plugin/dapp/echo/commands"
"github.com/33cn/plugin/plugin/dapp/echo/executor"
"github.com/33cn/plugin/plugin/dapp/echo/rpc"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
func init() {
pluginmgr.Register(&pluginmgr.PluginBase{
Name: echotypes.EchoX,
ExecName: echotypes.EchoX,
Exec: executor.Init,
Cmd: commands.EchoCmd,
RPC: rpc.Init,
})
}
syntax = "proto3";
package echo;
// ping操作action
message Ping {
string msg = 1;
}
// pang操作action
message Pang {
string msg = 1;
}
// 本执行器的统一Action结构
message EchoAction {
oneof value {
Ping ping = 1;
Pang pang = 2;
}
int32 ty = 3;
}
// ping操作生成的日志结构
message PingLog {
string msg = 1;
string echo = 2;
int32 count = 3;
}
// pang操作生成的日志结构
message PangLog {
string msg = 1;
string echo = 2;
int32 count = 3;
}
// 查询请求结构
message Query {
string msg = 1;
}
// 查询结果结构
message QueryResult {
string msg = 1;
int32 count = 2;
}
\ No newline at end of file
package rpc
import (
"context"
rpctypes "github.com/33cn/chain33/rpc/types"
"github.com/33cn/chain33/types"
echotypes "github.com/33cn/plugin/plugin/dapp/echo/types/echo"
)
// Jrpc 对外提供服务的RPC接口总体定义
type Jrpc struct {
cli *channelClient
}
// RPC接口的本地实现
type channelClient struct {
rpctypes.ChannelClient
}
// Init 注册 rpc 接口
func Init(name string, s rpctypes.RPCServer) {
cli := &channelClient{}
// 为了简单起见,这里只注册Jrpc,如果提供grpc的话也在这里注册
cli.Init(name, s, &Jrpc{cli: cli}, nil)
}
// QueryPing 本合约的查询操作可以使用通用的Query接口,这里单独封装rpc的Query接口只是为了说明实现方式
// 接收客户端请求,并调用本地具体实现逻辑,然后返回结果
func (c *Jrpc) QueryPing(param *echotypes.Query, result *interface{}) error {
if param == nil {
return types.ErrInvalidParam
}
// 将具体的接口实现传递给本地逻辑
reply, err := c.cli.QueryPing(context.Background(), param)
if err != nil {
return err
}
*result = reply
return nil
}
// QueryPing 本地具体实现逻辑
func (c *channelClient) QueryPing(ctx context.Context, queryParam *echotypes.Query) (types.Message, error) {
return c.Query(echotypes.EchoX, "GetPing", queryParam)
}
package echo
import (
"encoding/json"
"math/rand"
"strings"
"time"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/types"
)
// CreateTx 创建交易
func (e *Type) CreateTx(action string, message json.RawMessage) (*types.Transaction, error) {
elog.Debug("echo.CreateTx", "action", action)
// 只接受ping/pang两种交易操作
if action == "ping" || action == "pang" {
var param Tx
err := json.Unmarshal(message, &param)
if err != nil {
elog.Error("CreateTx", "Error", err)
return nil, types.ErrInvalidParam
}
return createPingTx(action, &param)
}
return nil, types.ErrNotSupport
}
func createPingTx(op string, parm *Tx) (*types.Transaction, error) {
var action *EchoAction
var err error
if strings.EqualFold(op, "ping") {
action, err = getPingAction(parm)
} else {
action, err = getPangAction(parm)
}
if err != nil {
return nil, err
}
tx := &types.Transaction{
Execer: []byte(types.ExecName(EchoX)),
Payload: types.Encode(action),
Nonce: rand.New(rand.NewSource(time.Now().UnixNano())).Int63(),
To: address.ExecAddress(types.ExecName(EchoX)),
}
return tx, nil
}
func getPingAction(parm *Tx) (*EchoAction, error) {
pingAction := &Ping{Msg: parm.Message}
action := &EchoAction{
Value: &EchoAction_Ping{Ping: pingAction},
Ty: ActionPing,
}
return action, nil
}
func getPangAction(parm *Tx) (*EchoAction, error) {
pangAction := &Pang{Msg: parm.Message}
action := &EchoAction{
Value: &EchoAction_Pang{Pang: pangAction},
Ty: ActionPang,
}
return action, nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: echo.proto
/*
Package echo is a generated protocol buffer package.
It is generated from these files:
echo.proto
It has these top-level messages:
Ping
Pang
EchoAction
PingLog
PangLog
Query
QueryResult
*/
package echo
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// ping操作action
type Ping struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
}
func (m *Ping) Reset() { *m = Ping{} }
func (m *Ping) String() string { return proto.CompactTextString(m) }
func (*Ping) ProtoMessage() {}
func (*Ping) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Ping) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
// pang操作action
type Pang struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
}
func (m *Pang) Reset() { *m = Pang{} }
func (m *Pang) String() string { return proto.CompactTextString(m) }
func (*Pang) ProtoMessage() {}
func (*Pang) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *Pang) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
// 本执行器的统一Action结构
type EchoAction struct {
// Types that are valid to be assigned to Value:
// *EchoAction_Ping
// *EchoAction_Pang
Value isEchoAction_Value `protobuf_oneof:"value"`
Ty int32 `protobuf:"varint,3,opt,name=ty" json:"ty,omitempty"`
}
func (m *EchoAction) Reset() { *m = EchoAction{} }
func (m *EchoAction) String() string { return proto.CompactTextString(m) }
func (*EchoAction) ProtoMessage() {}
func (*EchoAction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
type isEchoAction_Value interface {
isEchoAction_Value()
}
type EchoAction_Ping struct {
Ping *Ping `protobuf:"bytes,1,opt,name=ping,oneof"`
}
type EchoAction_Pang struct {
Pang *Pang `protobuf:"bytes,2,opt,name=pang,oneof"`
}
func (*EchoAction_Ping) isEchoAction_Value() {}
func (*EchoAction_Pang) isEchoAction_Value() {}
func (m *EchoAction) GetValue() isEchoAction_Value {
if m != nil {
return m.Value
}
return nil
}
func (m *EchoAction) GetPing() *Ping {
if x, ok := m.GetValue().(*EchoAction_Ping); ok {
return x.Ping
}
return nil
}
func (m *EchoAction) GetPang() *Pang {
if x, ok := m.GetValue().(*EchoAction_Pang); ok {
return x.Pang
}
return nil
}
func (m *EchoAction) GetTy() int32 {
if m != nil {
return m.Ty
}
return 0
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*EchoAction) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _EchoAction_OneofMarshaler, _EchoAction_OneofUnmarshaler, _EchoAction_OneofSizer, []interface{}{
(*EchoAction_Ping)(nil),
(*EchoAction_Pang)(nil),
}
}
func _EchoAction_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*EchoAction)
// value
switch x := m.Value.(type) {
case *EchoAction_Ping:
b.EncodeVarint(1<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Ping); err != nil {
return err
}
case *EchoAction_Pang:
b.EncodeVarint(2<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.Pang); err != nil {
return err
}
case nil:
default:
return fmt.Errorf("EchoAction.Value has unexpected type %T", x)
}
return nil
}
func _EchoAction_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*EchoAction)
switch tag {
case 1: // value.ping
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Ping)
err := b.DecodeMessage(msg)
m.Value = &EchoAction_Ping{msg}
return true, err
case 2: // value.pang
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(Pang)
err := b.DecodeMessage(msg)
m.Value = &EchoAction_Pang{msg}
return true, err
default:
return false, nil
}
}
func _EchoAction_OneofSizer(msg proto.Message) (n int) {
m := msg.(*EchoAction)
// value
switch x := m.Value.(type) {
case *EchoAction_Ping:
s := proto.Size(x.Ping)
n += proto.SizeVarint(1<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *EchoAction_Pang:
s := proto.Size(x.Pang)
n += proto.SizeVarint(2<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
}
return n
}
// ping操作生成的日志结构
type PingLog struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
Echo string `protobuf:"bytes,2,opt,name=echo" json:"echo,omitempty"`
Count int32 `protobuf:"varint,3,opt,name=count" json:"count,omitempty"`
}
func (m *PingLog) Reset() { *m = PingLog{} }
func (m *PingLog) String() string { return proto.CompactTextString(m) }
func (*PingLog) ProtoMessage() {}
func (*PingLog) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *PingLog) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
func (m *PingLog) GetEcho() string {
if m != nil {
return m.Echo
}
return ""
}
func (m *PingLog) GetCount() int32 {
if m != nil {
return m.Count
}
return 0
}
// pang操作生成的日志结构
type PangLog struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
Echo string `protobuf:"bytes,2,opt,name=echo" json:"echo,omitempty"`
Count int32 `protobuf:"varint,3,opt,name=count" json:"count,omitempty"`
}
func (m *PangLog) Reset() { *m = PangLog{} }
func (m *PangLog) String() string { return proto.CompactTextString(m) }
func (*PangLog) ProtoMessage() {}
func (*PangLog) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *PangLog) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
func (m *PangLog) GetEcho() string {
if m != nil {
return m.Echo
}
return ""
}
func (m *PangLog) GetCount() int32 {
if m != nil {
return m.Count
}
return 0
}
// 查询请求结构
type Query struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
}
func (m *Query) Reset() { *m = Query{} }
func (m *Query) String() string { return proto.CompactTextString(m) }
func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
func (m *Query) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
// 查询结果结构
type QueryResult struct {
Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
Count int32 `protobuf:"varint,2,opt,name=count" json:"count,omitempty"`
}
func (m *QueryResult) Reset() { *m = QueryResult{} }
func (m *QueryResult) String() string { return proto.CompactTextString(m) }
func (*QueryResult) ProtoMessage() {}
func (*QueryResult) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (m *QueryResult) GetMsg() string {
if m != nil {
return m.Msg
}
return ""
}
func (m *QueryResult) GetCount() int32 {
if m != nil {
return m.Count
}
return 0
}
func init() {
proto.RegisterType((*Ping)(nil), "echo.Ping")
proto.RegisterType((*Pang)(nil), "echo.Pang")
proto.RegisterType((*EchoAction)(nil), "echo.EchoAction")
proto.RegisterType((*PingLog)(nil), "echo.PingLog")
proto.RegisterType((*PangLog)(nil), "echo.PangLog")
proto.RegisterType((*Query)(nil), "echo.Query")
proto.RegisterType((*QueryResult)(nil), "echo.QueryResult")
}
func init() { proto.RegisterFile("echo.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 215 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x91, 0x31, 0x4b, 0xc7, 0x30,
0x10, 0xc5, 0x6d, 0xda, 0x58, 0x7a, 0x05, 0x91, 0xe0, 0x10, 0xb7, 0xd2, 0xa9, 0x53, 0x07, 0xc5,
0x0f, 0xa0, 0x50, 0x70, 0x70, 0xd0, 0x7c, 0x83, 0x18, 0x42, 0x1b, 0xa8, 0x49, 0x69, 0x13, 0xa1,
0xdf, 0x5e, 0x72, 0x2d, 0xa2, 0x90, 0xed, 0xbf, 0xbd, 0xf0, 0x1e, 0xbf, 0x97, 0xbb, 0x03, 0xd0,
0x6a, 0x72, 0xfd, 0xb2, 0x3a, 0xef, 0x58, 0x11, 0x75, 0xcb, 0xa1, 0x78, 0x37, 0x76, 0x64, 0xb7,
0x90, 0x7f, 0x6d, 0x23, 0xcf, 0x9a, 0xac, 0xab, 0x44, 0x94, 0xe8, 0xc8, 0xa4, 0x63, 0x00, 0x06,
0x35, 0xb9, 0x67, 0xe5, 0x8d, 0xb3, 0xac, 0x81, 0x62, 0x31, 0xf6, 0x08, 0xd4, 0x0f, 0xd0, 0x63,
0x45, 0x64, 0xbe, 0x5e, 0x09, 0x74, 0x30, 0x21, 0xed, 0xc8, 0xc9, 0xbf, 0x84, 0x3c, 0x13, 0xb1,
0xe3, 0x06, 0x88, 0xdf, 0x79, 0xde, 0x64, 0x1d, 0x15, 0xc4, 0xef, 0x2f, 0x25, 0xd0, 0x6f, 0x39,
0x07, 0xdd, 0x0e, 0x50, 0x46, 0xd4, 0x9b, 0x4b, 0xfc, 0x83, 0x31, 0xc0, 0x19, 0x90, 0x5b, 0x09,
0xd4, 0xec, 0x0e, 0xa8, 0x72, 0xc1, 0xfa, 0x13, 0x76, 0x3c, 0x10, 0x23, 0x2f, 0xc7, 0xdc, 0x03,
0xfd, 0x08, 0x7a, 0xdd, 0x13, 0x3b, 0x79, 0x82, 0x1a, 0x2d, 0xa1, 0xb7, 0x30, 0xfb, 0x44, 0xcb,
0x2f, 0x91, 0xfc, 0x21, 0x7e, 0x5e, 0xe3, 0x2d, 0x1e, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x91,
0xcb, 0x59, 0x09, 0x99, 0x01, 0x00, 0x00,
}
package echo
// Tx echo 交易结构
type Tx struct {
Message string `json:"msg"`
}
package echo
import (
"reflect"
log "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/types"
)
// 定义本执行器支持的Action种类
const (
ActionPing = iota
ActionPang
)
// 定义本执行器生成的log类型
const (
TyLogPing = 100001
TyLogPang = 100002
)
var (
// EchoX 本执行器名称
EchoX = "echo"
// 定义本执行器支持的Action对应关系
actionName = map[string]int32{
"Ping": ActionPing,
"Pang": ActionPang,
}
// 定义本执行器的Log收据解析结构
logInfo = map[int64]*types.LogInfo{
TyLogPing: {Ty: reflect.TypeOf(PingLog{}), Name: "PingLog"},
TyLogPang: {Ty: reflect.TypeOf(PangLog{}), Name: "PangLog"},
}
)
var elog = log.New("module", EchoX)
func init() {
// 将本执行器添加到系统白名单
types.AllowUserExec = append(types.AllowUserExec, []byte(EchoX))
// 向系统注册本执行器类型
types.RegistorExecutor(EchoX, NewType())
}
// Type 定义本执行器类型
type Type struct {
types.ExecTypeBase
}
// NewType 初始化本执行器类型
func NewType() *Type {
c := &Type{}
c.SetChild(c)
return c
}
// GetPayload 返回本执行器的负载类型
func (b *Type) GetPayload() types.Message {
return &EchoAction{}
}
// GetName 返回本执行器名称
func (b *Type) GetName() string {
return EchoX
}
// GetTypeMap 返回本执行器中的action字典,支持双向查找
func (b *Type) GetTypeMap() map[string]int32 {
return actionName
}
// GetLogMap 返回本执行器的日志类型信息,用于rpc解析日志数据
func (b *Type) GetLogMap() map[int64]*types.LogInfo {
return logInfo
}
......@@ -3,6 +3,7 @@ package init
import (
_ "github.com/33cn/plugin/plugin/dapp/blackwhite" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/cert" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/echo" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/evm" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/game" //auto gen
_ "github.com/33cn/plugin/plugin/dapp/guess" //auto gen
......
......@@ -887,7 +887,7 @@ func (m *MultiSig) getMultiSigAccAssets(multiSigAddr string, assets *mty.Assets)
}
var acc1 *types.Account
execaddress := dapp.ExecAddress(m.GetName())
execaddress := dapp.ExecAddress(types.ExecName(m.GetName()))
acc1 = acc.LoadExecAccount(multiSigAddr, execaddress)
return acc1, nil
}
......
......@@ -5,6 +5,7 @@
package executor
import (
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/types"
pt "github.com/33cn/plugin/plugin/dapp/paracross/types"
)
......@@ -18,7 +19,7 @@ func (e *Paracross) ExecDelLocal_Commit(payload *pt.ParacrossCommitAction, tx *t
types.Decode(log.Log, &g)
var r pt.ParacrossTx
r.TxHash = string(tx.Hash())
r.TxHash = common.ToHex(tx.Hash())
set.KV = append(set.KV, &types.KeyValue{Key: calcLocalTxKey(g.Status.Title, g.Status.Height, tx.From()), Value: nil})
} else if log.Ty == pt.TyLogParacrossCommitDone {
var g pt.ReceiptParacrossDone
......@@ -41,7 +42,7 @@ func (e *Paracross) ExecDelLocal_Commit(payload *pt.ParacrossCommitAction, tx *t
types.Decode(log.Log, &g)
var r pt.ParacrossTx
r.TxHash = string(tx.Hash())
r.TxHash = common.ToHex(tx.Hash())
set.KV = append(set.KV, &types.KeyValue{Key: calcLocalTxKey(g.Status.Title, g.Status.Height, tx.From()), Value: nil})
}
}
......
......@@ -7,6 +7,7 @@ package executor
import (
"bytes"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
pt "github.com/33cn/plugin/plugin/dapp/paracross/types"
......@@ -21,7 +22,7 @@ func (e *Paracross) ExecLocal_Commit(payload *pt.ParacrossCommitAction, tx *type
types.Decode(log.Log, &g)
var r pt.ParacrossTx
r.TxHash = string(tx.Hash())
r.TxHash = common.ToHex(tx.Hash())
set.KV = append(set.KV, &types.KeyValue{Key: calcLocalTxKey(g.Status.Title, g.Status.Height, tx.From()), Value: types.Encode(&r)})
} else if log.Ty == pt.TyLogParacrossCommitDone {
var g pt.ReceiptParacrossDone
......@@ -43,7 +44,7 @@ func (e *Paracross) ExecLocal_Commit(payload *pt.ParacrossCommitAction, tx *type
types.Decode(log.Log, &g)
var r pt.ParacrossTx
r.TxHash = string(tx.Hash())
r.TxHash = common.ToHex(tx.Hash())
set.KV = append(set.KV, &types.KeyValue{Key: calcLocalTxKey(g.Status.Title, g.Status.Height, tx.From()), Value: types.Encode(&r)})
}
}
......
......@@ -34,7 +34,7 @@ func CreateRawTx(cmd *cobra.Command, to string, amount float64, note string, isW
transfer.Value = v
transfer.Ty = tokenty.ActionTransfer
} else {
v := &tokenty.TokenAction_Withdraw{Withdraw: &types.AssetsWithdraw{Cointoken: tokenSymbol, Amount: amountInt64, Note: []byte(note)}}
v := &tokenty.TokenAction_Withdraw{Withdraw: &types.AssetsWithdraw{Cointoken: tokenSymbol, Amount: amountInt64, Note: []byte(note), To: to}}
transfer.Value = v
transfer.Ty = tokenty.ActionWithdraw
}
......
......@@ -614,6 +614,7 @@ func (t *trade) loadOrderFromKey(key []byte) *pty.ReplyTradeOrder {
return nil
}
reply.TradedBoardlot = sellOrder.SoldBoardlot
reply.Status = sellOrder.Status
return reply
} else if strings.HasPrefix(string(key), buyIDPrefix) {
txHash := strings.Replace(string(key), buyIDPrefix, "0x", 1)
......@@ -629,6 +630,7 @@ func (t *trade) loadOrderFromKey(key []byte) *pty.ReplyTradeOrder {
return nil
}
reply.TradedBoardlot = buyOrder.BoughtBoardlot
reply.Status = buyOrder.Status
return reply
}
txResult, err := getTx(key, t.GetLocalDB())
......
......@@ -140,7 +140,7 @@ func genSaveBuyLimitKv(buyOrder *pty.BuyLimitOrder) []*types.KeyValue {
kv = saveBuyLimitOrderKeyValue(kv, buyOrder, status)
if pty.TradeOrderStatusBoughtOut == status || pty.TradeOrderStatusBuyRevoked == status {
tradelog.Debug("trade saveBuyLimit ", "remove old status with Buyid", buyOrder.BuyID)
kv = deleteBuyLimitKeyValue(kv, buyOrder, pty.TradeOrderStatusOnSale)
kv = deleteBuyLimitKeyValue(kv, buyOrder, pty.TradeOrderStatusOnBuy)
}
return kv
}
......
......@@ -6,9 +6,9 @@ dist: xenial
notifications:
email: false
jobs:
matrix:
include:
- stage: check_fmt
- name: check_fmt
sudo: require
go:
- "1.9"
......@@ -23,13 +23,13 @@ jobs:
- make checkgofmt && make fmt_go
- make linter
- stage: unit-test
- name: unit-test
go: "1.9.x"
install: skip
script:
- make test
- stage: coverage
- name: coverage
if: branch = master
go:
- "1.9.x"
......@@ -41,7 +41,7 @@ jobs:
after_success:
- bash <(curl -s https://codecov.io/bash)
- stage: deploy
- name: deploy
sudo: required
services:
- docker
......
......@@ -8,7 +8,6 @@ package db
import (
"bytes"
"errors"
"fmt"
"github.com/33cn/chain33/types"
......@@ -16,7 +15,7 @@ import (
)
//ErrNotFoundInDb error
var ErrNotFoundInDb = errors.New("ErrNotFoundInDb")
var ErrNotFoundInDb = types.ErrNotFound
//Lister 列表接口
type Lister interface {
......
......@@ -11,6 +11,7 @@ import (
"fmt"
"github.com/33cn/chain33/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
......@@ -234,3 +235,26 @@ func testDBIteratorDel(t *testing.T, db DB) {
batch.Write()
}
}
func testLevelDBBatch(t *testing.T, db DB) {
batch := db.NewBatch(false)
batch.Set([]byte("hello"), []byte("world"))
err := batch.Write()
assert.Nil(t, err)
v, err := db.Get([]byte("hello"))
assert.Nil(t, err)
assert.Equal(t, v, []byte("world"))
//set and del
batch.Set([]byte("hello1"), []byte("world"))
batch.Set([]byte("hello2"), []byte("world"))
batch.Set([]byte("hello3"), []byte("world"))
batch.Set([]byte("hello4"), []byte("world"))
batch.Set([]byte("hello5"), []byte("world"))
batch.Delete([]byte("hello1"))
err = batch.Write()
assert.Nil(t, err)
v, err = db.Get([]byte("hello1"))
assert.Equal(t, err, types.ErrNotFound)
assert.Nil(t, v)
}
......@@ -43,6 +43,18 @@ func TestGoLevelDBIteratorDel(t *testing.T) {
testDBIteratorDel(t, leveldb)
}
func TestLevelDBBatch(t *testing.T) {
dir, err := ioutil.TempDir("", "goleveldb")
require.NoError(t, err)
t.Log(dir)
leveldb, err := NewGoLevelDB("goleveldb", dir, 128)
require.NoError(t, err)
defer leveldb.Close()
testLevelDBBatch(t, leveldb)
}
// leveldb边界测试
func TestGoLevelDBBoundary(t *testing.T) {
dir, err := ioutil.TempDir("", "goleveldb")
......
......@@ -55,7 +55,6 @@ func (db *ListHelper) List(prefix, key []byte, count, direction int32) (values [
return db.IteratorScanFromFirst(prefix, count)
}
return db.IteratorScanFromLast(prefix, count)
}
if count == 1 && direction == ListSeek {
it := db.db.Iterator(prefix, nil, true)
......
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package table
import (
"math"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
)
//Count 计数器
type Count struct {
prefix string
name string
kvdb db.KV
num int64
keydata []byte
}
//NewCount 创建一个计数器
func NewCount(prefix string, name string, kvdb db.KV) *Count {
keydata := []byte(prefix + "#" + name)
return &Count{
prefix: prefix,
name: name,
kvdb: kvdb,
keydata: keydata,
num: math.MinInt64,
}
}
func (c *Count) getKey() []byte {
return c.keydata
}
//Save 保存kv
func (c *Count) Save() (kvs []*types.KeyValue, err error) {
if c.num == math.MinInt64 {
return nil, nil
}
var i types.Int64
i.Data = c.num
item := &types.KeyValue{Key: c.getKey(), Value: types.Encode(&i)}
kvs = append(kvs, item)
return
}
//Get count
func (c *Count) Get() (int64, error) {
if c.num == math.MinInt64 {
data, err := c.kvdb.Get(c.getKey())
if err == types.ErrNotFound {
c.num = 0
} else if err != nil {
return 0, err
}
var num types.Int64
err = types.Decode(data, &num)
if err != nil {
return 0, err
}
c.num = num.Data
}
return c.num, nil
}
//Inc 增加1
func (c *Count) Inc() (num int64, err error) {
c.num, err = c.Get()
if err != nil {
return 0, err
}
c.num++
return c.num, nil
}
//Dec 减少1
func (c *Count) Dec() (num int64, err error) {
c.num, err = c.Get()
if err != nil {
return 0, err
}
c.num--
return c.num, nil
}
//Set 这个操作要谨慎使用
func (c *Count) Set(i int64) {
c.num = i
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package table
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCount(t *testing.T) {
dir, leveldb, kvdb := getdb()
defer dbclose(dir, leveldb)
count := NewCount("prefix", "name#hello", kvdb)
count.Inc()
count.Dec()
count.Inc()
i, err := count.Get()
assert.Nil(t, err)
assert.Equal(t, i, int64(1))
kvs, err := count.Save()
assert.Nil(t, err)
setKV(leveldb, kvs)
count = NewCount("prefix", "name#hello", kvdb)
i, err = count.Get()
assert.Nil(t, err)
assert.Equal(t, i, int64(1))
count.Set(2)
i, err = count.Get()
assert.Nil(t, err)
assert.Equal(t, i, int64(2))
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package table
import "errors"
//table 中的错误处理
var (
ErrEmptyPrimaryKey = errors.New("ErrEmptyPrimaryKey")
ErrPrimaryKey = errors.New("ErrPrimaryKey")
ErrIndexKey = errors.New("ErrIndexKey")
ErrTooManyIndex = errors.New("ErrTooManyIndex")
ErrTablePrefixOrTableName = errors.New("ErrTablePrefixOrTableName")
ErrDupPrimaryKey = errors.New("ErrDupPrimaryKey")
)
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package table
import (
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
)
//Query 列表查询结构
type Query struct {
table *Table
kvdb db.KVDB
}
//ListIndex 根据索引查询列表
//index 用哪个index
//prefix 必须要符合的前缀, 可以为空
//primaryKey 开始查询的位置(不包含数据本身)
//count 最多取的数量
//direction 方向
func (query *Query) ListIndex(indexName string, prefix []byte, primaryKey []byte, count, direction int32) (rows []*Row, err error) {
if indexName == "" {
return query.ListPrimary(prefix, primaryKey, count, direction)
}
p := query.table.indexPrefix(indexName)
var k []byte
if len(primaryKey) > 0 {
row, err := query.table.GetData(primaryKey)
if err != nil {
return nil, err
}
key, err := query.table.index(row, indexName)
if err != nil {
return nil, err
}
//如果存在prefix
if prefix != nil {
p2 := commonPrefix(prefix, key)
if len(p2) != len(prefix) {
return nil, types.ErrNotFound
}
p = append(p, p2...)
}
k = query.table.getIndexKey(indexName, key, row.Primary)
} else {
//这个情况下 k == nil
p = append(p, prefix...)
}
values, err := query.kvdb.List(p, k, count, direction)
if err != nil {
return nil, err
}
for _, value := range values {
row, err := query.table.GetData(value)
if err != nil {
return nil, err
}
rows = append(rows, row)
}
return rows, nil
}
//ListPrimary list primary data
func (query *Query) ListPrimary(prefix []byte, primaryKey []byte, count, direction int32) (rows []*Row, err error) {
p := query.table.primaryPrefix()
var k []byte
if primaryKey != nil {
if prefix != nil {
p2 := commonPrefix(prefix, primaryKey)
if len(p2) != len(prefix) {
return nil, types.ErrNotFound
}
p = append(p, p2...)
}
k = append(p, primaryKey...)
} else {
p = append(p, prefix...)
}
values, err := query.kvdb.List(p, k, count, direction)
if err != nil {
return nil, err
}
for _, value := range values {
row, err := query.table.getRow(value)
if err != nil {
return nil, err
}
rows = append(rows, row)
}
return rows, nil
}
func commonPrefix(key1, key2 []byte) []byte {
l1 := len(key1)
l2 := len(key2)
l := min(l1, l2)
for i := 0; i < l; i++ {
if key1[i] != key2[i] {
return key1[:i]
}
}
return key1[0:l]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//Package table 实现一个基于kv的关系型数据库的表格功能
package table
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
)
//设计结构:
/*
核心: 平衡
save:
数据保存:
tableprefix + tablename + Primary -> data
index:
tableprefix + tablemetaname + index + primary -> primary
read:
list by Primary -> 直接读出数据
list by index
根据index 先计算要读出的 primary list
从数据table读出数据(根据 primary key)
del:
利用 primaryKey + index 删除所有的 数据 和 索引
*/
//指出是 添加 还是 删除 行
//primary key auto 的del 需要指定 primary key
const (
None = iota
Add
Del
)
//meta key
const meta = "#m#"
const data = "#d#"
//RowMeta 定义行的操作
type RowMeta interface {
CreateRow() *Row
SetPayload(types.Message) error
Get(key string) ([]byte, error)
}
//Row 行操作
type Row struct {
Ty int
Primary []byte
Data types.Message
}
func encodeInt64(p int64) ([]byte, error) {
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, p)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func decodeInt64(p []byte) (int64, error) {
buf := bytes.NewBuffer(p)
var i int64
err := binary.Read(buf, binary.LittleEndian, &i)
if err != nil {
return 0, err
}
return i, nil
}
//Encode row
func (row *Row) Encode() ([]byte, error) {
b, err := encodeInt64(int64(len(row.Primary)))
if err != nil {
return nil, err
}
b = append(b, row.Primary...)
b = append(b, types.Encode(row.Data)...)
return b, nil
}
//DecodeRow from data
func DecodeRow(data []byte) ([]byte, []byte, error) {
if len(data) <= 8 {
return nil, nil, types.ErrDecode
}
l, err := decodeInt64(data[:8])
if err != nil {
return nil, nil, err
}
if len(data) < int(l)+8 {
return nil, nil, types.ErrDecode
}
return data[8 : int(l)+8], data[int(l)+8:], nil
}
//Table 定一个表格, 并且添加 primary key, index key
type Table struct {
meta RowMeta
rows []*Row
rowmap map[string]*Row
kvdb db.KV
opt *Option
autoinc *Count
dataprefix string
metaprefix string
}
//Option table 的选项
type Option struct {
Prefix string
Name string
Primary string
Index []string
}
//NewTable 新建一个表格
//primary 可以为: auto, 由系统自动创建
//index 可以为nil
func NewTable(rowmeta RowMeta, kvdb db.KV, opt *Option) (*Table, error) {
if len(opt.Index) > 16 {
return nil, ErrTooManyIndex
}
for _, index := range opt.Index {
if strings.Contains(index, "#") {
return nil, ErrIndexKey
}
}
if opt.Primary == "" {
opt.Primary = "auto"
}
if _, err := getPrimaryKey(rowmeta, opt.Primary); err != nil {
return nil, err
}
//不允许有#
if strings.Contains(opt.Prefix, "#") || strings.Contains(opt.Name, "#") {
return nil, ErrTablePrefixOrTableName
}
dataprefix := opt.Prefix + "#" + opt.Name + data
metaprefix := opt.Prefix + "#" + opt.Name + meta
count := NewCount(opt.Prefix, opt.Name+"#autoinc#", kvdb)
return &Table{
meta: rowmeta,
kvdb: kvdb,
rowmap: make(map[string]*Row),
opt: opt,
autoinc: count,
dataprefix: dataprefix,
metaprefix: metaprefix}, nil
}
func getPrimaryKey(meta RowMeta, primary string) ([]byte, error) {
if primary == "" {
return nil, ErrEmptyPrimaryKey
}
if strings.Contains(primary, "#") {
return nil, ErrPrimaryKey
}
if primary != "auto" {
key, err := meta.Get(primary)
return key, err
}
return nil, nil
}
func (table *Table) addRowCache(row *Row) {
table.rowmap[string(row.Primary)] = row
table.rows = append(table.rows, row)
}
func (table *Table) findRow(primary []byte) (*Row, error) {
if row, ok := table.rowmap[string(primary)]; ok {
return row, nil
}
return table.GetData(primary)
}
func (table *Table) checkIndex(data types.Message) error {
err := table.meta.SetPayload(data)
if err != nil {
return err
}
if _, err := getPrimaryKey(table.meta, table.opt.Primary); err != nil {
return err
}
for i := 0; i < len(table.opt.Index); i++ {
_, err := table.meta.Get(table.opt.Index[i])
if err != nil {
return err
}
}
return nil
}
func (table *Table) getPrimaryAuto() ([]byte, error) {
i, err := table.autoinc.Inc()
if err != nil {
return nil, err
}
return []byte(pad(i)), nil
}
//primaryKey 获取主键
//1. auto 的情况下,只能自增。
//2. 没有auto的情况下从数据中取
func (table *Table) primaryKey(data types.Message) (primaryKey []byte, err error) {
if table.opt.Primary == "auto" {
primaryKey, err = table.getPrimaryAuto()
if err != nil {
return nil, err
}
} else {
primaryKey, err = table.getPrimaryFromData(data)
}
return
}
func (table *Table) getPrimaryFromData(data types.Message) (primaryKey []byte, err error) {
err = table.meta.SetPayload(data)
if err != nil {
return nil, err
}
primaryKey, err = getPrimaryKey(table.meta, table.opt.Primary)
if err != nil {
return nil, err
}
return
}
//Replace 如果有重复的,那么替换
func (table *Table) Replace(data types.Message) error {
if err := table.checkIndex(data); err != nil {
return err
}
primaryKey, err := table.primaryKey(data)
if err != nil {
return err
}
//如果是auto的情况,一定是添加
if table.opt.Primary == "auto" {
table.addRowCache(&Row{Data: data, Primary: primaryKey, Ty: Add})
return nil
}
//如果没有找到行, 那么添加
row, err := table.findRow(primaryKey)
if err == types.ErrNotFound {
table.addRowCache(&Row{Data: data, Primary: primaryKey, Ty: Add})
return nil
}
//更新数据
delrow := *row
delrow.Ty = Del
//update 是一个del 和 update 的组合
table.addRowCache(&delrow)
table.addRowCache(&Row{Data: data, Primary: primaryKey, Ty: Add})
return nil
}
//Add 在表格中添加一行
func (table *Table) Add(data types.Message) error {
if err := table.checkIndex(data); err != nil {
return err
}
primaryKey, err := table.primaryKey(data)
if err != nil {
return err
}
//find in cache + db
_, err = table.findRow(primaryKey)
if err != types.ErrNotFound {
return ErrDupPrimaryKey
}
//检查cache中是否有重复,有重复也返回错误
table.addRowCache(&Row{Data: data, Primary: primaryKey, Ty: Add})
return nil
}
//Update 更新数据库
func (table *Table) Update(primaryKey []byte, newdata types.Message) (err error) {
if err := table.checkIndex(newdata); err != nil {
return err
}
p1, err := table.getPrimaryFromData(newdata)
if err != nil {
return err
}
if !bytes.Equal(p1, primaryKey) {
return types.ErrInvalidParam
}
row, err := table.findRow(primaryKey)
//查询发生错误
if err != nil {
return err
}
delrow := *row
delrow.Ty = Del
//update 是一个del 和 update 的组合
table.addRowCache(&delrow)
table.addRowCache(&Row{Data: newdata, Primary: primaryKey, Ty: Add})
return nil
}
//Del 在表格中删除一行(包括删除索引)
func (table *Table) Del(primaryKey []byte) error {
row, err := table.findRow(primaryKey)
if err != nil {
return err
}
delrow := *row
delrow.Ty = Del
table.addRowCache(&delrow)
return nil
}
//getDataKey data key 构造
func (table *Table) getDataKey(primaryKey []byte) []byte {
return append([]byte(table.dataprefix), primaryKey...)
}
//GetIndexKey data key 构造
func (table *Table) getIndexKey(indexName string, index, primaryKey []byte) []byte {
key := table.indexPrefix(indexName)
key = append(key, index...)
key = append(key, []byte("#")...)
key = append(key, primaryKey...)
return key
}
func (table *Table) primaryPrefix() []byte {
return []byte(table.dataprefix)
}
func (table *Table) indexPrefix(indexName string) []byte {
key := append([]byte(table.metaprefix), []byte(indexName+"#")...)
return key
}
func (table *Table) index(row *Row, indexName string) ([]byte, error) {
err := table.meta.SetPayload(row.Data)
if err != nil {
return nil, err
}
return table.meta.Get(indexName)
}
func (table *Table) getData(primaryKey []byte) ([]byte, error) {
key := table.getDataKey(primaryKey)
value, err := table.kvdb.Get(key)
if err != nil {
return nil, err
}
return value, nil
}
//GetData 根据主键获取数据
func (table *Table) GetData(primaryKey []byte) (*Row, error) {
value, err := table.getData(primaryKey)
if err != nil {
return nil, err
}
return table.getRow(value)
}
func (table *Table) getRow(value []byte) (*Row, error) {
primary, data, err := DecodeRow(value)
if err != nil {
return nil, err
}
row := table.meta.CreateRow()
row.Primary = primary
err = types.Decode(data, row.Data)
if err != nil {
return nil, err
}
return row, nil
}
//Save 保存表格
func (table *Table) Save() (kvs []*types.KeyValue, err error) {
for _, row := range table.rows {
kvlist, err := table.saveRow(row)
if err != nil {
return nil, err
}
kvs = append(kvs, kvlist...)
}
kvlist, err := table.autoinc.Save()
if err != nil {
return nil, err
}
kvs = append(kvs, kvlist...)
//del cache
table.rowmap = make(map[string]*Row)
table.rows = nil
return kvs, nil
}
func pad(i int64) string {
return fmt.Sprintf("%020d", i)
}
func (table *Table) saveRow(row *Row) (kvs []*types.KeyValue, err error) {
if row.Ty == Del {
return table.delRow(row)
}
return table.addRow(row)
}
func (table *Table) delRow(row *Row) (kvs []*types.KeyValue, err error) {
deldata := &types.KeyValue{Key: table.getDataKey(row.Primary)}
kvs = append(kvs, deldata)
for _, index := range table.opt.Index {
indexkey, err := table.index(row, index)
if err != nil {
return nil, err
}
delindex := &types.KeyValue{Key: table.getIndexKey(index, indexkey, row.Primary)}
kvs = append(kvs, delindex)
}
return kvs, nil
}
func (table *Table) addRow(row *Row) (kvs []*types.KeyValue, err error) {
data, err := row.Encode()
if err != nil {
return nil, err
}
adddata := &types.KeyValue{Key: table.getDataKey(row.Primary), Value: data}
kvs = append(kvs, adddata)
for _, index := range table.opt.Index {
indexkey, err := table.index(row, index)
if err != nil {
return nil, err
}
addindex := &types.KeyValue{Key: table.getIndexKey(index, indexkey, row.Primary), Value: row.Primary}
kvs = append(kvs, addindex)
}
return kvs, nil
}
//GetQuery 获取查询结构
func (table *Table) GetQuery(kvdb db.KVDB) *Query {
return &Query{table: table, kvdb: kvdb}
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package table
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert"
)
func TestTransactinList(t *testing.T) {
dir, leveldb, kvdb := getdb()
defer dbclose(dir, leveldb)
opt := &Option{
Prefix: "prefix",
Name: "name",
Primary: "Hash",
Index: []string{"From", "To"},
}
table, err := NewTable(NewTransactionRow(), kvdb, opt)
assert.Nil(t, err)
addr1, priv := util.Genaddress()
tx1 := util.CreateNoneTx(priv)
err = table.Add(tx1)
assert.Nil(t, err)
tx2 := util.CreateNoneTx(priv)
err = table.Add(tx2)
assert.Nil(t, err)
addr2, priv := util.Genaddress()
tx3 := util.CreateNoneTx(priv)
err = table.Add(tx3)
assert.Nil(t, err)
tx4 := util.CreateNoneTx(priv)
err = table.Add(tx4)
assert.Nil(t, err)
//添加一个无效的类型
err = table.Add(nil)
assert.Equal(t, types.ErrTypeAsset, err)
kvs, err := table.Save()
assert.Nil(t, err)
assert.Equal(t, len(kvs), 12)
//save to database
setKV(leveldb, kvs)
//测试查询
query := table.GetQuery(kvdb)
rows, err := query.ListIndex("From", []byte(addr1), nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 2, len(rows))
if bytes.Compare(tx1.Hash(), tx2.Hash()) > 0 {
assert.Equal(t, true, proto.Equal(tx1, rows[0].Data))
assert.Equal(t, true, proto.Equal(tx2, rows[1].Data))
} else {
assert.Equal(t, true, proto.Equal(tx1, rows[1].Data))
assert.Equal(t, true, proto.Equal(tx2, rows[0].Data))
}
//prefix full
rows, err = query.ListIndex("From", []byte(addr2), nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 2, len(rows))
if bytes.Compare(tx3.Hash(), tx4.Hash()) > 0 {
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
assert.Equal(t, true, proto.Equal(tx4, rows[1].Data))
} else {
assert.Equal(t, true, proto.Equal(tx3, rows[1].Data))
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
}
//prefix part
rows, err = query.ListIndex("From", []byte(addr2[0:10]), nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 2, len(rows))
if bytes.Compare(tx3.Hash(), tx4.Hash()) > 0 {
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
assert.Equal(t, true, proto.Equal(tx4, rows[1].Data))
} else {
assert.Equal(t, true, proto.Equal(tx3, rows[1].Data))
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
}
//count
rows, err = query.ListIndex("From", []byte(addr2[0:10]), nil, 1, 0)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
if bytes.Compare(tx3.Hash(), tx4.Hash()) > 0 {
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
} else {
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
}
primary := rows[0].Primary
//primary
rows, err = query.ListIndex("From", nil, primary, 1, 0)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
if bytes.Compare(tx3.Hash(), tx4.Hash()) > 0 {
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
} else {
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
}
//prefix + primary
rows, err = query.ListIndex("From", []byte(addr2[0:10]), primary, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
if bytes.Compare(tx3.Hash(), tx4.Hash()) > 0 {
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
} else {
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
}
rows, err = query.ListIndex("From", []byte(addr1[0:10]), primary, 0, 0)
assert.Equal(t, types.ErrNotFound, err)
assert.Equal(t, 0, len(rows))
//ListPrimary all
rows, err = query.ListPrimary(nil, nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 4, len(rows))
primary = rows[0].Primary
rows, err = query.ListPrimary(primary[0:10], nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
rows, err = query.ListPrimary(nil, primary, 0, 0)
assert.Nil(t, err)
assert.Equal(t, 3, len(rows))
}
func TestTransactinListAuto(t *testing.T) {
dir, leveldb, kvdb := getdb()
defer dbclose(dir, leveldb)
opt := &Option{
Prefix: "prefix",
Name: "name",
Primary: "",
Index: []string{"From", "To"},
}
table, err := NewTable(NewTransactionRow(), kvdb, opt)
assert.Nil(t, err)
addr1, priv := util.Genaddress()
tx1 := util.CreateNoneTx(priv)
err = table.Add(tx1)
assert.Nil(t, err)
tx2 := util.CreateNoneTx(priv)
err = table.Add(tx2)
assert.Nil(t, err)
addr2, priv := util.Genaddress()
tx3 := util.CreateNoneTx(priv)
err = table.Add(tx3)
assert.Nil(t, err)
tx4 := util.CreateNoneTx(priv)
err = table.Add(tx4)
assert.Nil(t, err)
//添加一个无效的类型
err = table.Add(nil)
assert.Equal(t, types.ErrTypeAsset, err)
kvs, err := table.Save()
assert.Nil(t, err)
assert.Equal(t, len(kvs), 13)
//save to database
setKV(leveldb, kvs)
//测试查询
query := table.GetQuery(kvdb)
rows, err := query.ListIndex("From", []byte(addr1), nil, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 2, len(rows))
assert.Equal(t, true, proto.Equal(tx1, rows[0].Data))
assert.Equal(t, true, proto.Equal(tx2, rows[1].Data))
//prefix full
rows, err = query.ListIndex("From", []byte(addr2), nil, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 2, len(rows))
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
assert.Equal(t, true, proto.Equal(tx4, rows[1].Data))
//prefix part
rows, err = query.ListIndex("From", []byte(addr2[0:10]), nil, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 2, len(rows))
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
assert.Equal(t, true, proto.Equal(tx4, rows[1].Data))
//count
rows, err = query.ListIndex("From", []byte(addr2[0:10]), nil, 1, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
assert.Equal(t, true, proto.Equal(tx3, rows[0].Data))
primary := rows[0].Primary
//primary
rows, err = query.ListIndex("From", nil, primary, 1, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
//prefix + primary
rows, err = query.ListIndex("From", []byte(addr2[0:10]), primary, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
assert.Equal(t, true, proto.Equal(tx4, rows[0].Data))
rows, err = query.ListIndex("From", []byte(addr1[0:10]), primary, 0, db.ListASC)
assert.Equal(t, types.ErrNotFound, err)
assert.Equal(t, 0, len(rows))
//ListPrimary all
rows, err = query.ListPrimary(nil, nil, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 4, len(rows))
primary = rows[0].Primary
rows, err = query.ListPrimary(primary, nil, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 1, len(rows))
rows, err = query.ListPrimary(nil, primary, 0, db.ListASC)
assert.Nil(t, err)
assert.Equal(t, 3, len(rows))
}
func mergeDup(kvs []*types.KeyValue) (kvset []*types.KeyValue) {
maplist := make(map[string]*types.KeyValue)
for _, kv := range kvs {
if item, ok := maplist[string(kv.Key)]; ok {
item.Value = kv.Value //更新item 的value
} else {
maplist[string(kv.Key)] = kv
kvset = append(kvset, kv)
}
}
return kvset
}
func setKV(kvdb db.DB, kvs []*types.KeyValue) {
batch := kvdb.NewBatch(true)
for i := 0; i < len(kvs); i++ {
if kvs[i].Value == nil {
batch.Delete(kvs[i].Key)
continue
}
batch.Set(kvs[i].Key, kvs[i].Value)
}
err := batch.Write()
if err != nil {
panic(err)
}
}
func printKV(kvs []*types.KeyValue) {
for i := 0; i < len(kvs); i++ {
fmt.Println("KV", i, string(kvs[i].Key), common.ToHex(kvs[i].Value))
}
}
func TestRow(t *testing.T) {
rowmeta := NewTransactionRow()
row := rowmeta.CreateRow()
_, priv := util.Genaddress()
tx1 := util.CreateNoneTx(priv)
row.Data = tx1
row.Primary = tx1.Hash()
data, err := row.Encode()
assert.Nil(t, err)
primary, protodata, err := DecodeRow(data)
assert.Nil(t, err)
assert.Equal(t, primary, row.Primary)
var tx types.Transaction
err = types.Decode(protodata, &tx)
assert.Nil(t, err)
assert.Equal(t, proto.Equal(&tx, tx1), true)
}
func TestDel(t *testing.T) {
dir, leveldb, kvdb := getdb()
defer dbclose(dir, leveldb)
opt := &Option{
Prefix: "prefix",
Name: "name",
Primary: "Hash",
Index: []string{"From", "To"},
}
table, err := NewTable(NewTransactionRow(), kvdb, opt)
assert.Nil(t, err)
addr1, priv := util.Genaddress()
tx1 := util.CreateNoneTx(priv)
err = table.Add(tx1)
assert.Nil(t, err)
_, priv = util.Genaddress()
tx2 := util.CreateNoneTx(priv)
err = table.Add(tx2)
assert.Nil(t, err)
//删除掉一个
err = table.Del(tx1.Hash())
assert.Nil(t, err)
//save 然后从列表中读取
kvs, err := table.Save()
assert.Nil(t, err)
assert.Equal(t, len(kvs), 9)
//save to database
setKV(leveldb, kvs)
//printKV(kvs)
query := table.GetQuery(kvdb)
rows, err := query.ListIndex("From", []byte(addr1[0:10]), nil, 0, 0)
assert.Equal(t, types.ErrNotFound, err)
assert.Equal(t, 0, len(rows))
}
func printAllKey(db db.DB) {
it := db.Iterator(nil, nil, false)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
fmt.Println("db.allkey", string(it.Key()))
}
}
func TestUpdate(t *testing.T) {
dir, leveldb, kvdb := getdb()
defer dbclose(dir, leveldb)
opt := &Option{
Prefix: "prefix",
Name: "name",
Primary: "Hash",
Index: []string{"From", "To"},
}
table, err := NewTable(NewTransactionRow(), kvdb, opt)
assert.Nil(t, err)
_, priv := util.Genaddress()
tx1 := util.CreateNoneTx(priv)
err = table.Add(tx1)
assert.Nil(t, err)
err = table.Update([]byte("hello"), tx1)
assert.Equal(t, err, types.ErrInvalidParam)
tx1.Signature = nil
err = table.Update(tx1.Hash(), tx1)
assert.Nil(t, err)
kvs, err := table.Save()
assert.Nil(t, err)
assert.Equal(t, len(kvs), 9)
//save to database
setKV(leveldb, kvs)
query := table.GetQuery(kvdb)
rows, err := query.ListIndex("From", []byte(tx1.From()), nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, rows[0].Data.(*types.Transaction).From(), tx1.From())
}
func TestReplace(t *testing.T) {
dir, leveldb, kvdb := getdb()
defer dbclose(dir, leveldb)
opt := &Option{
Prefix: "prefix",
Name: "name",
Primary: "Hash",
Index: []string{"From", "To"},
}
table, err := NewTable(NewTransactionRow(), kvdb, opt)
assert.Nil(t, err)
addr1, priv := util.Genaddress()
tx1 := util.CreateNoneTx(priv)
err = table.Add(tx1)
assert.Nil(t, err)
err = table.Add(tx1)
assert.Equal(t, err, ErrDupPrimaryKey)
//不改变hash,改变签名
tx1.Signature = nil
err = table.Replace(tx1)
assert.Nil(t, err)
//save 然后从列表中读取
kvs, err := table.Save()
assert.Nil(t, err)
assert.Equal(t, len(kvs), 9)
//save to database
setKV(leveldb, kvs)
query := table.GetQuery(kvdb)
_, err = query.ListIndex("From", []byte(addr1[0:10]), nil, 0, 0)
assert.Equal(t, err, types.ErrNotFound)
rows, err := query.ListIndex("From", []byte(tx1.From()), nil, 0, 0)
assert.Nil(t, err)
assert.Equal(t, rows[0].Data.(*types.Transaction).From(), tx1.From())
}
type TransactionRow struct {
*types.Transaction
}
func NewTransactionRow() *TransactionRow {
return &TransactionRow{Transaction: &types.Transaction{}}
}
func (tx *TransactionRow) CreateRow() *Row {
return &Row{Data: &types.Transaction{}}
}
func (tx *TransactionRow) SetPayload(data types.Message) error {
if txdata, ok := data.(*types.Transaction); ok {
tx.Transaction = txdata
return nil
}
return types.ErrTypeAsset
}
func (tx *TransactionRow) Get(key string) ([]byte, error) {
if key == "Hash" {
return tx.Hash(), nil
} else if key == "From" {
return []byte(tx.From()), nil
} else if key == "To" {
return []byte(tx.To), nil
}
return nil, types.ErrNotFound
}
func getdb() (string, db.DB, db.KVDB) {
dir, err := ioutil.TempDir("", "goleveldb")
if err != nil {
panic(err)
}
leveldb, err := db.NewGoLevelDB("goleveldb", dir, 128)
if err != nil {
panic(err)
}
return dir, leveldb, db.NewKVDB(leveldb)
}
func dbclose(dir string, dbm db.DB) {
os.RemoveAll(dir)
dbm.Close()
}
......@@ -105,7 +105,7 @@ func CreateRawTx(cmd *cobra.Command, to string, amount float64, note string, isW
transfer.Ty = cty.CoinsActionTransfer
}
} else {
v := &cty.CoinsAction_Withdraw{Withdraw: &types.AssetsWithdraw{Amount: amountInt64, Note: []byte(note), ExecName: execName}}
v := &cty.CoinsAction_Withdraw{Withdraw: &types.AssetsWithdraw{Amount: amountInt64, Note: []byte(note), ExecName: execName, To: to}}
transfer.Value = v
transfer.Ty = cty.CoinsActionWithdraw
}
......
......@@ -97,7 +97,7 @@ func (mem *Mempool) eventProcess() {
func (mem *Mempool) eventTx(msg queue.Message) {
if !mem.getSync() {
msg.Reply(mem.client.NewMessage("", types.EventReply, &types.Reply{Msg: []byte(types.ErrNotSync.Error())}))
mlog.Error("wrong tx", "err", types.ErrNotSync.Error())
mlog.Debug("wrong tx", "err", types.ErrNotSync.Error())
} else {
checkedMsg := mem.checkTxs(msg)
select {
......
......@@ -111,7 +111,9 @@ func CreateTxWithExecer(priv crypto.PrivKey, execer string) *types.Transaction {
tx := &types.Transaction{Execer: []byte(execer), Payload: []byte("none")}
tx.To = address.ExecAddress(execer)
tx, _ = types.FormatTx(execer, tx)
tx.Sign(types.SECP256K1, priv)
if priv != nil {
tx.Sign(types.SECP256K1, priv)
}
return tx
}
......
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