Skip to content

Instantly share code, notes, and snippets.

@zhaojun-sh
Last active April 26, 2020 16:11
Show Gist options
  • Select an option

  • Save zhaojun-sh/20d909743ceba2cda8a5f5cfde615a1d to your computer and use it in GitHub Desktop.

Select an option

Save zhaojun-sh/20d909743ceba2cda8a5f5cfde615a1d to your computer and use it in GitHub Desktop.
//
// 20200426 v0.10 增加共管账户lockout审批功能
// 20200424 v0.9 增加创建共管账户和审批功能
// 20200421 v0.8 增加建组gid和获取enode签名功能
// 20200409 v0.5 更新reqDcrmAddr接口,mode参数放到data中
// 20200401 v0.4 增加lockout测试功能
// 20200327 v0.3 增加测试结果统计功能,打印出失败的交易KeyID
// 20200325 v0.2 支持循环测试
// 20200324 v0.1 支持发送个人模式申请DCRM地址
//
package main
import (
"crypto/ecdsa"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io/ioutil"
"math/big"
"strconv"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/onrik/ethrpc"
)
const (
KEYFILE = `{"version":3,"id":"16b5e31c-cd1a-4cdc-87a6-fc4164766698","address":"00c37841378920e2ba5151a5d1e074cf367586c4","crypto":{"ciphertext":"2070bf8491759f01b4f3f4d6d4b2e274f105be8dc01edd1ebce8d7d954eb64bd","cipherparams":{"iv":"03263465543e4631db50ecfc6b75a74f"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"9c7b6430552524f0bc1b47bed69e34b0595bc29af4d12e65ec966b16af9c2cf6","n":8192,"r":8,"p":1},"mac":"44d1b7106c28711b06cda116205ee741cba90ab3df0776d59c246b876ded0e97"}}`
DCRM_TO_ADDR = `0x00000000000000000000000000000000000000dc`
CHAIN_ID = 30400 //DCRM_walletService ID
)
type response struct {
Status string `json:"Status"`
Tip string `json:"Tip"`
Error string `json:"Error"`
Data interface{} `json:"Data"`
}
type dataResult struct {
Result string `json:"result"`
}
type dataEnode struct {
Enode string `json:"Enode"`
}
type groupInfo struct {
Gid string `json:"Gid"`
Mode string `json:"Mode"`
Count int `json:"Count"`
Enodes interface{} `json:"Enodes"`
}
type reqAddrData struct {
TxType string `json:"TxType"`
GroupID string `json:"GroupId"`
ThresHold string `json:"ThresHold"`
Mode string `json:"Mode"`
TimeStamp string `json:"TimeStamp"`
Sigs string `json:"Sigs"`
}
type acceptData struct {
TxType string `json:"TxType"`
Key string `json:"Key"`
Accept string `json:"Accept"`
TimeStamp string `json:"TimeStamp"`
}
type lockoutData struct {
TxType string `json:"TxType"`
DcrmAddr string `json:"DcrmAddr"`
DcrmTo string `json:"DcrmTo"`
Value string `json:"Value"`
Cointype string `json:"Cointype"`
GroupID string `json:"GroupId"`
ThresHold string `json:"ThresHold"`
Mode string `json:"Mode"`
TimeStamp string `json:"TimeStamp"`
Memo string `json:"Memo"`
}
type reqAddrStatus struct {
Status string `json:"Status"`
PubKey string `json:"PubKey"`
Tip string `json:"Tip"`
Error string `json:"Error"`
AllReply interface{} `json:"AllReply"`
TimeStamp string `json:"TimeStamp"`
}
type lockoutStatus struct {
Status string `json:"Status"`
OutTxHash string `json:"OutTxHash"`
Tip string `json:"Tip"`
Error string `json:"Error"`
AllReply interface{} `json:"AllReply"`
TimeStamp string `json:"TimeStamp"`
}
// Value Set方法是flag.Value接口, 设置flag Value的方法.
type Value interface {
String() string
Set(string) error
}
type arrayFlags []string
func (i *arrayFlags) String() string {
return fmt.Sprint(*i)
}
// 通过多个flag指定的值, 所以我们追加到最终的数组上.
func (i *arrayFlags) Set(value string) error {
*i = append(*i, value)
return nil
}
func main() {
keyfile := flag.String("keystore", "", "Keystore file")
passwd := flag.String("passwd", "111111", "Password")
url := flag.String("url", "http://47.56.150.104:8087", "Set node RPC URL")
n := flag.Int("n", 1, "Test number")
t := flag.Int("t", 1, "Duration")
test := flag.String("test", "REQDCRMADDR", "REQDCRMADDR/LOCKOUT/EnodeSig/SetGroup/ACCEPTREQADDR/ACCEPTLOCKOUT")
gid := flag.String("gid", "", "groupID")
ts := flag.String("ts", "2/3", "Threshold")
mode := flag.String("mode", "1", "Mode:private=1/managed=0")
toAddr := flag.String("to", "0x0520e8e5E08169c4dbc1580Dc9bF56638532773A", "To address")
value := flag.String("value", "10000000000000000", "lockout value")
coin := flag.String("coin", "FSN", "Coin type")
fromAddr := flag.String("from", "", "From address")
memo := flag.String("memo", "smpcwallet.com", "Memo")
accept := flag.String("accept", "AGREE", "AGREE/DISAGREE")
key := flag.String("key", "", "Accept key")
var enodesSig, nodes arrayFlags
flag.Var(&enodesSig, "sig", "Enodes Sig list")
flag.Var(&nodes, "node", "Node rpc url")
flag.Parse()
// To账户
toAccDef := accounts.Account{
Address: common.HexToAddress(DCRM_TO_ADDR),
}
fmt.Println("To address: = ", toAccDef.Address.String())
// 解析私钥文件
var keyjson []byte
if *keyfile != "" {
var err error
keyjson, err = ioutil.ReadFile(*keyfile)
if err != nil {
fmt.Println("Read keystore fail", err)
}
} else {
keyjson = []byte(KEYFILE)
}
keyWrapper, keyErr := keystore.DecryptKey(keyjson, *passwd)
if keyErr != nil {
fmt.Println("Key decrypt error:")
panic(keyErr)
}
privateKey := hex.EncodeToString(keyWrapper.PrivateKey.D.Bytes())
fmt.Printf("PrivateKey = %s\n", privateKey)
fmt.Printf("Recover from address = %s\n", keyWrapper.Address.String())
// 设置 signer and chain id
chainID := big.NewInt(CHAIN_ID)
signer := types.NewEIP155Signer(chainID)
// 初始化RPC客户端
client := ethrpc.New(*url)
fmt.Printf("HTTP-RPC client connected\n")
enodeRep, err := client.Call("dcrm_getEnode")
if err != nil {
panic(err)
}
fmt.Printf("getEnode = %s\n\n", enodeRep)
// 获取签名后的enode
if *test == "EnodeSig" {
var enodeJSON dataEnode
enodeData, _ := getJSONData(enodeRep)
if err := json.Unmarshal(enodeData, &enodeJSON); err != nil {
panic(err)
}
fmt.Printf("enode = %s\n", enodeJSON.Enode)
// 提取enode的pubkey
s := strings.Split(enodeJSON.Enode, "@")
enodePubkey := strings.Split(s[0], "//")
fmt.Printf("enodePubkey = %s\n", enodePubkey[1])
sig, err := crypto.Sign(crypto.Keccak256([]byte(enodePubkey[1])), keyWrapper.PrivateKey)
if err != nil {
panic(err)
}
fmt.Printf("\nenodeSig self = \n%s\n\n", enodeJSON.Enode+common.ToHex(sig))
// 建组获取GID
} else if *test == "SetGroup" {
var enodeList []string
// 通过-sig参数的enodesSig签名中获取enode列表建组
if len(enodesSig) > 0 {
enodeList = make([]string, len(enodesSig))
for i := 0; i < len(enodesSig); i++ {
s := strings.Split(enodesSig[i], "0x")
enodeList[i] = s[0]
fmt.Printf("enode[%d] = %s\n", i, enodeList[i])
}
// 通过-node参数的节点rpc地址获取enode列表建组
} else if len(nodes) > 0 {
enodeList = make([]string, len(nodes))
for i := 0; i < len(nodes); i++ {
client := ethrpc.New(nodes[i])
enodeRep, err := client.Call("dcrm_getEnode")
if err != nil {
panic(err)
}
var enodeJSON dataEnode
enodeData, _ := getJSONData(enodeRep)
if err := json.Unmarshal(enodeData, &enodeJSON); err != nil {
panic(err)
}
enodeList[i] = enodeJSON.Enode
fmt.Printf("enode[%d] = %s\n", i, enodeList[i])
}
}
// 发送建组请求获取gid
groupRep, err := client.Call("dcrm_createGroup", *ts, enodeList)
if err != nil {
panic(err)
}
fmt.Printf("dcrm_createGroup = %s\n", groupRep)
var groupJSON groupInfo
groupData, _ := getJSONData(groupRep)
if err := json.Unmarshal(groupData, &groupJSON); err != nil {
panic(err)
}
fmt.Printf("\nGid = %s\n\n", groupJSON.Gid)
// 创建DCRM账户
} else if *test == "REQDCRMADDR" {
// 获取 nonce
reqAddrNonce, err := client.Call("dcrm_getReqAddrNonce", keyWrapper.Address.String())
if err != nil {
panic(err)
}
nonceStr, _ := getJSONResult(reqAddrNonce)
nonce, _ := strconv.ParseUint(nonceStr, 0, 64)
fmt.Printf("dcrm_getReqAddrNonce = %s\nNonce = %d\n", reqAddrNonce, nonce)
// 构建Sigs签名列表参数
sigs := ""
if *mode == "0" {
for i := 0; i < len(enodesSig)-1; i++ {
sigs = sigs + enodesSig[i] + "|"
}
sigs = sigs + enodesSig[len(enodesSig)-1]
}
// 循环测试n次
keyIDList := make([]string, *n)
for j := 0; j < *n; j++ {
// 构建交易的data参数
timestamp := strconv.FormatInt((time.Now().UnixNano() / 1e6), 10)
txdata := reqAddrData{
TxType: *test,
GroupID: *gid,
ThresHold: *ts,
Mode: *mode,
TimeStamp: timestamp,
Sigs: sigs,
}
playload, _ := json.Marshal(txdata)
// 签名交易
rawTX, err := signTX(signer, keyWrapper.PrivateKey, nonce, playload)
if err != nil {
panic(err)
}
nonce++
// 发送申请地址rawTx至节点rpc
reqKeyID, err := client.Call("dcrm_reqDcrmAddr", rawTX)
if err != nil {
panic(err)
}
// 获取返回的keyID
keyID, err := getJSONResult(reqKeyID)
if err != nil {
panic(err)
}
fmt.Printf("\ndcrm_reqDcrmAddr keyID = %s\n\n", keyID)
keyIDList[j] = keyID
time.Sleep(time.Duration(*t) * time.Second)
}
fmt.Printf("\nWaiting for stats result...\n")
// 获取已生成所有账户列表
time.Sleep(time.Duration(20) * time.Second)
accounts, err := client.Call("dcrm_getAccounts", keyWrapper.Address.String(), *mode)
if err != nil {
panic(err)
}
fmt.Printf("\naddress = %s\naccounts = %s\n\n", keyWrapper.Address.String(), accounts)
// 根据keyID列表遍历查询申请地址失败的key
time.Sleep(time.Duration(2) * time.Second)
var statusJSON reqAddrStatus
var i, j, k int
for i, j, k = 0, 0, 0; j < *n; j++ {
reqStatus, err := client.Call("dcrm_getReqAddrStatus", keyIDList[j])
if err != nil {
fmt.Println("\ndcrm_getReqAddrStatus rpc error:", err)
continue
}
statusJSONStr, err := getJSONResult(reqStatus)
if err != nil {
i++
fmt.Printf("\n%d\tdcrm_getReqAddrStatus=NotStart\tkeyID=%s ", i, keyIDList[j])
fmt.Println("\tRequest not complete:", err)
continue
}
if err := json.Unmarshal([]byte(statusJSONStr), &statusJSON); err != nil {
fmt.Println("\tUnmarshal statusJSONStr fail:", err)
continue
}
if statusJSON.Status != "Success" {
i++
fmt.Printf("\n%d\tdcrm_getReqAddrStatus=%s\tkeyID=%s ", i, statusJSON.Status, keyIDList[j])
} else {
k++
fmt.Printf("Success=%d\tPubkey=%s\n", k, statusJSON.PubKey)
}
time.Sleep(time.Duration(100) * time.Millisecond)
}
// reqAddr统计结果
fmt.Printf("\nUser=%s request DCRM address Rate of Success %d / %d\n", keyWrapper.Address.String(), k, *n)
// 审批创建共管账户
} else if *test == "ACCEPTREQADDR" {
// 获取创建共管账户审批列表
reqListRep, err := client.Call("dcrm_getCurNodeReqAddrInfo", keyWrapper.Address.String())
if err != nil {
panic(err)
}
reqListJSON, _ := getJSONData(reqListRep)
fmt.Printf("dcrm_getCurNodeReqAddrInfo = %s\n", reqListJSON)
// todo 生成未审批的key列表,自动审核通过发送同意审批交易。代替-key参数手动传入
for i := 0; i < 1; i++ {
// 构建交易的data参数
timestamp := strconv.FormatInt((time.Now().UnixNano() / 1e6), 10)
data := acceptData{
TxType: *test,
Key: *key,
Accept: *accept,
TimeStamp: timestamp,
}
playload, err := json.Marshal(data)
if err != nil {
fmt.Println("error:", err)
}
// 签名交易
rawTX, err := signTX(signer, keyWrapper.PrivateKey, 0, playload)
if err != nil {
panic(err)
}
// 发送申请地址rawTx至节点rpc
acceptReqAddrRep, err := client.Call("dcrm_acceptReqAddr", rawTX)
if err != nil {
panic(err)
}
fmt.Printf("\ndcrm_acceptReqAddr = %s\n\n", acceptReqAddrRep)
// 获取返回的结果
reqRet, err := getJSONResult(acceptReqAddrRep)
if err != nil {
panic(err)
}
fmt.Printf("\ndcrm_acceptReq result = %s\n\n", reqRet)
}
// 测试发起交易
} else if *test == "LOCKOUT" {
// 获取 lockout nonce
lockoutNonce, err := client.Call("dcrm_getLockOutNonce", keyWrapper.Address.String())
if err != nil {
panic(err)
}
nonceStr, err := getJSONResult(lockoutNonce)
if err != nil {
panic(err)
}
nonce, _ := strconv.ParseUint(nonceStr, 0, 64)
fmt.Printf("dcrm_getLockOutNonce = %s\nNonce = %d\n", lockoutNonce, nonce)
// 循环测试n次
keyIDList := make([]string, *n)
for j := 0; j < *n; j++ {
// 构建交易的data参数
timestamp := strconv.FormatInt((time.Now().UnixNano() / 1e6), 10)
txdata := lockoutData{
TxType: *test,
DcrmAddr: *fromAddr,
DcrmTo: *toAddr,
Value: *value,
Cointype: *coin,
GroupID: *gid,
ThresHold: *ts,
Mode: *mode,
TimeStamp: timestamp,
Memo: *memo,
}
playload, _ := json.Marshal(txdata)
// 签名交易
rawTX, err := signTX(signer, keyWrapper.PrivateKey, nonce, playload)
if err != nil {
panic(err)
}
nonce++
// 发送申请地址rawTx至节点rpc
reqKeyID, err := client.Call("dcrm_lockOut", rawTX)
if err != nil {
panic(err)
}
// 获取返回的keyID
keyID, err := getJSONResult(reqKeyID)
if err != nil {
panic(err)
}
fmt.Printf("\ndcrm_lockOut keyID = %s\n\n", keyID)
keyIDList[j] = keyID
time.Sleep(time.Duration(*t) * time.Second)
}
fmt.Printf("\nWaiting for stats result...\n")
// lockout根据keyID列表遍历查询申请地址失败的key
time.Sleep(time.Duration(30) * time.Second)
var statusJSON lockoutStatus
var i, j, k int
for i, j, k = 0, 0, 0; j < *n; j++ {
reqStatus, err := client.Call("dcrm_getLockOutStatus", keyIDList[j])
if err != nil {
fmt.Println("\ndcrm_getLockOutStatus rpc error:", err)
continue
}
statusJSONStr, err := getJSONResult(reqStatus)
if err != nil {
i++
fmt.Printf("\n%d\tdcrm_getLockOutStatus=NotStart\tkeyID=%s ", i, keyIDList[j])
fmt.Println("\tRequest not complete:", err)
continue
}
if err := json.Unmarshal([]byte(statusJSONStr), &statusJSON); err != nil {
fmt.Println("\tUnmarshal statusJSONStr fail:", err)
continue
}
if statusJSON.Status != "Success" {
i++
fmt.Printf("\n%d\tdcrm_getLockOutStatus=%s\tkeyID=%s ", i, statusJSON.Status, keyIDList[j])
} else {
k++
fmt.Printf("\nSuccess=%d\tOutTXhash=%s", k, statusJSON.OutTxHash)
}
time.Sleep(time.Duration(100) * time.Millisecond)
}
// lockout统计结果
fmt.Printf("\n\nUser=%s dcrm_LockOut Rate of Success %d / %d\n", keyWrapper.Address.String(), k, *n)
// 审批共管账户发起交易
} else if *test == "ACCEPTLOCKOUT" {
// 获取共管账户lockout审批列表
reqListRep, err := client.Call("dcrm_getCurNodeLockOutInfo", keyWrapper.Address.String())
if err != nil {
panic(err)
}
reqListJSON, _ := getJSONData(reqListRep)
fmt.Printf("dcrm_getCurNodeLockOutInfo = %s\n", reqListJSON)
// todo 生成未审批的key列表,自动审核通过发送同意审批交易。代替-key参数手动传入
for i := 0; i < 1; i++ {
// 构建交易的data参数
timestamp := strconv.FormatInt((time.Now().UnixNano() / 1e6), 10)
data := acceptData{
TxType: *test,
Key: *key,
Accept: *accept,
TimeStamp: timestamp,
}
playload, err := json.Marshal(data)
if err != nil {
fmt.Println("error:", err)
}
// 签名交易
rawTX, err := signTX(signer, keyWrapper.PrivateKey, 0, playload)
if err != nil {
panic(err)
}
// 发送rawTx至节点rpc
acceptLockOutRep, err := client.Call("dcrm_acceptLockOut", rawTX)
if err != nil {
panic(err)
}
fmt.Printf("\ndcrm_acceptLockOut = %s\n\n", acceptLockOutRep)
// 获取返回的结果
acceptRet, err := getJSONResult(acceptLockOutRep)
if err != nil {
panic(err)
}
fmt.Printf("\ndcrm_acceptLockOut result = %s\n\n", acceptRet)
}
}
}
// 解析rpc返回的data中result数据
func getJSONResult(successResponse json.RawMessage) (string, error) {
var data dataResult
repData, err := getJSONData(successResponse)
if err != nil {
return "", err
}
if err := json.Unmarshal(repData, &data); err != nil {
fmt.Println("getJSONResult Unmarshal json fail:", err)
return "", err
}
return data.Result, nil
}
func getJSONData(successResponse json.RawMessage) ([]byte, error) {
var rep response
if err := json.Unmarshal(successResponse, &rep); err != nil {
fmt.Println("getJSONData Unmarshal json fail:", err)
return nil, err
}
if rep.Status != "Success" {
return nil, errors.New(rep.Error)
}
repData, err := json.Marshal(rep.Data)
if err != nil {
fmt.Println("getJSONData Marshal json fail:", err)
return nil, err
}
return repData, nil
}
// 签名交易返回raw的十六进制字符串
func signTX(signer types.EIP155Signer, privatekey *ecdsa.PrivateKey, nonce uint64, playload []byte) (string, error) {
toAccDef := accounts.Account{
Address: common.HexToAddress(DCRM_TO_ADDR),
}
// 建立交易
tx := types.NewTransaction(
uint64(nonce), // nonce
toAccDef.Address, // to address
big.NewInt(0), // value
100000, // gasLimit
big.NewInt(80000), // gasPrice
playload) // data
// 用私钥签署交易签名
signature, signatureErr := crypto.Sign(signer.Hash(tx).Bytes(), privatekey)
if signatureErr != nil {
fmt.Println("signature create error:")
panic(signatureErr)
}
// 签名后构建完整交易
sigTx, signErr := tx.WithSignature(signer, signature)
if signErr != nil {
fmt.Println("signer with signature error:")
panic(signErr)
}
// 获取raw TX
txdata, txerr := rlp.EncodeToBytes(sigTx)
if txerr != nil {
panic(txerr)
}
rawTX := common.ToHex(txdata)
fmt.Printf("\nSignTx:\nChainId\t\t=%s\nGas\t\t=%d\nGasPrice\t=%s\nNonce\t\t=%d\nToAddr\t\t=%s\nHash\t\t=%s\nData\t\t=%s\n",
sigTx.ChainId(), sigTx.Gas(), sigTx.GasPrice(), sigTx.Nonce(), sigTx.To().String(), sigTx.Hash().Hex(), sigTx.Data())
fmt.Printf("RawTransaction = %+v\n", rawTX)
return rawTX, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment