├── example ├── contract │ ├── data │ │ ├── keys │ │ │ ├── address │ │ │ ├── public.key │ │ │ └── private.key │ │ └── counter.wasm │ └── contract.go ├── event │ └── event.go ├── query │ └── query.go ├── account │ └── account.go ├── transfer │ └── transfer.go ├── acl │ └── acl.go └── multisig │ └── multisig.go ├── .gitignore ├── Makefile ├── common ├── txhash.go ├── error.go ├── config │ ├── config_test.go │ └── config.go ├── common_test.go └── common.go ├── go.mod ├── xuper ├── event_test.go ├── acl_test.go ├── acl.go ├── transaction_test.go ├── transaction.go ├── options.go ├── event.go ├── query.go ├── request.go ├── xuperclient.go ├── proposal_test.go ├── proposal.go └── xuperclient_test.go ├── conf ├── sdk.yaml └── sdk.testnet.yaml ├── crypto └── client.go ├── account ├── address_trans_test.go ├── account_test.go ├── account.go └── address_trans.go ├── README.md └── LICENSE /example/contract/data/keys/address: -------------------------------------------------------------------------------- 1 | TeyyPLpp9L7QAcxHangtcHTu7HUZ6iydY -------------------------------------------------------------------------------- /example/contract/data/counter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xuperchain/xuper-sdk-go/HEAD/example/contract/data/counter.wasm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE related 2 | .vscode/ 3 | .idea 4 | 5 | # output files 6 | output/ 7 | /.project 8 | /.gitignore 9 | 10 | # test 11 | coverage.* 12 | 13 | .DS_Store -------------------------------------------------------------------------------- /example/contract/data/keys/public.key: -------------------------------------------------------------------------------- 1 | {"Curvname":"P-256","X":36505150171354363400464126431978257855318414556425194490762274938603757905292,"Y":79656876957602994269528255245092635964473154458596947290316223079846501380076} -------------------------------------------------------------------------------- /example/contract/data/keys/private.key: -------------------------------------------------------------------------------- 1 | {"Curvname":"P-256","X":36505150171354363400464126431978257855318414556425194490762274938603757905292,"Y":79656876957602994269528255245092635964473154458596947290316223079846501380076,"D":111497060296999106528800133634901141644446751975433315540300236500052690483486} -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | PLATFORM="Windows" 3 | else 4 | ifeq ($(shell uname),Darwin) 5 | PLATFORM="MacOS" 6 | else 7 | PLATFORM="Linux" 8 | endif 9 | endif 10 | 11 | all: test 12 | export GO111MODULE=on 13 | export OUTPUT=./output 14 | 15 | test: 16 | go test -race -coverprofile=coverage.txt -covermode=atomic ./account/... ./common/... ./xuper/... 17 | go tool cover -html=coverage.txt -o coverage.html 18 | 19 | .PHONY: test 20 | -------------------------------------------------------------------------------- /common/txhash.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/xuperchain/xuperchain/service/common" 5 | "github.com/xuperchain/xuperchain/service/pb" 6 | ) 7 | 8 | // MakeTransactionID 计算交易ID,包括签名。 9 | func MakeTransactionID(tx *pb.Transaction) ([]byte, error) { 10 | return common.MakeTxId(tx) 11 | } 12 | 13 | // MakeTxDigestHash 计算交易哈希,不包括签名。 14 | func MakeTxDigestHash(tx *pb.Transaction) ([]byte, error) { 15 | return common.MakeTxDigestHash(tx) 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xuperchain/xuper-sdk-go/v2 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d 7 | github.com/golang/protobuf v1.4.2 8 | github.com/hyperledger/burrow v0.30.5 9 | github.com/pkg/errors v0.9.1 10 | github.com/xuperchain/crypto v0.0.0-20211221122406-302ac826ac90 11 | github.com/xuperchain/xuperchain v0.0.0-20210708031936-951e4ade7bdd 12 | google.golang.org/grpc v1.33.1 13 | gopkg.in/yaml.v2 v2.3.0 14 | ) 15 | -------------------------------------------------------------------------------- /xuper/event_test.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEventOpts(t *testing.T) { 8 | xclient := &XClient{} 9 | watcher, err := xclient.newWatcher( 10 | WithBlockEventBcname("xuper"), 11 | WithAuthRequire("a"), 12 | WithBlockRange("1", "10"), 13 | WithContract("counter"), 14 | WithEventName("event"), 15 | WithExcludeTx(true), 16 | WithExcludeTxEvent(true), 17 | WithFromAddr("a"), 18 | WithInitiator("a"), 19 | WithToAddr("b")) 20 | if err != nil { 21 | t.Error(err) 22 | } 23 | if watcher.opt.blockFilter.AuthRequire != "a" || 24 | watcher.opt.blockFilter.Contract != "counter" { 25 | t.Error("Event opts assert failed") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /conf/sdk.yaml: -------------------------------------------------------------------------------- 1 | # endorseService Info 2 | # testNet addrs 3 | endorseServiceHost: "39.156.69.83:37100" 4 | complianceCheck: 5 | # 是否需要进行合规性背书 6 | isNeedComplianceCheck: false 7 | # 是否需要支付合规性背书费用 8 | isNeedComplianceCheckFee: false 9 | # 合规性背书费用 10 | complianceCheckEndorseServiceFee: 400 11 | # 支付合规性背书费用的收款地址 12 | complianceCheckEndorseServiceFeeAddr: aB2hpHnTBDxko3UoP2BpBZRujwhdcAFoT 13 | # 如果通过合规性检查,签发认证签名的地址 14 | complianceCheckEndorseServiceAddr: jknGxa6eyum1JrATWvSJKW3thJ9GKHA9n 15 | #创建平行链所需要的最低费用 16 | minNewChainAmount: "100" 17 | crypto: "xchain" 18 | txVersion: 1 19 | # maxRecvMsgSize set the max message size in bytes the server can receive. 20 | # If this is not set, gRPC uses the default 4MB. 21 | maxRecvMsgSize: 134217728 22 | -------------------------------------------------------------------------------- /conf/sdk.testnet.yaml: -------------------------------------------------------------------------------- 1 | # endorseService Info 2 | # testNet addrs 3 | endorseServiceHost: "14.215.183.139:37101" 4 | complianceCheck: 5 | # 是否需要进行合规性背书 6 | isNeedComplianceCheck: true 7 | # 是否需要支付合规性背书费用 8 | isNeedComplianceCheckFee: true 9 | # 合规性背书费用 10 | complianceCheckEndorseServiceFee: 100 11 | # 支付合规性背书费用的收款地址 12 | complianceCheckEndorseServiceFeeAddr: cHvBK1TTB52GYtVxHK7HnW8N9RTqkN99R 13 | # 如果通过合规性检查,签发认证签名的地址 14 | complianceCheckEndorseServiceAddr: XDxkpQkfLwG6h56e896f3vBHhuN5g6M9u 15 | #创建平行链所需要的最低费用 16 | minNewChainAmount: "100" 17 | crypto: "xchain" 18 | txVersion: 1 19 | # maxRecvMsgSize set the max message size in bytes the server can receive. 20 | # If this is not set, gRPC uses the default 4MB. 21 | maxRecvMsgSize: 134217728 22 | -------------------------------------------------------------------------------- /example/event/event.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 8 | ) 9 | 10 | func main() { 11 | testEvent() 12 | } 13 | 14 | func testEvent() error { 15 | // 创建节点客户端。 16 | client, err := xuper.New("127.0.0.1:37101") 17 | if err != nil { 18 | return err 19 | } 20 | 21 | // 监听时间,返回 Watcher,通过 Watche 中的 channel 获取block。 22 | watcher, err := client.WatchBlockEvent(xuper.WithSkipEmplyTx()) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | go func() { 28 | for { 29 | b, ok := <-watcher.FilteredBlockChan 30 | if !ok { 31 | fmt.Println("watch block event channel closed.") 32 | return 33 | } 34 | fmt.Printf("%+v\n", b) 35 | } 36 | }() 37 | 38 | time.Sleep(time.Second * 3) 39 | 40 | // 关闭监听。 41 | watcher.Close() 42 | client.Close() 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /xuper/acl_test.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import "testing" 4 | 5 | func TestAcl(t *testing.T) { 6 | acl := NewACL(1, 1.0) 7 | if acl.PM.AcceptValue != 1 || acl.PM.Rule != 1.0 { 8 | t.Error("Acl assert failed") 9 | } 10 | 11 | acl.AddAK("a", 1.0) 12 | if len(acl.AksWeight) != 1 { 13 | t.Error("Acl AddAK assert failed") 14 | } 15 | 16 | if v, ok := acl.AksWeight["a"]; !ok { 17 | t.Error("Acl AddAK assert failed") 18 | } else if v != 1.0 { 19 | t.Error("Acl AddAK assert failed") 20 | } 21 | 22 | defaultACL := getDefaultACL("bob") 23 | if v, ok := defaultACL.AksWeight["bob"]; !ok { 24 | t.Error("Acl AddAK assert failed") 25 | } else if v != 1.0 { 26 | t.Error("Acl AddAK assert failed") 27 | } 28 | 29 | acl1 := &ACL{} 30 | acl1.AddAK("aa", 1.0) 31 | if w, _ := acl1.AksWeight["aa"]; w != 1.0 { 32 | t.Error("Acl AddAK assert failed") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /common/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // Package common is related to common variables and utils funcs 4 | package common 5 | 6 | import ( 7 | "errors" 8 | ) 9 | 10 | var ( 11 | // ErrInvalidAmount invalid amount 12 | ErrInvalidAmount = errors.New("invalid amount") 13 | // ErrTxNotFound tx is not found 14 | ErrTxNotFound = errors.New("tx not found") 15 | // ErrInvalidAccount invalid account 16 | ErrInvalidAccount = errors.New("invalid account") 17 | // ErrInvalidContractAccount contract account invalid 18 | ErrInvalidContractAccount = errors.New("conrtact account must be numbers of length 16") 19 | // ErrAmountNotEnough amount invalid 20 | ErrAmountNotEnough = errors.New("Amount must be bigger than compliancecheck fee which is 10") 21 | //ErrInvalidInitiator from account invalid 22 | ErrInvalidInitiator = errors.New("From account can not be nil") 23 | // ErrInvalidParam param invalid 24 | ErrInvalidParam = errors.New("Parmeter invalid") 25 | ) 26 | -------------------------------------------------------------------------------- /xuper/acl.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | // ACL acl. 4 | type ACL struct { 5 | PM PermissionModel `json:"pm"` 6 | AksWeight map[string]float64 `json:"aksWeight"` 7 | } 8 | 9 | // PermissionModel acl permission model. 10 | type PermissionModel struct { 11 | Rule int32 `json:"rule"` 12 | AcceptValue float64 `json:"acceptValue"` 13 | } 14 | 15 | // NewACL new ACl instance. 16 | func NewACL(rule int32, acceptValue float64) *ACL { 17 | return &ACL{ 18 | PM: PermissionModel{ 19 | Rule: rule, 20 | AcceptValue: acceptValue, 21 | }, 22 | AksWeight: map[string]float64{}, 23 | } 24 | } 25 | 26 | // AddAK add ak and weight pair. 27 | func (a *ACL) AddAK(ak string, weight float64) { 28 | if a.AksWeight == nil { 29 | a.AksWeight = make(map[string]float64, 1) 30 | } 31 | a.AksWeight[ak] = weight 32 | } 33 | 34 | func getDefaultACL(address string) *ACL { 35 | return &ACL{ 36 | PM: PermissionModel{ 37 | Rule: 1, 38 | AcceptValue: 1.0, 39 | }, 40 | AksWeight: map[string]float64{ 41 | address: 1.0, 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crypto/client.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // Package crypto is related to generate crypto client 4 | package crypto 5 | 6 | import ( 7 | "github.com/xuperchain/crypto/client/service/base" 8 | "github.com/xuperchain/crypto/client/service/gm" 9 | "github.com/xuperchain/crypto/client/service/xchain" 10 | "github.com/xuperchain/xuper-sdk-go/v2/common/config" 11 | ) 12 | 13 | func getInstance() interface{} { 14 | switch config.GetInstance().Crypto { 15 | case config.CRYPTO_XCHAIN: 16 | return &xchain.XchainCryptoClient{} 17 | case config.CRYPTO_GM: 18 | return &gm.GmCryptoClient{} 19 | default: 20 | return &xchain.XchainCryptoClient{} 21 | } 22 | } 23 | 24 | // GetCryptoClient get crypto client 25 | func GetCryptoClient() base.CryptoClient { 26 | cryptoClient := getInstance().(base.CryptoClient) 27 | return cryptoClient 28 | } 29 | 30 | // GetXchainCryptoClient get xchain crypto client 31 | func GetXchainCryptoClient() *xchain.XchainCryptoClient { 32 | return &xchain.XchainCryptoClient{} 33 | } 34 | 35 | // GetGmCryptoClient get gm crypto client 36 | func GetGmCryptoClient() *gm.GmCryptoClient { 37 | return &gm.GmCryptoClient{} 38 | } 39 | -------------------------------------------------------------------------------- /common/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConfig(t *testing.T) { 8 | c := GetInstance() 9 | if c == nil { 10 | t.Error("GetInstance test failed") 11 | } 12 | 13 | c.SetGMCrypto() 14 | if c.Crypto != CRYPTO_GM { 15 | t.Error("GetInstance test failed") 16 | } 17 | c.SetXchainCrypto() 18 | if c.Crypto != CRYPTO_XCHAIN { 19 | t.Error("GetInstance test failed") 20 | } 21 | } 22 | 23 | func TestSetConfig(t *testing.T) { 24 | SetConfig("a", "b", "c", "1", true, true, "1") 25 | c := GetInstance() 26 | if c == nil { 27 | t.Error("GetInstance test failed") 28 | } 29 | 30 | if c.EndorseServiceHost != "a" { 31 | t.Error("SetConfig check host failed") 32 | } 33 | 34 | if c.ComplianceCheck.ComplianceCheckEndorseServiceAddr != "b" { 35 | t.Error("SetConfig check ComplianceCheckEndorseServiceAddr failed") 36 | } 37 | 38 | if c.ComplianceCheck.ComplianceCheckEndorseServiceFeeAddr != "c" { 39 | t.Error("SetConfig check ComplianceCheckEndorseServiceFeeAddr failed") 40 | } 41 | 42 | if c.ComplianceCheck.ComplianceCheckEndorseServiceFee != 1 { 43 | t.Error("SetConfig check ComplianceCheckEndorseServiceFee failed") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /account/address_trans_test.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTrans(t *testing.T) { 8 | type Case struct { 9 | XchainAddress string 10 | Type string 11 | EVMAddress string 12 | } 13 | 14 | cases := []Case{ 15 | { 16 | XchainAddress: "XC1111111111111113@xuper", 17 | Type: "contract-account", 18 | EVMAddress: "3131313231313131313131313131313131313133", 19 | }, 20 | { 21 | XchainAddress: "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN", 22 | Type: "xchain", 23 | EVMAddress: "93F86A462A3174C7AD1281BCF400A9F18D244E06", 24 | }, 25 | { 26 | XchainAddress: "storagedata11", 27 | Type: "contract-name", 28 | EVMAddress: "313131312D2D2D73746F72616765646174613131", 29 | }, 30 | } 31 | 32 | for _, c := range cases { 33 | evmAddr, addrType, err := XchainToEVMAddress(c.XchainAddress) 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | Assert(c.EVMAddress, evmAddr, t) 38 | Assert(c.Type, addrType, t) 39 | 40 | xAddr, addrType, err := EVMToXchainAddress(c.EVMAddress) 41 | if err != nil { 42 | t.Error(err) 43 | } 44 | Assert(c.XchainAddress, xAddr, t) 45 | Assert(c.Type, addrType, t) 46 | } 47 | } 48 | 49 | func Assert(expect, acture string, t *testing.T) { 50 | if expect != acture { 51 | t.Errorf("expect: %s, acture: %s.", expect, acture) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /xuper/transaction_test.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/xuperchain/xuper-sdk-go/v2/account" 7 | "github.com/xuperchain/xuperchain/service/pb" 8 | ) 9 | 10 | func TestTransaction(t *testing.T) { 11 | // acc, _ := account.CreateAccount(1, 1) 12 | acc1, _ := account.CreateAccount(1, 1) 13 | 14 | type Case struct { 15 | hasHash bool 16 | signAcc *account.Account 17 | hasErr bool 18 | inAuthRequire bool 19 | } 20 | 21 | cases := []*Case{ 22 | { 23 | hasHash: true, 24 | signAcc: nil, 25 | hasErr: true, 26 | inAuthRequire: true, 27 | }, 28 | { 29 | hasHash: false, 30 | signAcc: acc1, 31 | hasErr: false, 32 | inAuthRequire: true, 33 | }, 34 | { 35 | hasHash: true, 36 | signAcc: acc1, 37 | hasErr: true, 38 | inAuthRequire: false, 39 | }, 40 | } 41 | 42 | for _, c := range cases { 43 | tx := &Transaction{ 44 | Tx: &pb.Transaction{}, 45 | } 46 | if c.hasHash { 47 | tx.DigestHash = []byte("haha") 48 | } 49 | 50 | if c.inAuthRequire { 51 | tx.Tx.AuthRequire = append(tx.Tx.AuthRequire, acc1.GetAuthRequire()) 52 | } 53 | 54 | err := tx.Sign(c.signAcc) 55 | if c.hasErr { 56 | if err == nil { 57 | t.Error("Transactions assert failed1") 58 | } 59 | } else { 60 | if err != nil { 61 | t.Error("Transactions assert failed2") 62 | } 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /example/query/query.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 7 | ) 8 | 9 | func main() { 10 | // 查询接口有一些返回的是 xuperchain 项目中的 pb 结构,有的是返回 sdk 中的结构。 11 | // 对于返回 xuperchain 中的 pb 结构,可能会带有 Header,需要先判断 Header 中的 error。 12 | // 示例如下: 13 | queryBlockByHeight() 14 | 15 | // 查询余额时,可以指定链名,如果没有创建平行链,默认使用 xuper。 16 | queryBalance() 17 | } 18 | 19 | func queryBlockByHeight() { 20 | // 示例代码省略了 err 的检查。 21 | node := "127.0.0.1" 22 | xclient, _ := xuper.New(node) 23 | blockResult, _ := xclient.QueryBlockByHeight(8) 24 | if blockResult.GetHeader().GetError() != 0 { 25 | // 处理错误。 26 | } else { 27 | // 处理区块数据。 28 | block := blockResult.GetBlock() 29 | fmt.Println(block.GetBlockid()) 30 | fmt.Println(block.GetHeight()) 31 | fmt.Println(block.GetTxCount()) 32 | } 33 | } 34 | 35 | func queryBalance() { 36 | // 示例代码省略了 err 的检查。 37 | node := "127.0.0.1" 38 | xclient, _ := xuper.New(node) 39 | 40 | // 查询账户余额,默认 xuper 链。 41 | bal, _ := xclient.QueryBalance("nuSMPvo6UUoTaT8mMQmHbfiRbJNbAymGh") 42 | fmt.Println(bal) 43 | 44 | // 查询账户余额,在 hello 链。 45 | bal, _ = xclient.QueryBalance("nuSMPvo6UUoTaT8mMQmHbfiRbJNbAymGh", xuper.WithQueryBcname("hello")) 46 | fmt.Println(bal) 47 | 48 | // 查询账户余额详细数据 49 | balDetails, _ := xclient.QueryBalanceDetail("nuSMPvo6UUoTaT8mMQmHbfiRbJNbAymGh") 50 | for _, bd := range balDetails { 51 | fmt.Println(bd.Balance) 52 | fmt.Println(bd.IsFrozen) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /common/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // package common is related to common variables and utils funcs 4 | package common 5 | 6 | import ( 7 | "os" 8 | "testing" 9 | 10 | "github.com/xuperchain/xuperchain/service/pb" 11 | ) 12 | 13 | func TestIsValidAmount(t *testing.T) { 14 | testCase := []string{ 15 | "", 16 | "0", 17 | "345", 18 | "-345", 19 | "-34fdsafds5", 20 | } 21 | 22 | for _, arg := range testCase { 23 | amount, ok := IsValidAmount(arg) 24 | t.Logf("amount: %v, err: %v", amount, ok) 25 | } 26 | } 27 | 28 | func TestSeed(t *testing.T) { 29 | err := SetSeed() 30 | if err != nil { 31 | t.Error("SetSeed assert failed") 32 | } 33 | n := GetNonce() 34 | if n == "" { 35 | t.Error("GetNonce assert failed") 36 | } 37 | 38 | p := "" 39 | err = PathExistsAndMkdir(p) 40 | if err == nil { 41 | t.Error("PathExistsAndMkdir assert failed") 42 | } 43 | 44 | p = "./tmp" 45 | err = PathExistsAndMkdir(p) 46 | if err != nil { 47 | t.Error("PathExistsAndMkdir assert failed") 48 | } 49 | os.RemoveAll(p) 50 | } 51 | 52 | func TestVaildAmount(t *testing.T) { 53 | 54 | _, ok := IsValidAmount("a") 55 | if ok { 56 | t.Error("TestVaildAmount assert failed") 57 | } 58 | _, ok = IsValidAmount("-100") 59 | if ok { 60 | t.Error("TestVaildAmount assert failed") 61 | } 62 | _, ok = IsValidAmount("1") 63 | if !ok { 64 | t.Error("TestVaildAmount assert failed") 65 | } 66 | } 67 | 68 | func TestTxHash(t *testing.T) { 69 | _, e := MakeTxDigestHash(&pb.Transaction{}) 70 | if e != nil { 71 | t.Error("MakeTxDigestHash assert failed") 72 | } 73 | 74 | _, e = MakeTransactionID(&pb.Transaction{}) 75 | if e != nil { 76 | t.Error("MakeTransactionID assert failed") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // Package common is related to common variables and utils funcs 4 | package common 5 | 6 | import ( 7 | "bytes" 8 | "encoding/binary" 9 | "fmt" 10 | "log" 11 | "math/rand" 12 | "os" 13 | "strconv" 14 | "time" 15 | 16 | walletRand "github.com/xuperchain/crypto/core/hdwallet/rand" 17 | ) 18 | 19 | const ( 20 | // TxVersion tx version number 21 | TxVersion = 3 22 | ) 23 | 24 | // GetNonce get nonce value 25 | func GetNonce() string { 26 | return fmt.Sprintf("%d%8d", time.Now().Unix(), rand.Intn(100000000)) 27 | } 28 | 29 | // SetSeed set seed 30 | func SetSeed() error { 31 | // 生成加强版的随机熵 32 | seedByte, err := walletRand.GenerateSeedWithStrengthAndKeyLen(walletRand.KeyStrengthHard, walletRand.KeyLengthInt64) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // 生成大随机数 38 | bytesBuffer := bytes.NewBuffer(seedByte) 39 | var seed int64 40 | binary.Read(bytesBuffer, binary.BigEndian, &seed) 41 | 42 | // 设置随机数生成器的随机源 43 | rand.Seed(seed) 44 | 45 | return nil 46 | } 47 | 48 | // PathExistsAndMkdir judge whether path is existant or not 49 | func PathExistsAndMkdir(path string) error { 50 | _, err := os.Stat(path) 51 | if err == nil { 52 | return nil 53 | } 54 | err = os.Mkdir(path, os.ModePerm) 55 | if err != nil { 56 | return err 57 | } 58 | return nil 59 | } 60 | 61 | // IsValidAmount judge whether the number is legal 62 | func IsValidAmount(amount string) (string, bool) { 63 | if amount == "" { 64 | amount = "0" 65 | return amount, true 66 | } 67 | 68 | amountInt64, err := strconv.ParseInt(amount, 10, 64) 69 | if err != nil { 70 | log.Printf("Transfer amount to int64 err: %v", err) 71 | return "", false 72 | } 73 | 74 | if amountInt64 < 0 { 75 | log.Printf("Transfer amount is negative") 76 | return "", false 77 | } 78 | 79 | return amount, true 80 | } 81 | -------------------------------------------------------------------------------- /example/account/account.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/xuperchain/xuper-sdk-go/v2/account" 6 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 7 | ) 8 | 9 | func testAccount() { 10 | // 测试创建账户 11 | acc, err := account.CreateAccount(1, 1) 12 | if err != nil { 13 | fmt.Printf("create account error: %v\n", err) 14 | } 15 | fmt.Println(acc) 16 | fmt.Println(acc.Mnemonic) 17 | 18 | // 测试恢复账户 19 | acc, err = account.RetrieveAccount("玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 1) 20 | if err != nil { 21 | fmt.Printf("retrieveAccount err: %v\n", err) 22 | return 23 | } 24 | fmt.Printf("RetrieveAccount: to %v\n", acc) 25 | 26 | // 创建账户并存储到文件中 27 | acc, err = account.CreateAndSaveAccountToFile("./keys", "123", 1, 1) 28 | if err != nil { 29 | fmt.Printf("createAndSaveAccountToFile err: %v\n", err) 30 | } 31 | fmt.Printf("CreateAndSaveAccountToFile: %v\n", acc) 32 | 33 | // 从文件中恢复账户 34 | acc, err = account.GetAccountFromFile("keys/", "123") 35 | if err != nil { 36 | fmt.Printf("getAccountFromFile err: %v\n", err) 37 | } 38 | fmt.Printf("getAccountFromFile: %v\n", acc) 39 | return 40 | } 41 | 42 | //测试合约账户 43 | func testContractAccount() { 44 | // 通过助记词恢复账户 45 | account, err := account.RetrieveAccount("玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 1) 46 | if err != nil { 47 | fmt.Printf("retrieveAccount err: %v\n", err) 48 | return 49 | } 50 | fmt.Printf("retrieveAccount address: %v\n", account.Address) 51 | 52 | // 创建一个合约账户 53 | // 合约账户是由 (XC + 16个数字 + @xuper) 组成, 比如 "XC1234567890123456@xuper" 54 | contractAccount := "XC1234567890123456@xuper" 55 | 56 | xchainClient, err := xuper.New("127.0.0.1:37101") 57 | tx, err := xchainClient.CreateContractAccount(account, contractAccount) 58 | if err != nil { 59 | fmt.Printf("createContractAccount err:%s\n", err.Error()) 60 | } 61 | fmt.Println(tx.Tx.Txid) 62 | return 63 | } 64 | 65 | func main() { 66 | //testAccount() 67 | testContractAccount() 68 | } 69 | -------------------------------------------------------------------------------- /xuper/transaction.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/xuperchain/xuper-sdk-go/v2/account" 8 | "github.com/xuperchain/xuper-sdk-go/v2/common" 9 | "github.com/xuperchain/xuper-sdk-go/v2/crypto" 10 | 11 | "github.com/xuperchain/xuperchain/service/pb" 12 | ) 13 | 14 | // Transaction xuperchain transaction. 15 | type Transaction struct { 16 | Tx *pb.Transaction 17 | ContractResponse *pb.ContractResponse 18 | Bcname string 19 | 20 | Fee string 21 | GasUsed int64 22 | 23 | DigestHash []byte 24 | } 25 | 26 | // Sign account sign for tx, for multisign.multisign 27 | func (t *Transaction) Sign(account *account.Account) error { 28 | if account == nil { 29 | return errors.New("Transaction sign account can not be nil") 30 | } 31 | // 对于多签,在交易预执行时就需要写好所有的需要签名的地址到 AuthRequire 字段,其他地址再进行签名时,需要检查是否已经在 AuthRequire 字段中。 32 | // 同时签名的顺序也要保持一致,不然上链时会失败。 33 | if !inSlice(t.Tx.AuthRequire, account.GetAuthRequire()) { 34 | return errors.New("this account not in transaction's AuthRequire list") 35 | } 36 | 37 | if t.DigestHash == nil { 38 | digestHash, err := common.MakeTxDigestHash(t.Tx) 39 | if err != nil { 40 | return err 41 | } 42 | t.DigestHash = digestHash 43 | } 44 | 45 | cryptoClient := crypto.GetCryptoClient() 46 | privateKey, err := cryptoClient.GetEcdsaPrivateKeyFromJsonStr(account.PrivateKey) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | sign, err := cryptoClient.SignECDSA(privateKey, t.DigestHash) 52 | 53 | signatureInfo := &pb.SignatureInfo{ 54 | PublicKey: account.PublicKey, 55 | Sign: sign, 56 | } 57 | 58 | t.Tx.AuthRequireSigns = append(t.Tx.AuthRequireSigns, signatureInfo) 59 | t.Tx.InitiatorSigns = append(t.Tx.InitiatorSigns, signatureInfo) 60 | 61 | // make txid 62 | t.Tx.Txid, err = common.MakeTransactionID(t.Tx) 63 | 64 | return err 65 | } 66 | 67 | func inSlice(slice []string, str string) bool { 68 | for _, v := range slice { 69 | if v == str { 70 | return true 71 | } 72 | 73 | splitRes := strings.Split(v, "/") 74 | addr := splitRes[len(splitRes)-1] 75 | if addr == str { 76 | return true 77 | } 78 | } 79 | return false 80 | } 81 | -------------------------------------------------------------------------------- /example/transfer/transfer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/xuperchain/xuper-sdk-go/v2/account" 7 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 8 | ) 9 | 10 | func main() { 11 | akTransfer() 12 | contractAccountTransfer() 13 | } 14 | 15 | // akTransfer 普通账户转账(Ak)示例。 16 | func akTransfer() { 17 | // 创建或者使用已有账户,此处为新创建一个账户。 18 | from, err := account.RetrieveAccount("玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 1) 19 | if err != nil { 20 | fmt.Printf("retrieveAccount err: %v\n", err) 21 | return 22 | } 23 | 24 | to, err := account.CreateAccount(1, 1) 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Println(to.Address) 29 | fmt.Println(to.Mnemonic) 30 | 31 | // 节点地址。 32 | node := "127.0.0.1:37101" 33 | 34 | // 创建节点客户端。 35 | xclient, _ := xuper.New(node) 36 | 37 | // 转账前查看两个地址余额。 38 | fmt.Println(xclient.QueryBalance(from.Address)) 39 | fmt.Println(xclient.QueryBalance(to.Address)) 40 | 41 | tx, err := xclient.Transfer(from, to.Address, "10") 42 | if err != nil { 43 | panic(err) 44 | } 45 | fmt.Printf("%x\n", tx.Tx.Txid) 46 | 47 | // 转账后查看两个地址余额。 48 | fmt.Println(xclient.QueryBalance(from.Address)) 49 | fmt.Println(xclient.QueryBalance(to.Address)) 50 | } 51 | 52 | // contractAccountTransfer 合约账户转账示例。 53 | func contractAccountTransfer() { 54 | // 创建或者使用已有账户,此处为新创建一个账户。 55 | me, err := account.CreateAccount(1, 1) 56 | if err != nil { 57 | panic(err) 58 | } 59 | // XC1234567812345678@xuper 为合约账户,如果没有合约账户需要先创建合约账户。 60 | me.SetContractAccount("XC1234567812345678@xuper") 61 | fmt.Println(me.Address) 62 | fmt.Println(me.Mnemonic) 63 | fmt.Println(me.GetContractAccount()) 64 | fmt.Println(me.GetAuthRequire()) 65 | 66 | to, err := account.CreateAccount(1, 1) 67 | if err != nil { 68 | panic(err) 69 | } 70 | fmt.Println(to.Address) 71 | fmt.Println(to.Mnemonic) 72 | 73 | // 节点地址。 74 | node := "127.0.0.1:37101" 75 | xclient, _ := xuper.New(node) 76 | 77 | // 转账前查看两个地址余额。 78 | fmt.Println(xclient.QueryBalance(me.Address)) 79 | fmt.Println(xclient.QueryBalance(to.Address)) 80 | 81 | tx, err := xclient.Transfer(me, "a", "10") 82 | if err != nil { 83 | panic(err) 84 | } 85 | fmt.Printf("%x\n", tx.Tx.Txid) 86 | 87 | // 转账后查看两个地址余额。 88 | fmt.Println(xclient.QueryBalance(me.GetContractAccount())) // 转账时使用的是合约账户,因此查询余额时也是合约账户。 89 | fmt.Println(xclient.QueryBalance(to.Address)) 90 | } 91 | -------------------------------------------------------------------------------- /example/acl/acl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/xuperchain/xuper-sdk-go/v2/account" 7 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 8 | ) 9 | 10 | func main() { 11 | // XuperChain 可以为合约账户以及合约方法设置 ACL,下面分别使用 sdk 来设置 ACL。 12 | setAccountACLExample() 13 | setMethodACLExample() 14 | } 15 | 16 | func setAccountACLExample() { 17 | // 假设你已经有一个账户,助记词为:玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即,同时已经有了对应的合约账户:XC8888888899999999@xuper, 18 | // 同时这个账户可以操作对应的合约账户。 19 | mnemonic := "玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即" 20 | bob, _ := account.RetrieveAccount(mnemonic, 1) 21 | 22 | // 如果想修改 XC8888888899999999@xuper 的 ACL,首先需要设置普通账户的合约账户。 23 | contractAcc := "XC8888888899999999@xuper" 24 | bob.SetContractAccount(contractAcc) 25 | 26 | // 创建节点客户端。 27 | node := "127.0.0.0:37101" 28 | xclient, err := xuper.New(node) 29 | if err != nil { 30 | panic(err) 31 | } 32 | defer xclient.Close() 33 | 34 | // 设置想要的 ACL,假设想要设置两个普通账户一起签名才能使用合约账户,另外一个账户地址为:nuSMPvo6UUoTaT8mMQmHbfiRbJNbAymGh 35 | acl := xuper.NewACL(1, 0.6) 36 | acl.AddAK("nuSMPvo6UUoTaT8mMQmHbfiRbJNbAymGh", 0.3) 37 | acl.AddAK(bob.Address, 0.3) 38 | fmt.Println(acl) 39 | 40 | tx, err := xclient.SetAccountACL(bob, acl) 41 | if err != nil { 42 | panic(err) 43 | } 44 | // 查看本次交易的 gas。 45 | fmt.Println(tx.GasUsed) 46 | 47 | queryACL, err := xclient.QueryAccountACL(bob.GetContractAccount()) 48 | if err != nil { 49 | panic(err) 50 | } 51 | // 查看修改后的 ACL。 52 | fmt.Println(queryACL) 53 | } 54 | 55 | func setMethodACLExample() { 56 | // 设置合约方法的 ACL 与设置合约账户 ACL 类似, 57 | // 同样假设你已经有一个账户,助记词为:玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即,同时已经有了对应的合约账户:XC8888888899999999@xuper, 58 | // 另外假设你也有了一个合约 counter,同时还有一个方法 increas 已经部署了。 59 | // 现在要设置 counter 合约的 increase 方法只能你自己调用。 60 | 61 | mnemonic := "玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即" 62 | bob, _ := account.RetrieveAccount(mnemonic, 1) 63 | 64 | // 设置普通账户的合约账户。 65 | contractAcc := "XC8888888899999999@xuper" 66 | bob.SetContractAccount(contractAcc) 67 | 68 | // 创建节点客户端。 69 | node := "127.0.0.0:37101" 70 | xclient, err := xuper.New(node) 71 | if err != nil { 72 | panic(err) 73 | } 74 | defer xclient.Close() 75 | 76 | // 设置你的账户才有权限调用。 77 | acl := xuper.NewACL(1, 1.0) 78 | acl.AddAK(bob.Address, 1.0) 79 | fmt.Println(acl) 80 | 81 | tx, err := xclient.SetMethodACL(bob, "counter", "increase", acl) 82 | if err != nil { 83 | panic(err) 84 | } 85 | // 查看本次交易的 gas。 86 | fmt.Println(tx.GasUsed) 87 | 88 | queryACL, err := xclient.QueryMethodACL("counter", "increase") 89 | if err != nil { 90 | panic(err) 91 | } 92 | // 查看修改后的 ACL。 93 | fmt.Println(queryACL) 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # xuper-sdk-go XuperChain Client SDK for Go 3 | This SDK enables Go developers to build solutions that interact with open chain network of XuperChain. 4 | 5 | [![GoDoc](https://pkg.go.dev/badge/github.com/xuperchain/xuper-sdk-go?utm_source=godoc)](https://pkg.go.dev/github.com/xuperchain/xuper-sdk-go/v2) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/xuperchain/xuper-sdk-go)](https://goreportcard.com/report/github.com/xuperchain/xuper-sdk-go) 7 | [![License](https://img.shields.io/github/license/xuperchain/xuper-sdk-go?style=flat-square)](/LICENSE) 8 | 9 | # Getting started 10 | ## Requirements 11 | * OS Support: Linux and Mac OS 12 | * Go 1.12.x or later 13 | * GCC 4.8.x or later 14 | * Git 15 | 16 | 17 | ## Simple use samples 18 | 19 | Get xuper-sdk-go 20 | ```bash 21 | git clone https://github.com/xuperchain/xuper-sdk-go.git 22 | 23 | cd xuper-sdk-go 24 | ``` 25 | 26 | Run test 27 | ```bash 28 | make 29 | ``` 30 | 31 | Use go get xuper-sdk-go 32 | ```bash 33 | go get github.com/xuperchain/xuper-sdk-go/v2 34 | ``` 35 | 36 | ## example 37 | xuper-go-sdk more [example](https://github.com/xuperchain/xuper-sdk-go/tree/master/example) 38 | 39 | # Contributing to the xuper-sdk-go 40 | If you want to contribute to xuper SDK, 41 | please read the source code, understand the current technology, and then develop it. 42 | After the completion of development, the corresponding test cases shall be supplemented. 43 | Initiate pull request. 44 | 45 | # License 46 | xuper-sdk-go software is licensed under [Apache License, Version 2.0](https://github.com/xuperchain/xuper-sdk-go/blob/master/LICENSE) 47 | 48 | # Contact us 49 | If you're interested in xuper-sdk-go, welcome to join us and develop together 50 | 51 | 52 | # XuperChain go语言的客户端xuper-sdk-go 53 | SDK 可以使 go 的开发者更好的与 XuperChain 的公开链网络进行交互以及其他 XuperChain 的节点。 54 | 55 | # Getting started 56 | ## 环境配置 57 | 58 | * 操作系统:支持Linux以及Mac OS 59 | * 开发语言:Go 1.12.x及以上 60 | * 编译器:GCC 4.8.x及以上 61 | * 版本控制工具:Git 62 | 63 | ## 使用 64 | 65 | 克隆xuper-sdk-go仓库 66 | ``` 67 | git clone https://github.com/xuperchain/xuper-sdk-go.git 68 | 69 | cd xuper-sdk-go 70 | ``` 71 | 72 | 运行测试 73 | ```bash 74 | make 75 | ``` 76 | 77 | 使用 go get 安装 xuper-sdk-go 78 | ```bash 79 | go get github.com/xuperchain/xuper-sdk-go/v2 80 | ``` 81 | 82 | ## 示例 83 | xuper-go-sdk 使用示例请参考[example](https://github.com/xuperchain/xuper-sdk-go/tree/master/example) 84 | 85 | # 如何参与开发xuper-sdk-go 86 | 如果您想为xuper-sdk-go做贡献 87 | 请阅读源代码,了解当前技术之后,进行开发 88 | 开发完毕后,补充相应test caset 89 | 发起pull request 90 | 91 | # 许可证 92 | xuper-sdk-go使用的许可证是[Apache License, Version 2.0](https://github.com/xuperchain/xuper-sdk-go/blob/master/LICENSE) 93 | 94 | # 联系我们 95 | 如果你对xuper-sdk-go感兴趣,欢迎加入我们,共同开发 96 | -------------------------------------------------------------------------------- /example/multisig/multisig.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/xuperchain/xuper-sdk-go/v2/account" 7 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 8 | ) 9 | 10 | func main() { 11 | // 多签场景有两种方式可以选择,主要流程都是先创建好交易,然后收集到足够的签名再发送到链上。 12 | multisign1() 13 | multisign2() 14 | } 15 | 16 | // 构建 Request 再构造 Transaction,然后签名再 post 到链。 17 | func multisign1() { 18 | // 假设有一个合约账户,两个普通地址(AK),需要两个地址同时签名才可以使用合约账户,此时多签的示例如下。 19 | // 已有账户 alice 和 bob,两个账户同时签名才可以使用合约账户 XC1234567812345678@xuper。 20 | contractAccount := "XC1234567812345678@xuper" 21 | alice, err := account.CreateAccount(1, 1) 22 | if err != nil { 23 | panic(err) 24 | } 25 | alice.SetContractAccount(contractAccount) 26 | 27 | bob, err := account.CreateAccount(1, 1) 28 | if err != nil { 29 | panic(err) 30 | } 31 | bob.SetContractAccount(contractAccount) 32 | 33 | // 创建链的客户端。 34 | node := "127.0.0.1:37101" 35 | xclient, _ := xuper.New(node) 36 | 37 | // 创建本次交易的请求数据。 38 | code := []byte{} 39 | args := map[string]string{ 40 | "creator": "bob", 41 | } 42 | 43 | // 首先使用 alice 构造交易,由于知道还需要 bob 账户签名,因此需要增加 bob 的 AuthRequire。 44 | authRequire := []string{bob.GetAuthRequire()} 45 | request, err := xuper.NewDeployContractRequest(alice, "counter", nil, code, args, "wasm", "c", xuper.WithOtherAuthRequires(authRequire)) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | // 构造本次交易的数据结构,此时交易还未发送到链上。 51 | // 由于使用多签的形式,我们还需要其他账户对此交易结构进行签名。 52 | tx, err := xclient.GenerateTx(request) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | // 可以将 tx 数据通过网络传输给其他服务,也可以直接使用账户对 tx 进行签名。最后只需要将收集到签名的 tx 返回即可。 58 | 59 | // 使用 bob 账户对 tx 进行签名。 60 | tx.Sign(bob) 61 | 62 | // 收集到足够的签名后,将交易发送出去。 63 | tx, err = xclient.PostTx(tx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | // 打印出交易的 ID。 69 | fmt.Printf("%x\n", tx.Tx.Txid) 70 | } 71 | 72 | // 使用 xuperclient 构造交易。 73 | func multisign2() { 74 | // 假设有一个合约账户,两个普通地址(AK),需要两个地址同时签名才可以使用合约账户,此时多签的示例如下。 75 | // 已有账户 alice 和 bob,两个账户同时签名才可以使用合约账户 XC1234567812345678@xuper。 76 | contractAccount := "XC1234567812345678@xuper" 77 | alice, err := account.CreateAccount(1, 1) 78 | if err != nil { 79 | panic(err) 80 | } 81 | alice.SetContractAccount(contractAccount) 82 | 83 | bob, err := account.CreateAccount(1, 1) 84 | if err != nil { 85 | panic(err) 86 | } 87 | bob.SetContractAccount(contractAccount) 88 | 89 | // 创建链的客户端。 90 | node := "127.0.0.1:37101" 91 | xclient, _ := xuper.New(node) 92 | 93 | // 创建本次交易的请求数据。 94 | code := []byte{} 95 | args := map[string]string{ 96 | "creator": "bob", 97 | } 98 | 99 | // 首先使用 alice 构造交易,由于知道还需要 bob 账户签名,因此需要增加 bob 的 AuthRequire。 100 | authRequire := []string{bob.GetAuthRequire()} 101 | 102 | // 使用 xuper.WithNotPost() 表明只构造交易,不将交易 post 到链上。 103 | // xuper.WithOtherAuthRequires() 表明还需要增加 bob 的签名。 104 | tx, err := xclient.DeployWasmContract(alice, "counter", code, args, xuper.WithNotPost(), xuper.WithOtherAuthRequires(authRequire)) 105 | if err != nil { 106 | panic(err) 107 | } 108 | 109 | // 可以将 tx 数据通过网络传输给其他服务,也可以直接使用账户对 tx 进行签名。最后只需要将收集到签名的 tx 返回即可。 110 | 111 | // 使用 bob 账户对 tx 进行签名。 112 | tx.Sign(bob) 113 | 114 | // 收集到足够的签名后,将交易发送出去。 115 | tx, err = xclient.PostTx(tx) 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | // 打印出交易的 ID。 121 | fmt.Printf("%x\n", tx.Tx.Txid) 122 | } 123 | -------------------------------------------------------------------------------- /xuper/options.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import "github.com/pkg/errors" 4 | 5 | type clientOptions struct { 6 | configFile string 7 | useGrpcGZIP bool 8 | grpcTLS *grpcTLSConfig 9 | } 10 | 11 | type grpcTLSConfig struct { 12 | serverName string 13 | cacertFile string 14 | certFile string 15 | keyFile string 16 | } 17 | 18 | type requestOptions struct { 19 | onlyFeeFromAccount bool 20 | fee string 21 | bcname string 22 | contractInvokeAmount string 23 | desc string 24 | otherAuthRequire []string 25 | notPost bool 26 | gasFromAddress bool 27 | } 28 | 29 | type queryOption struct { 30 | bcname string 31 | } 32 | 33 | // RequestOption tx opt. 34 | type RequestOption func(opt *requestOptions) error 35 | 36 | // ClientOption xuperclient opt. 37 | type ClientOption func(opt *clientOptions) error 38 | 39 | // QueryOption query opt. 40 | type QueryOption func(opt *queryOption) error 41 | 42 | // WithQueryBcname query method bcname option. 43 | func WithQueryBcname(bcname string) QueryOption { 44 | return func(opts *queryOption) error { 45 | opts.bcname = bcname 46 | return nil 47 | } 48 | } 49 | 50 | // WithConfigFile set xuperclient config file. 51 | func WithConfigFile(configFile string) ClientOption { 52 | return func(opts *clientOptions) error { 53 | opts.configFile = configFile 54 | return nil 55 | } 56 | } 57 | 58 | // WithGrpcGZIP use gzip. 59 | func WithGrpcGZIP() ClientOption { 60 | return func(opts *clientOptions) error { 61 | opts.useGrpcGZIP = true 62 | return nil 63 | } 64 | } 65 | 66 | // WithGrpcTLS grpc TLS cert config. 67 | func WithGrpcTLS(serverName, cacertFile, certFile, keyFile string) ClientOption { 68 | return func(opts *clientOptions) error { 69 | if opts.grpcTLS == nil { 70 | opts.grpcTLS = new(grpcTLSConfig) 71 | } 72 | opts.grpcTLS.serverName = serverName 73 | opts.grpcTLS.cacertFile = cacertFile 74 | opts.grpcTLS.certFile = certFile 75 | opts.grpcTLS.keyFile = keyFile 76 | return nil 77 | } 78 | } 79 | 80 | // WithFeeFromAccount fee & gas from contract account. 81 | func WithFeeFromAccount() RequestOption { 82 | return func(opts *requestOptions) error { 83 | opts.onlyFeeFromAccount = true 84 | return nil 85 | } 86 | } 87 | 88 | func WithGasFromAddress() RequestOption { 89 | return func(opts *requestOptions) error { 90 | opts.gasFromAddress = true 91 | return nil 92 | } 93 | } 94 | 95 | // WithFee set fee. 96 | func WithFee(fee string) RequestOption { 97 | return func(opts *requestOptions) error { 98 | opts.fee = fee 99 | return nil 100 | } 101 | } 102 | 103 | // WithBcname set blockchain name. 104 | func WithBcname(name string) RequestOption { 105 | return func(opts *requestOptions) error { 106 | if name == "" { 107 | return errors.New("invalid bcname") 108 | } 109 | opts.bcname = name 110 | return nil 111 | } 112 | } 113 | 114 | // WithContractInvokeAmount set transfer to contract when invoke contract. 115 | func WithContractInvokeAmount(amount string) RequestOption { 116 | return func(opts *requestOptions) error { 117 | opts.contractInvokeAmount = amount 118 | return nil 119 | } 120 | } 121 | 122 | // WithDesc set tx desc. 123 | func WithDesc(desc string) RequestOption { 124 | return func(opts *requestOptions) error { 125 | opts.desc = desc 126 | return nil 127 | } 128 | } 129 | 130 | // WithNotPost generate transaction only, won't post to server. 131 | func WithNotPost() RequestOption { 132 | return func(opts *requestOptions) error { 133 | opts.notPost = true 134 | return nil 135 | } 136 | } 137 | 138 | // WithOtherAuthRequires for multisign, other address need sign, exclude initiator. 139 | func WithOtherAuthRequires(authRequires []string) RequestOption { 140 | return func(opts *requestOptions) error { 141 | opts.otherAuthRequire = authRequires 142 | return nil 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /common/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "path/filepath" 7 | 8 | "strconv" 9 | 10 | "gopkg.in/yaml.v2" 11 | ) 12 | 13 | // ComplianceCheckConfig endorser config. 14 | type ComplianceCheckConfig struct { 15 | IsNeedComplianceCheck bool `yaml:"isNeedComplianceCheck,omitempty"` 16 | IsNeedComplianceCheckFee bool `yaml:"isNeedComplianceCheckFee,omitempty"` 17 | ComplianceCheckEndorseServiceFee int `yaml:"complianceCheckEndorseServiceFee,omitempty"` 18 | ComplianceCheckEndorseServiceFeeAddr string `yaml:"complianceCheckEndorseServiceFeeAddr,omitempty"` 19 | ComplianceCheckEndorseServiceAddr string `yaml:"complianceCheckEndorseServiceAddr,omitempty"` 20 | } 21 | 22 | // CommConfig sdk config. 23 | type CommConfig struct { 24 | EndorseServiceHost string `yaml:"endorseServiceHost,omitempty"` 25 | ComplianceCheck ComplianceCheckConfig `yaml:"complianceCheck,omitempty"` 26 | MinNewChainAmount string `yaml:"minNewChainAmount,omitempty"` 27 | Crypto string `yaml:"crypto,omitempty"` 28 | TxVersion int32 `yaml:"txVersion,omitempty"` 29 | MaxRecvMsgSize int `yaml:"maxRecvMsgSize,omitempty"` 30 | } 31 | 32 | const confPath = "./conf" 33 | const confName = "sdk.yaml" 34 | 35 | const CRYPTO_XCHAIN = "xchain" 36 | const CRYPTO_GM = "gm" 37 | 38 | var config *CommConfig 39 | 40 | // GetInstance get config instance. 41 | func GetInstance() *CommConfig { 42 | if config == nil { 43 | var err error 44 | config, err = GetConfig(filepath.Join(confPath, confName)) 45 | if err != nil { 46 | log.Printf("no config file in ./conf/sdk.yaml, use default config: %v\n", config) 47 | } 48 | } 49 | return config 50 | } 51 | 52 | // SetGMCrypto 使用国密,用这个方法可以不使用配置文件来修改了。 53 | func (c *CommConfig) SetGMCrypto() { 54 | c.Crypto = CRYPTO_GM 55 | } 56 | 57 | // SetXchainCrypto 使用 xchain 加密算法,用这个方法可以不使用配置文件来修改了。 58 | func (c *CommConfig) SetXchainCrypto() { 59 | c.Crypto = CRYPTO_XCHAIN 60 | } 61 | 62 | // GetConfig load config from confFile and new config instance. 63 | func GetConfig(confFile string) (*CommConfig, error) { 64 | // default config 65 | commConfig := &CommConfig{ 66 | EndorseServiceHost: "10.144.94.18:8848", 67 | ComplianceCheck: ComplianceCheckConfig{ 68 | ComplianceCheckEndorseServiceFee: 10, 69 | ComplianceCheckEndorseServiceFeeAddr: "XBbhR82cB6PvaLJs3D4uB9f12bhmKkHeX", 70 | ComplianceCheckEndorseServiceAddr: "TYyA3y8wdFZyzExtcbRNVd7ZZ2XXcfjdw", 71 | }, 72 | MinNewChainAmount: "100", 73 | Crypto: CRYPTO_XCHAIN, 74 | } 75 | 76 | yamlFile, err := ioutil.ReadFile(confFile) 77 | if err != nil { 78 | return commConfig, err 79 | } 80 | 81 | err = yaml.Unmarshal(yamlFile, commConfig) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | config = commConfig 87 | return commConfig, nil 88 | } 89 | 90 | // SetConfig set config fileds. 91 | func SetConfig(checkHost, checkAddr, checkFeeAddr, checkFee string, isNeedCheck, isNeedCheckFee bool, minNewChainAmount string) { 92 | commConfig := &CommConfig{ 93 | EndorseServiceHost: "10.144.94.18:8848", 94 | ComplianceCheck: ComplianceCheckConfig{ 95 | ComplianceCheckEndorseServiceFee: 10, 96 | ComplianceCheckEndorseServiceFeeAddr: "XBbhR82cB6PvaLJs3D4uB9f12bhmKkHeX", 97 | ComplianceCheckEndorseServiceAddr: "TYyA3y8wdFZyzExtcbRNVd7ZZ2XXcfjdw", 98 | }, 99 | MinNewChainAmount: "100", 100 | Crypto: CRYPTO_XCHAIN, 101 | } 102 | if checkHost != "" { 103 | commConfig.EndorseServiceHost = checkHost 104 | } 105 | if checkFeeAddr != "" { 106 | commConfig.ComplianceCheck.ComplianceCheckEndorseServiceFeeAddr = checkFeeAddr 107 | } 108 | if checkAddr != "" { 109 | commConfig.ComplianceCheck.ComplianceCheckEndorseServiceAddr = checkAddr 110 | } 111 | if checkFee != "" { 112 | fee, _ := strconv.Atoi(checkFee) 113 | commConfig.ComplianceCheck.ComplianceCheckEndorseServiceFee = fee 114 | } 115 | if minNewChainAmount != "" { 116 | commConfig.MinNewChainAmount = minNewChainAmount 117 | } 118 | commConfig.ComplianceCheck.IsNeedComplianceCheck = isNeedCheck 119 | commConfig.ComplianceCheck.IsNeedComplianceCheckFee = isNeedCheckFee 120 | 121 | config = commConfig 122 | } 123 | -------------------------------------------------------------------------------- /example/contract/contract.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "time" 7 | 8 | "github.com/xuperchain/xuper-sdk-go/v2/account" 9 | "github.com/xuperchain/xuper-sdk-go/v2/xuper" 10 | ) 11 | 12 | func main() { 13 | //testEVMContract() 14 | //testNativeContract() 15 | testWasmContract() 16 | 17 | } 18 | 19 | func testWasmContract() { 20 | codePath := "example/contract/data/counter.wasm" 21 | code, err := ioutil.ReadFile(codePath) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | xuperClient, err := xuper.New("127.0.0.1:37101") 27 | if err != nil { 28 | panic("new xuper Client error:") 29 | } 30 | 31 | // account, err := account.RetrieveAccount("玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 1) 32 | acc, err := account.GetAccountFromPlainFile("example/contract/data/keys") 33 | if err != nil { 34 | fmt.Printf("retrieveAccount err: %v\n", err) 35 | return 36 | } 37 | fmt.Printf("retrieveAccount address: %v\n", acc.Address) 38 | contractAccount := "XC1111111111111111@xuper" 39 | timeStamp := fmt.Sprintf("%d", time.Now().Unix()) 40 | contractName := fmt.Sprintf("counter%s", timeStamp[1:]) 41 | fmt.Println(contractName) 42 | err = acc.SetContractAccount(contractAccount) 43 | 44 | args := map[string]string{ 45 | "creator": "test", 46 | "key": "test", 47 | } 48 | 49 | tx, err := xuperClient.DeployWasmContract(acc, contractName, code, args) 50 | if err != nil { 51 | panic(err) 52 | } 53 | fmt.Printf("Deploy wasm Success!TxID:%x\n", tx.Tx.Txid) 54 | 55 | tx, err = xuperClient.InvokeWasmContract(acc, contractName, "increase", args) 56 | if err != nil { 57 | panic(err) 58 | } 59 | fmt.Printf("Invoke Wasm Contract Success! TxID:%x\n", tx.Tx.Txid) 60 | 61 | tx, err = xuperClient.QueryWasmContract(acc, contractName, "get", args) 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Printf("Query Wasm Contract Success! Response:%s\n", tx.ContractResponse.Body) 66 | } 67 | 68 | func testEVMContract() { 69 | binPath := "./example/contract/Counter.bin" 70 | abiPath := "./example/contract/Counter.abi" 71 | 72 | bin, err := ioutil.ReadFile(binPath) 73 | if err != nil { 74 | panic(err) 75 | } 76 | abi, err := ioutil.ReadFile(abiPath) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | // 通过助记词恢复账户 82 | account, err := account.RetrieveAccount("玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 1) 83 | if err != nil { 84 | fmt.Printf("retrieveAccount err: %v\n", err) 85 | return 86 | } 87 | fmt.Printf("retrieveAccount address: %v\n", account.Address) 88 | contractAccount := "XC1234567890123456@xuper" 89 | err = account.SetContractAccount(contractAccount) 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | contractName := "SDKEvmContract" 95 | xchainClient, err := xuper.New("127.0.0.1:37101") 96 | 97 | args := map[string]string{ 98 | "key": "test", 99 | } 100 | tx, err := xchainClient.DeployEVMContract(account, contractName, abi, bin, args) 101 | if err != nil { 102 | panic(err) 103 | } 104 | fmt.Printf("DeployEVMContract SUCCESS! %x\n", tx.Tx.Txid) 105 | 106 | tx, err = xchainClient.InvokeEVMContract(account, contractName, "increase", args) 107 | if err != nil { 108 | panic(err) 109 | } 110 | fmt.Printf("InvokeEVMContract SUCCESS! %x\n", tx.Tx.Txid) 111 | 112 | tx, err = xchainClient.QueryEVMContract(account, contractName, "get", args) 113 | if err != nil { 114 | panic(err) 115 | } 116 | fmt.Printf("InvokeEVMContract Success! Response:%s\n", tx.ContractResponse.Body) 117 | 118 | } 119 | 120 | func testNativeContract() { 121 | codePath := "./example/contract/counter" // 编译好的二进制文件 122 | code, err := ioutil.ReadFile(codePath) 123 | if err != nil { 124 | panic(err) 125 | } 126 | 127 | account, err := account.RetrieveAccount("玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 1) 128 | if err != nil { 129 | fmt.Printf("retrieveAccount err: %v\n", err) 130 | return 131 | } 132 | fmt.Printf("retrieveAccount address: %v\n", account.Address) 133 | contractAccount := "XC1234567890123456@xuper" 134 | contractName := "SDKNativeCount1" 135 | err = account.SetContractAccount(contractAccount) 136 | if err != nil { 137 | panic(err) 138 | } 139 | 140 | xchainClient, err := xuper.New("127.0.0.1:37101") 141 | if err != nil { 142 | panic(err) 143 | } 144 | args := map[string]string{ 145 | "creator": "test", 146 | "key": "test", 147 | } 148 | tx, err := xchainClient.DeployNativeGoContract(account, contractName, code, args) 149 | if err != nil { 150 | panic(err) 151 | } 152 | fmt.Printf("Deploy Native Go Contract Success! %x\n", tx.Tx.Txid) 153 | 154 | tx, err = xchainClient.InvokeNativeContract(account, contractName, "increase", args) 155 | if err != nil { 156 | panic(err) 157 | } 158 | fmt.Printf("Invoke Native Go Contract Success! %x\n", tx.Tx.Txid) 159 | 160 | tx, err = xchainClient.QueryNativeContract(account, contractName, "get", args) 161 | if err != nil { 162 | panic(err) 163 | } 164 | if tx != nil { 165 | fmt.Printf("查询结果:%s\n", tx.ContractResponse.Body) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /xuper/event.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/xuperchain/xuperchain/service/pb" 8 | ) 9 | 10 | // Watcher event watcher. 11 | type Watcher struct { 12 | FilteredBlockChan <-chan *FilteredBlock 13 | exit chan<- struct{} 14 | 15 | opt *blockEventOption 16 | } 17 | 18 | func initEventOpts(opts ...BlockEventOption) (*blockEventOption, error) { 19 | opt := &blockEventOption{ 20 | blockChanBufferSize: 100, // default 100. 21 | blockFilter: &pb.BlockFilter{ 22 | Bcname: "xuper", // default xuper. 23 | }, 24 | } 25 | 26 | for _, param := range opts { 27 | err := param(opt) 28 | if err != nil { 29 | return nil, fmt.Errorf("event option failed: %v", err) 30 | } 31 | } 32 | 33 | return opt, nil 34 | } 35 | 36 | // Close close watcher. 37 | func (w *Watcher) Close() { 38 | close(w.exit) 39 | } 40 | 41 | // FilteredBlock pb.FilteredBlock 42 | type FilteredBlock struct { 43 | Bcname string `json:"bcname,omitempty"` 44 | Blockid string `json:"blockid,omitempty"` 45 | BlockHeight int64 `json:"block_height,omitempty"` 46 | Txs []*FilteredTransaction `json:"txs,omitempty"` 47 | } 48 | 49 | // FilteredTransaction pb.FilteredTransaction 50 | type FilteredTransaction struct { 51 | Txid string `json:"txid,omitempty"` 52 | Events []*ContractEvent `json:"events,omitempty"` 53 | } 54 | 55 | // ContractEvent pb.ContractEvent 56 | type ContractEvent struct { 57 | Contract string `json:"contract,omitempty"` 58 | Name string `json:"name,omitempty"` 59 | Body string `json:"body,omitempty"` 60 | } 61 | 62 | func fromFilteredBlockPB(pbblock *pb.FilteredBlock) *FilteredBlock { 63 | block := &FilteredBlock{ 64 | Bcname: pbblock.Bcname, 65 | Blockid: pbblock.Blockid, 66 | BlockHeight: pbblock.BlockHeight, 67 | Txs: make([]*FilteredTransaction, 0, len(pbblock.Txs)), 68 | } 69 | 70 | for _, pbtx := range pbblock.Txs { 71 | tx := &FilteredTransaction{ 72 | Txid: pbtx.Txid, 73 | Events: make([]*ContractEvent, 0, len(pbtx.Events)), 74 | } 75 | for _, pbevent := range pbtx.Events { 76 | tx.Events = append(tx.Events, &ContractEvent{ 77 | Contract: pbevent.Contract, 78 | Name: pbevent.Name, 79 | Body: string(pbevent.Body), 80 | }) 81 | } 82 | block.Txs = append(block.Txs, tx) 83 | } 84 | return block 85 | } 86 | 87 | // BlockEventOption event opt. 88 | type BlockEventOption func(*blockEventOption) error 89 | 90 | type blockEventOption struct { 91 | blockFilter *pb.BlockFilter 92 | 93 | blockChanBufferSize uint 94 | skipEmptyTx bool 95 | } 96 | 97 | // WithBlockChanBufferSize block event block channel size, default 100. 98 | func WithBlockChanBufferSize(size uint) BlockEventOption { 99 | return func(f *blockEventOption) error { 100 | if size < 0 { 101 | return errors.New("Invalid size for watcher blockChanBufferSize chan") 102 | } 103 | f.blockChanBufferSize = size 104 | return nil 105 | } 106 | } 107 | 108 | // WithSkipEmplyTx block event skip emply tx block. 109 | func WithSkipEmplyTx() BlockEventOption { 110 | return func(f *blockEventOption) error { 111 | f.skipEmptyTx = true 112 | return nil 113 | } 114 | } 115 | 116 | // WithBlockEventBcname blockchain name. 117 | func WithBlockEventBcname(name string) BlockEventOption { 118 | return func(f *blockEventOption) error { 119 | f.blockFilter.Bcname = name 120 | return nil 121 | } 122 | } 123 | 124 | // WithContract indicates the contract name from which tx are to be received. 125 | func WithContract(contract string) BlockEventOption { 126 | return func(f *blockEventOption) error { 127 | f.blockFilter.Contract = contract 128 | return nil 129 | } 130 | } 131 | 132 | // WithEventName indicates the event name from which events are to be received. 133 | func WithEventName(eventName string) BlockEventOption { 134 | return func(f *blockEventOption) error { 135 | f.blockFilter.EventName = eventName 136 | return nil 137 | } 138 | } 139 | 140 | // WithInitiator indicates the contract initiator from which tx are to be received. 141 | func WithInitiator(initiator string) BlockEventOption { 142 | return func(f *blockEventOption) error { 143 | f.blockFilter.Initiator = initiator 144 | return nil 145 | } 146 | } 147 | 148 | // WithAuthRequire indicates the auth require from which tx are to be received. 149 | func WithAuthRequire(authRequire string) BlockEventOption { 150 | return func(f *blockEventOption) error { 151 | f.blockFilter.AuthRequire = authRequire 152 | return nil 153 | } 154 | } 155 | 156 | // WithFromAddr indicates the transfer address from which tx are to be received. 157 | func WithFromAddr(fromAddr string) BlockEventOption { 158 | return func(f *blockEventOption) error { 159 | f.blockFilter.FromAddr = fromAddr 160 | return nil 161 | } 162 | } 163 | 164 | // WithToAddr indicates the receiver address from which tx are to be received. 165 | func WithToAddr(toAddr string) BlockEventOption { 166 | return func(f *blockEventOption) error { 167 | f.blockFilter.ToAddr = toAddr 168 | return nil 169 | } 170 | } 171 | 172 | // WithBlockRange indicates the block range. 173 | func WithBlockRange(startBlock, endBlock string) BlockEventOption { 174 | return func(f *blockEventOption) error { 175 | if f.blockFilter.Range == nil { 176 | f.blockFilter.Range = &pb.BlockRange{} 177 | } 178 | f.blockFilter.Range.Start = startBlock 179 | f.blockFilter.Range.End = endBlock 180 | return nil 181 | } 182 | } 183 | 184 | // WithExcludeTx indicates if exclude tx. 185 | func WithExcludeTx(excludeTx bool) BlockEventOption { 186 | return func(f *blockEventOption) error { 187 | f.blockFilter.ExcludeTx = excludeTx 188 | return nil 189 | } 190 | } 191 | 192 | // WithExcludeTxEvent indicates if exclude tx event. 193 | func WithExcludeTxEvent(excludeTxEvent bool) BlockEventOption { 194 | return func(f *blockEventOption) error { 195 | f.blockFilter.ExcludeTxEvent = excludeTxEvent 196 | return nil 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /account/account_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // package account is related to account operation 4 | package account 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | 13 | "github.com/xuperchain/xuper-sdk-go/v2/common" 14 | ) 15 | 16 | func TestCreateAccount(t *testing.T) { 17 | testCase := []struct { 18 | strength uint8 19 | language int 20 | }{ 21 | { 22 | strength: 1, 23 | language: 1, 24 | }, 25 | { 26 | strength: 2, 27 | language: 1, 28 | }, 29 | { 30 | strength: 3, 31 | language: 1, 32 | }, 33 | { 34 | strength: 1, 35 | language: 2, 36 | }, 37 | { 38 | strength: 2, 39 | language: 2, 40 | }, 41 | { 42 | strength: 3, 43 | language: 2, 44 | }, 45 | { 46 | strength: 0, 47 | language: 5, 48 | }, 49 | } 50 | 51 | for _, arg := range testCase { 52 | acc, err := CreateAccount(arg.strength, arg.language) 53 | t.Logf("create account: %v, err: %v", acc, err) 54 | } 55 | } 56 | 57 | func TestRetrieveAccount(t *testing.T) { 58 | testCase := []struct { 59 | mnemonic string 60 | language int 61 | }{ 62 | { 63 | mnemonic: "玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 64 | language: 1, 65 | }, 66 | { 67 | mnemonic: "玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即", 68 | language: 2, 69 | }, 70 | { 71 | mnemonic: "", 72 | language: 1, 73 | }, 74 | } 75 | 76 | for _, arg := range testCase { 77 | acc, err := RetrieveAccount(arg.mnemonic, arg.language) 78 | t.Logf("RetrieveAccount: %v, err: %v", acc, err) 79 | } 80 | } 81 | 82 | func TestCreateAndSaveAccountToFile(t *testing.T) { 83 | testCase := []struct { 84 | path string 85 | passwd string 86 | strength uint8 87 | language int 88 | }{ 89 | { 90 | path: "./keys", 91 | passwd: "123", 92 | strength: 1, 93 | language: 1, 94 | }, 95 | { 96 | path: "./keys", 97 | passwd: "123", 98 | strength: 1, 99 | language: 2, 100 | }, 101 | { 102 | path: "./aaa", 103 | passwd: "123", 104 | strength: 1, 105 | language: 2, 106 | }, 107 | } 108 | 109 | for _, arg := range testCase { 110 | acc, err := CreateAndSaveAccountToFile(arg.path, arg.passwd, arg.strength, arg.language) 111 | t.Logf("CreateAndSaveAccountToFile: %v, err: %v", acc, err) 112 | fmt.Println(os.RemoveAll(arg.path)) 113 | } 114 | } 115 | 116 | func TestGetAccountFromFile(t *testing.T) { 117 | testCase := []struct { 118 | path string 119 | passwd string 120 | }{ 121 | { 122 | path: "./keys/", 123 | passwd: "123", 124 | }, 125 | { 126 | path: "./aaa/", 127 | passwd: "123", 128 | }, 129 | } 130 | 131 | for _, arg := range testCase { 132 | CreateAndSaveAccountToFile(arg.path, arg.passwd, 1, 1) 133 | 134 | acc, err := GetAccountFromFile(arg.path, arg.passwd) 135 | if err != nil { 136 | t.Error(err) 137 | } 138 | if acc == nil { 139 | t.Error("GetAccountFromFile assert failed") 140 | } 141 | os.RemoveAll(arg.path) 142 | } 143 | } 144 | 145 | func TestGetAccountFromPalinFile(t *testing.T) { 146 | testCase := []struct { 147 | path string 148 | hasAddress bool 149 | hasPubkey bool 150 | hasPrivkey bool 151 | }{ 152 | { 153 | path: "./keys/", 154 | hasAddress: true, 155 | hasPubkey: true, 156 | hasPrivkey: true, 157 | }, 158 | { 159 | path: "./aaa/", 160 | hasAddress: true, 161 | hasPubkey: true, 162 | hasPrivkey: false, 163 | }, 164 | { 165 | path: "./aaa/", 166 | hasAddress: false, 167 | hasPubkey: true, 168 | hasPrivkey: true, 169 | }, 170 | { 171 | path: "./aaa/", 172 | hasAddress: true, 173 | hasPubkey: false, 174 | hasPrivkey: true, 175 | }, 176 | } 177 | for _, c := range testCase { 178 | acc, _ := CreateAccount(1, 1) 179 | 180 | err := os.MkdirAll(c.path, 0750) 181 | if err != nil { 182 | t.Error(err) 183 | } 184 | 185 | if c.hasAddress { 186 | fs, err := os.Create(filepath.Join(c.path, "address")) 187 | if err != nil { 188 | t.Error(err) 189 | } 190 | fs.WriteString(acc.Address) 191 | fs.Close() 192 | } 193 | if c.hasPubkey { 194 | fs, err := os.Create(filepath.Join(c.path, "public.key")) 195 | if err != nil { 196 | t.Error(err) 197 | } 198 | fs.WriteString(acc.PublicKey) 199 | fs.Close() 200 | } 201 | if c.hasPrivkey { 202 | fs, err := os.Create(filepath.Join(c.path, "private.key")) 203 | if err != nil { 204 | t.Error(err) 205 | } 206 | fs.WriteString(acc.PrivateKey) 207 | fs.Close() 208 | } 209 | 210 | acc1, err := GetAccountFromPlainFile(c.path) 211 | if err != nil { 212 | if !(!c.hasAddress || !c.hasPrivkey || !c.hasPubkey) { 213 | t.Error(err) 214 | } 215 | } else { 216 | if acc1.Address != acc.Address { 217 | t.Error("TestGetAccountFromPalinFile address not match.") 218 | } 219 | if acc1.PublicKey != acc.PublicKey { 220 | t.Error("TestGetAccountFromPalinFile address not match.") 221 | } 222 | if acc1.PrivateKey != acc.PrivateKey { 223 | t.Error("TestGetAccountFromPalinFile address not match.") 224 | } 225 | } 226 | 227 | os.RemoveAll(c.path) 228 | } 229 | } 230 | 231 | func TestSetContractAccount(t *testing.T) { 232 | acc, _ := CreateAccount(1, 1) 233 | err := acc.SetContractAccount("123") 234 | if !errors.Is(err, common.ErrInvalidContractAccount) { 235 | t.Error(err) 236 | } 237 | 238 | err = acc.SetContractAccount("XC123@xuper") 239 | if !errors.Is(err, common.ErrInvalidContractAccount) { 240 | t.Error(err) 241 | } 242 | 243 | err = acc.SetContractAccount("1234567812345678@xuper") 244 | if !errors.Is(err, common.ErrInvalidContractAccount) { 245 | t.Error(err) 246 | } 247 | 248 | err = acc.SetContractAccount("XC1234567812345678@xuper") 249 | if err != nil { 250 | t.Error(err) 251 | } 252 | 253 | ar := acc.GetAuthRequire() 254 | if ar != "XC1234567812345678@xuper/"+acc.Address { 255 | t.Error("account authRequire assert failed") 256 | } 257 | 258 | acc.RemoveContractAccount() 259 | if acc.HasContractAccount() { 260 | t.Error("Remove contract account test failed") 261 | } 262 | ar = acc.GetAuthRequire() 263 | if ar != acc.Address { 264 | t.Error("account authRequire assert failed") 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /account/account.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // Package account is related to account operation. 4 | // You can reate account and mnemonic, or save account private to file. 5 | // You can set contract account for account if you want to deploy contract. 6 | package account 7 | 8 | import ( 9 | "io/ioutil" 10 | "log" 11 | "path/filepath" 12 | "regexp" 13 | 14 | "github.com/xuperchain/xuper-sdk-go/v2/common" 15 | "github.com/xuperchain/xuper-sdk-go/v2/crypto" 16 | ) 17 | 18 | // Account account structure 19 | type Account struct { 20 | contractAccount string 21 | 22 | Address string 23 | PrivateKey string 24 | PublicKey string 25 | Mnemonic string 26 | } 27 | 28 | // CreateAccount create an account. 29 | // 30 | //Parameters: 31 | // - `strength`:1弱(12个助记词),2中(18个助记词),3强(24个助记词)。 32 | // - `language`:1中文,2英文。 33 | func CreateAccount(strength uint8, language int) (*Account, error) { 34 | cli := crypto.GetCryptoClient() 35 | ecdsaAccount, err := cli.CreateNewAccountWithMnemonic(language, strength) 36 | if err != nil { 37 | log.Printf("CreateAccount CreateNewAccountWithMnemonic err: %v", err) 38 | return nil, err 39 | } 40 | 41 | account := &Account{ 42 | Address: ecdsaAccount.Address, 43 | PublicKey: ecdsaAccount.JsonPublicKey, 44 | PrivateKey: ecdsaAccount.JsonPrivateKey, 45 | Mnemonic: ecdsaAccount.Mnemonic, 46 | } 47 | return account, nil 48 | } 49 | 50 | // RetrieveAccount retrieve account from mnemonic. 51 | // Parameters: 52 | // - `mnemonic`: 助记词,例如:"玉 脸 驱 协 介 跨 尔 籍 杆 伏 愈 即"。 53 | // - `language`: 1中文,2英文。 54 | func RetrieveAccount(mnemonic string, language int) (*Account, error) { 55 | cli := crypto.GetCryptoClient() 56 | ecdsaAccount, err := cli.RetrieveAccountByMnemonic(mnemonic, language) 57 | if err != nil { 58 | return nil, err 59 | } 60 | account := &Account{ 61 | Address: ecdsaAccount.Address, 62 | PublicKey: ecdsaAccount.JsonPublicKey, 63 | PrivateKey: ecdsaAccount.JsonPrivateKey, 64 | Mnemonic: ecdsaAccount.Mnemonic, 65 | } 66 | return account, nil 67 | } 68 | 69 | // CreateAndSaveAccountToFile create an account and save to file. 70 | // 71 | // Parameters: 72 | // - `path`:保存路径。 73 | // - `passwd`: 密码。 74 | // - `strength`:助记词强度。 75 | // - `language`:助记词语言。 76 | func CreateAndSaveAccountToFile(path, passwd string, strength uint8, language int) (*Account, error) { 77 | cli := crypto.GetCryptoClient() 78 | ecdsaAccount, err := cli.CreateNewAccountWithMnemonic(language, strength) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | err = common.PathExistsAndMkdir(path) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | _, err = cli.RetrieveAccountByMnemonicAndSavePrivKey(path, language, ecdsaAccount.Mnemonic, passwd) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | account := &Account{ 94 | Address: ecdsaAccount.Address, 95 | PublicKey: ecdsaAccount.JsonPublicKey, 96 | PrivateKey: ecdsaAccount.JsonPrivateKey, 97 | Mnemonic: ecdsaAccount.Mnemonic, 98 | } 99 | return account, nil 100 | } 101 | 102 | // GetAccountFromPlainFile import account from plain files which are JSON encoded 103 | // 104 | // 指定路径下的结构如下: 105 | // - keys 106 | // |-- address 107 | // |-- private.key 108 | // |-- public.key 109 | func GetAccountFromPlainFile(path string) (*Account, error) { 110 | addr, err := ioutil.ReadFile(filepath.Join(path, "address")) 111 | if err != nil { 112 | log.Printf("GetAccountFromPlainFile error load address error = %v", err) 113 | return nil, err 114 | } 115 | pubkey, err := ioutil.ReadFile(filepath.Join(path, "public.key")) 116 | if err != nil { 117 | log.Printf("GetAccountFromPlainFile error load pubkey error = %v", err) 118 | return nil, err 119 | } 120 | prikey, err := ioutil.ReadFile(filepath.Join(path, "private.key")) 121 | if err != nil { 122 | log.Printf("GetAccountFromPlainFile error load prikey error = %v", err) 123 | return nil, err 124 | } 125 | 126 | account := &Account{ 127 | Address: string(addr), 128 | PublicKey: string(pubkey), 129 | PrivateKey: string(prikey), 130 | } 131 | return account, nil 132 | } 133 | 134 | // GetAccountFromFile get an account from file and password. 135 | func GetAccountFromFile(path, passwd string) (*Account, error) { 136 | cryptoClient := crypto.GetCryptoClient() 137 | ecdsaPrivateKey, err := cryptoClient.GetEcdsaPrivateKeyFromFileByPassword(path, passwd) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | account := &Account{} 143 | account.PrivateKey, err = cryptoClient.GetEcdsaPrivateKeyJsonFormatStr(ecdsaPrivateKey) 144 | if err != nil { 145 | return nil, err 146 | } 147 | account.PublicKey, err = cryptoClient.GetEcdsaPublicKeyJsonFormatStr(ecdsaPrivateKey) 148 | if err != nil { 149 | return nil, err 150 | } 151 | account.Address, err = cryptoClient.GetAddressFromPublicKey(&ecdsaPrivateKey.PublicKey) 152 | if err != nil { 153 | return nil, err 154 | } 155 | return account, err 156 | } 157 | 158 | // SetContractAccount set contract account. 159 | // If you set contract account, this account represents the contract account. 160 | // In some scenarios, must set contract account, such as deploy contract. 161 | func (a *Account) SetContractAccount(contractAccount string) error { 162 | if ok, _ := regexp.MatchString(`^XC\d{16}@*`, contractAccount); !ok { 163 | return common.ErrInvalidContractAccount 164 | } 165 | 166 | a.contractAccount = contractAccount 167 | return nil 168 | } 169 | 170 | // RemoveContractAccount remove contract account from this account. 171 | func (a *Account) RemoveContractAccount() { 172 | a.contractAccount = "" 173 | } 174 | 175 | // GetAuthRequire get this account's authRequire for transaction. 176 | // If you set contract account, returns $ContractAccount+"/"+$Address, otherwise returns $Address. 177 | func (a *Account) GetAuthRequire() string { 178 | if a.HasContractAccount() { 179 | return a.GetContractAccount() + "/" + a.Address 180 | } 181 | return a.Address 182 | } 183 | 184 | // GetContractAccount get current contract account, returns an empty string if the contract account is not set. 185 | func (a *Account) GetContractAccount() string { 186 | return a.contractAccount 187 | } 188 | 189 | // HasContractAccount reutrn true if you set contract account, otherwise returns false. 190 | func (a *Account) HasContractAccount() bool { 191 | return a.contractAccount != "" 192 | } 193 | -------------------------------------------------------------------------------- /account/address_trans.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "crypto/sha256" 5 | "errors" 6 | "fmt" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/btcsuite/btcutil/base58" 11 | "github.com/hyperledger/burrow/binary" 12 | "github.com/hyperledger/burrow/crypto" 13 | ) 14 | 15 | const ( 16 | evmAddressFiller = "-" 17 | 18 | contractNamePrefixs = "1111" 19 | contractAccountPrefixs = "1112" 20 | 21 | accountPrefix = "XC" 22 | accountBcnameSep = "@" 23 | accountSize = 16 24 | contractNameMaxSize = 16 25 | contractNameMinSize = 4 26 | 27 | // XchainAddrType xchain AK 地址类型 28 | XchainAddrType = "xchain" 29 | 30 | // ContractNameType 合约名字地址类型 31 | ContractNameType = "contract-name" 32 | 33 | // ContractAccountType 合约账户地址类型 34 | ContractAccountType = "contract-account" 35 | ) 36 | 37 | var ( 38 | contractNameRegex = regexp.MustCompile("^[a-zA-Z_]{1}[0-9a-zA-Z_.]+[0-9a-zA-Z_]$") 39 | ) 40 | 41 | // XchainToEVMAddress xchain address transfer to evm address: xchainAddr can be xchain contract account, AK address, xchain contract name. 42 | // 43 | // Return: evm address, address type, error. 44 | func XchainToEVMAddress(xchainAddr string) (string, string, error) { 45 | var addr crypto.Address 46 | var addrType string 47 | var err error 48 | if determineContractAccount(xchainAddr) { 49 | addr, err = contractAccountToEVMAddress(xchainAddr) 50 | addrType = ContractAccountType 51 | } else if determineContractName(xchainAddr) == nil { 52 | addr, err = contractNameToEVMAddress(xchainAddr) 53 | addrType = ContractNameType 54 | } else { 55 | addr, err = xchainAKToEVMAddress(xchainAddr) 56 | addrType = XchainAddrType 57 | } 58 | 59 | if err != nil { 60 | return "", "", err 61 | } 62 | 63 | return addr.String(), addrType, nil 64 | } 65 | 66 | // EVMToXchainAddress evm address transfer to xchain address: evmAddr can be evm contract account, AK address, xchain contract name. 67 | // 68 | // Return: xchain address, address type, error. 69 | func EVMToXchainAddress(evmAddr string) (string, string, error) { 70 | eAddr, err := crypto.AddressFromHexString(evmAddr) 71 | if err != nil { 72 | return "", "", err 73 | } 74 | 75 | evmAddrWithPrefix := eAddr.Bytes() 76 | evmAddrStrWithPrefix := string(evmAddrWithPrefix) 77 | 78 | var addr, addrType string 79 | if evmAddrStrWithPrefix[0:4] == contractAccountPrefixs { 80 | addr, err = evmAddressToContractAccount(eAddr) 81 | addrType = ContractAccountType 82 | } else if evmAddrStrWithPrefix[0:4] == contractNamePrefixs { 83 | addr, err = evmAddressToContractName(eAddr) 84 | addrType = ContractNameType 85 | } else { 86 | addr, err = evmAddressToXchain(eAddr) 87 | addrType = XchainAddrType 88 | } 89 | 90 | if err != nil { 91 | return "", "", err 92 | } 93 | 94 | return addr, addrType, nil 95 | } 96 | 97 | // transfer xchain address to evm address 98 | func xchainAKToEVMAddress(addr string) (crypto.Address, error) { 99 | rawAddr := base58.Decode(addr) 100 | if len(rawAddr) < 21 { 101 | return crypto.ZeroAddress, errors.New("bad address") 102 | } 103 | ripemd160Hash := rawAddr[1:21] 104 | 105 | return crypto.AddressFromBytes(ripemd160Hash) 106 | } 107 | 108 | // transfer evm address to xchain address 109 | func evmAddressToXchain(evmAddress crypto.Address) (string, error) { 110 | addrType := 1 111 | nVersion := uint8(addrType) 112 | bufVersion := []byte{byte(nVersion)} 113 | 114 | outputRipemd160 := evmAddress.Bytes() 115 | 116 | strSlice := make([]byte, len(bufVersion)+len(outputRipemd160)) 117 | copy(strSlice, bufVersion) 118 | copy(strSlice[len(bufVersion):], outputRipemd160) 119 | 120 | checkCode := DoubleSha256(strSlice) 121 | simpleCheckCode := checkCode[:4] 122 | slice := make([]byte, len(strSlice)+len(simpleCheckCode)) 123 | copy(slice, strSlice) 124 | copy(slice[len(strSlice):], simpleCheckCode) 125 | 126 | return base58.Encode(slice), nil 127 | } 128 | 129 | // transfer contract name to evm address 130 | func contractNameToEVMAddress(contractName string) (crypto.Address, error) { 131 | contractNameLength := len(contractName) 132 | var prefixStr string 133 | for i := 0; i < binary.Word160Length-contractNameLength-4; i++ { 134 | prefixStr += evmAddressFiller 135 | } 136 | contractName = prefixStr + contractName 137 | contractName = contractNamePrefixs + contractName 138 | 139 | return crypto.AddressFromBytes([]byte(contractName)) 140 | } 141 | 142 | // transfer evm address to contract name 143 | func evmAddressToContractName(evmAddr crypto.Address) (string, error) { 144 | contractNameWithPrefix := evmAddr.Bytes() 145 | contractNameStrWithPrefix := string(contractNameWithPrefix) 146 | prefixIndex := strings.LastIndex(contractNameStrWithPrefix, evmAddressFiller) 147 | 148 | return contractNameStrWithPrefix[prefixIndex+1:], nil 149 | } 150 | 151 | // transfer contract account to evm address 152 | func contractAccountToEVMAddress(contractAccount string) (crypto.Address, error) { 153 | contractAccountValid := contractAccount[2:18] 154 | contractAccountValid = contractAccountPrefixs + contractAccountValid 155 | 156 | return crypto.AddressFromBytes([]byte(contractAccountValid)) 157 | } 158 | 159 | // transfer evm address to contract account 160 | func evmAddressToContractAccount(evmAddr crypto.Address) (string, error) { 161 | contractNameWithPrefix := evmAddr.Bytes() 162 | contractNameStrWithPrefix := string(contractNameWithPrefix) 163 | 164 | return accountPrefix + contractNameStrWithPrefix[4:] + "@xuper", nil 165 | } 166 | 167 | // determine whether it is a contract account 168 | func determineContractAccount(account string) bool { 169 | if isAccount(account) != 1 { 170 | return false 171 | } 172 | 173 | return strings.Index(account, "@xuper") != -1 174 | } 175 | 176 | func isAccount(name string) int { 177 | if name == "" { 178 | return -1 179 | } 180 | if !strings.HasPrefix(name, accountPrefix) { 181 | return 0 182 | } 183 | prefix := strings.Split(name, "@")[0] 184 | prefix = prefix[len(accountPrefix):] 185 | if err := validRawAccount(prefix); err != nil { 186 | return 0 187 | } 188 | 189 | return 1 190 | } 191 | 192 | // ValidRawAccount validate account number 193 | func validRawAccount(accountName string) error { 194 | // param absence check 195 | if accountName == "" { 196 | return fmt.Errorf("invoke NewAccount failed, account name is empty") 197 | } 198 | 199 | // account naming rule check 200 | if len(accountName) != accountSize { 201 | return fmt.Errorf("invoke NewAccount failed, account name length expect %d, actual: %d", accountSize, len(accountName)) 202 | } 203 | 204 | for i := 0; i < accountSize; i++ { 205 | if accountName[i] >= '0' && accountName[i] <= '9' { 206 | continue 207 | } else { 208 | return fmt.Errorf("invoke NewAccount failed, account name expect continuous %d number", accountSize) 209 | } 210 | } 211 | 212 | return nil 213 | } 214 | 215 | // determine whether it is a contract name 216 | func determineContractName(contractName string) error { 217 | return validContractName(contractName) 218 | } 219 | 220 | func validContractName(contractName string) error { 221 | // param absence check 222 | // contract naming rule check 223 | contractSize := len(contractName) 224 | contractMaxSize := contractNameMaxSize 225 | contractMinSize := contractNameMinSize 226 | 227 | if contractSize > contractMaxSize || contractSize < contractMinSize { 228 | return fmt.Errorf("contract name length expect [%d~%d], actual: %d", contractMinSize, contractMaxSize, contractSize) 229 | } 230 | 231 | if !contractNameRegex.MatchString(contractName) { 232 | return fmt.Errorf("contract name does not fit the rule of contract name") 233 | } 234 | 235 | return nil 236 | } 237 | 238 | // UsingSha256 get the hash result of data using SHA256 239 | func UsingSha256(data []byte) []byte { 240 | h := sha256.New() 241 | h.Write(data) 242 | out := h.Sum(nil) 243 | 244 | return out 245 | } 246 | 247 | // DoubleSha256 执行2次SHA256,这是为了防止SHA256算法被攻破。 248 | func DoubleSha256(data []byte) []byte { 249 | return UsingSha256(UsingSha256(data)) 250 | } 251 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Baidu, Inc. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | 181 | Copyright 2019 Baidu, Inc. 182 | 183 | Licensed under the Apache License, Version 2.0 (the "License"); 184 | you may not use this file except in compliance with the License. 185 | You may obtain a copy of the License at 186 | 187 | http://www.apache.org/licenses/LICENSE-2.0 188 | 189 | Unless required by applicable law or agreed to in writing, software 190 | distributed under the License is distributed on an "AS IS" BASIS, 191 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 192 | See the License for the specific language governing permissions and 193 | limitations under the License. -------------------------------------------------------------------------------- /xuper/query.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/xuperchain/xuper-sdk-go/v2/common" 11 | "github.com/xuperchain/xuperchain/service/pb" 12 | ) 13 | 14 | func initQueryOpts(opts ...QueryOption) (*queryOption, error) { 15 | opt := &queryOption{} 16 | for _, param := range opts { 17 | err := param(opt) 18 | if err != nil { 19 | return nil, fmt.Errorf("option failed: %v", err) 20 | } 21 | } 22 | 23 | return opt, nil 24 | } 25 | 26 | func getBCname(opt *queryOption) string { 27 | chainName := defaultChainName 28 | if opt.bcname != "" { 29 | chainName = opt.bcname 30 | } 31 | return chainName 32 | } 33 | 34 | func (x *XClient) queryTxByID(txID string, opts ...QueryOption) (*pb.Transaction, error) { 35 | rawTx, err := hex.DecodeString(txID) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | opt, err := initQueryOpts(opts...) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | txStatus := &pb.TxStatus{ 46 | Bcname: getBCname(opt), 47 | Txid: rawTx, 48 | } 49 | res, err := x.xc.QueryTx(context.TODO(), txStatus) 50 | if err != nil { 51 | return nil, err 52 | } 53 | if res.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 54 | return nil, errors.New(res.GetHeader().GetError().String()) 55 | } 56 | if res.Tx == nil { 57 | return nil, common.ErrTxNotFound 58 | } 59 | return res.Tx, nil 60 | } 61 | 62 | func (x *XClient) queryBlockByID(blockID string, opts ...QueryOption) (*pb.Block, error) { 63 | rawBlockid, err := hex.DecodeString(blockID) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | opt, err := initQueryOpts(opts...) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | blockIDPB := &pb.BlockID{ 74 | Bcname: getBCname(opt), 75 | Blockid: rawBlockid, 76 | NeedContent: true, 77 | } 78 | 79 | block, err := x.xc.GetBlock(context.TODO(), blockIDPB) 80 | if err != nil { 81 | return nil, err 82 | } 83 | if block.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 84 | return nil, errors.New(block.GetHeader().GetError().String()) 85 | } 86 | if block.Block == nil { 87 | return nil, errors.New("block not found") 88 | } 89 | 90 | return block, nil 91 | } 92 | 93 | // queryContractCount query the number of contracts 94 | func (x *XClient) queryContractCount(opts ...QueryOption) (*pb.ContractStatDataResponse, error) { 95 | opt, err := initQueryOpts(opts...) 96 | if err != nil { 97 | return nil, err 98 | } 99 | request := &pb.ContractStatDataRequest{ 100 | Header: &pb.Header{}, 101 | Bcname: getBCname(opt), 102 | } 103 | 104 | reply, err := x.xc.QueryContractStatData(context.TODO(), request) 105 | if err != nil { 106 | return nil, err 107 | } 108 | return reply, nil 109 | } 110 | 111 | func (x *XClient) queryBlockByHeight(height int64, opts ...QueryOption) (*pb.Block, error) { 112 | opt, err := initQueryOpts(opts...) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | blockHeightPB := &pb.BlockHeight{ 118 | Bcname: getBCname(opt), 119 | Height: height, 120 | } 121 | 122 | block, err := x.xc.GetBlockByHeight(context.TODO(), blockHeightPB) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | if block.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 128 | return nil, errors.New(block.GetHeader().GetError().String()) 129 | } 130 | if block.Block == nil { 131 | return nil, errors.New("block not found") 132 | } 133 | 134 | return block, nil 135 | } 136 | 137 | func (x *XClient) queryAccountACL(account string, opts ...QueryOption) (*ACL, error) { 138 | opt, err := initQueryOpts(opts...) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | in := &pb.AclStatus{ 144 | Bcname: getBCname(opt), 145 | AccountName: account, 146 | } 147 | aclStatus, err := x.xc.QueryACL(context.TODO(), in) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | if aclStatus.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 153 | return nil, errors.New(aclStatus.GetHeader().GetError().String()) 154 | } 155 | 156 | acl := &ACL{} 157 | pm := PermissionModel{} 158 | pm.Rule = int32(aclStatus.GetAcl().GetPm().GetRule()) 159 | pm.AcceptValue = aclStatus.GetAcl().GetPm().GetAcceptValue() 160 | 161 | acl.PM = pm 162 | acl.AksWeight = aclStatus.GetAcl().GetAksWeight() 163 | return acl, nil 164 | 165 | } 166 | 167 | func (x *XClient) queryMethodACL(name, method string, opts ...QueryOption) (*ACL, error) { // todo 168 | opt, err := initQueryOpts(opts...) 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | in := &pb.AclStatus{ 174 | Bcname: getBCname(opt), 175 | ContractName: name, 176 | MethodName: method, 177 | } 178 | 179 | aclStatus, err := x.xc.QueryACL(context.TODO(), in) 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | if aclStatus.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 185 | return nil, errors.New(aclStatus.GetHeader().GetError().String()) 186 | } 187 | 188 | if aclStatus == nil { 189 | return nil, nil 190 | } 191 | 192 | acl := &ACL{} 193 | pm := PermissionModel{} 194 | pm.Rule = int32(aclStatus.GetAcl().GetPm().GetRule()) 195 | pm.AcceptValue = aclStatus.GetAcl().GetPm().GetAcceptValue() 196 | 197 | acl.PM = pm 198 | acl.AksWeight = aclStatus.GetAcl().GetAksWeight() 199 | return acl, nil 200 | } 201 | 202 | func (x *XClient) queryAccountContracts(account string, opts ...QueryOption) ([]*pb.ContractStatus, error) { 203 | opt, err := initQueryOpts(opts...) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | req := &pb.GetAccountContractsRequest{ 209 | Bcname: getBCname(opt), 210 | Account: account, 211 | } 212 | 213 | resp, err := x.xc.GetAccountContracts(context.TODO(), req) 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | if resp.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 219 | return nil, errors.New(resp.GetHeader().GetError().String()) 220 | } 221 | 222 | return resp.GetContractsStatus(), nil 223 | } 224 | 225 | func (x *XClient) queryAddressContracts(address string, opts ...QueryOption) (map[string]*pb.ContractList, error) { 226 | opt, err := initQueryOpts(opts...) 227 | if err != nil { 228 | return nil, err 229 | } 230 | 231 | req := &pb.AddressContractsRequest{ 232 | Address: address, 233 | Bcname: getBCname(opt), 234 | } 235 | 236 | resp, err := x.xc.GetAddressContracts(context.TODO(), req) 237 | if err != nil { 238 | return nil, err 239 | } 240 | 241 | if resp.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 242 | return nil, errors.New(resp.GetHeader().GetError().String()) 243 | } 244 | 245 | return resp.GetContracts(), nil 246 | } 247 | 248 | func (x *XClient) queryBalance(address string, opts ...QueryOption) (*big.Int, error) { 249 | opt, err := initQueryOpts(opts...) 250 | if err != nil { 251 | return nil, err 252 | } 253 | 254 | bcname := getBCname(opt) 255 | addrstatus := &pb.AddressStatus{ 256 | Address: address, 257 | Bcs: []*pb.TokenDetail{ 258 | {Bcname: bcname}, 259 | }, 260 | } 261 | 262 | reply, err := x.xc.GetBalance(context.TODO(), addrstatus) 263 | if err != nil { 264 | return nil, err 265 | } 266 | 267 | if reply.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 268 | return nil, errors.New(reply.GetHeader().GetError().String()) 269 | } 270 | 271 | for _, v := range reply.Bcs { 272 | if v.GetBcname() == bcname { 273 | if v.GetError() != pb.XChainErrorEnum_SUCCESS { 274 | return nil, errors.New(v.GetError().String()) 275 | } 276 | 277 | if v.GetBalance() == "" { 278 | return big.NewInt(0), nil 279 | } 280 | bal, ok := big.NewInt(0).SetString(v.GetBalance(), 10) 281 | if !ok { 282 | return nil, errors.New("invalid balance query from chain") 283 | } 284 | return bal, nil 285 | } 286 | } 287 | 288 | return nil, errors.New("invalid bcname:" + bcname) 289 | } 290 | 291 | // BalanceDetail address or account balance detailds. 292 | type BalanceDetail struct { 293 | Balance string 294 | IsFrozen bool 295 | } 296 | 297 | func (x *XClient) queryBalanceDetail(address string, opts ...QueryOption) ([]*BalanceDetail, error) { 298 | opt, err := initQueryOpts(opts...) 299 | if err != nil { 300 | return nil, err 301 | } 302 | tfds := []*pb.TokenFrozenDetails{{Bcname: getBCname(opt)}} 303 | ctx := context.Background() 304 | addressBalanceStatus := &pb.AddressBalanceStatus{ 305 | Address: address, 306 | Tfds: tfds, 307 | } 308 | 309 | bs, err := x.xc.GetBalanceDetail(ctx, addressBalanceStatus) 310 | if err != nil { 311 | return nil, err 312 | } 313 | 314 | if bs.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 315 | return nil, errors.New(bs.GetHeader().GetError().String()) 316 | } 317 | 318 | bcname := getBCname(opt) 319 | for _, tfd := range bs.Tfds { 320 | if tfd.Bcname == bcname { 321 | if tfd.GetError() != pb.XChainErrorEnum_SUCCESS { 322 | return nil, errors.New(bs.GetHeader().GetError().String()) 323 | } 324 | 325 | result := make([]*BalanceDetail, 0, len(tfd.Tfd)) 326 | for _, v := range tfd.Tfd { 327 | result = append(result, &BalanceDetail{ 328 | Balance: v.Balance, 329 | IsFrozen: v.IsFrozen, 330 | }) 331 | } 332 | 333 | return result, nil 334 | } 335 | } 336 | 337 | return nil, fmt.Errorf("Can not query balance detail for bcname: %s", bcname) 338 | } 339 | 340 | func (x *XClient) querySystemStatus(opts ...QueryOption) (*pb.SystemsStatusReply, error) { 341 | ss, err := x.xc.GetSystemStatus(context.TODO(), &pb.CommonIn{}) 342 | if err != nil { 343 | return nil, err 344 | } 345 | 346 | if ss.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 347 | return nil, errors.New(ss.GetHeader().GetError().String()) 348 | } 349 | return ss, nil 350 | } 351 | 352 | func (x *XClient) queryBlockChains(opts ...QueryOption) ([]string, error) { 353 | bcs, err := x.xc.GetBlockChains(context.TODO(), &pb.CommonIn{}) 354 | if err != nil { 355 | return nil, err 356 | } 357 | 358 | if bcs.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 359 | return nil, errors.New(bcs.GetHeader().GetError().String()) 360 | } 361 | 362 | return bcs.GetBlockchains(), nil 363 | } 364 | 365 | func (x *XClient) queryBlockChainStatus(opts ...QueryOption) (*pb.BCStatus, error) { 366 | opt, err := initQueryOpts(opts...) 367 | if err != nil { 368 | return nil, err 369 | } 370 | 371 | bcStatusPB := &pb.BCStatus{ 372 | Bcname: getBCname(opt), 373 | } 374 | 375 | bcs, err := x.xc.GetBlockChainStatus(context.TODO(), bcStatusPB) 376 | if err != nil { 377 | return nil, err 378 | } 379 | 380 | if bcs.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 381 | return nil, errors.New(bcs.GetHeader().GetError().String()) 382 | } 383 | 384 | return bcs, err 385 | } 386 | 387 | func (x *XClient) queryNetURL(opts ...QueryOption) (string, error) { 388 | rawURL, err := x.xc.GetNetURL(context.TODO(), &pb.CommonIn{}) 389 | if err != nil { 390 | return "", err 391 | } 392 | 393 | if rawURL.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 394 | return "", errors.New(rawURL.GetHeader().GetError().String()) 395 | } 396 | 397 | return rawURL.GetRawUrl(), nil 398 | } 399 | 400 | func (x *XClient) queryAccountByAK(address string, opts ...QueryOption) ([]string, error) { 401 | opt, err := initQueryOpts(opts...) 402 | if err != nil { 403 | return nil, err 404 | } 405 | 406 | AK2AccountRequest := &pb.AK2AccountRequest{ 407 | Bcname: getBCname(opt), 408 | Address: address, 409 | } 410 | 411 | resp, err := x.xc.GetAccountByAK(context.TODO(), AK2AccountRequest) 412 | if err != nil { 413 | return nil, err 414 | } 415 | 416 | if resp.GetHeader().GetError() != pb.XChainErrorEnum_SUCCESS { 417 | return nil, errors.New(resp.GetHeader().GetError().String()) 418 | } 419 | return resp.GetAccount(), nil 420 | } 421 | -------------------------------------------------------------------------------- /xuper/request.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/golang/protobuf/proto" 8 | "github.com/pkg/errors" 9 | 10 | "github.com/xuperchain/xuper-sdk-go/v2/account" 11 | "github.com/xuperchain/xuper-sdk-go/v2/common" 12 | "github.com/xuperchain/xuperchain/service/pb" 13 | ) 14 | 15 | // Request xuperchain transaction request. 16 | type Request struct { 17 | initiatorAccount *account.Account 18 | 19 | // contract parameters, kernel or user contract. 20 | module string 21 | contractName string 22 | methodName string 23 | 24 | // 重要:这个 args 是 pb.InvokeRpcRequest 里面的 args,xuperclient 和 request 参数中的 args 为合约调用的 args。 25 | args map[string][]byte 26 | 27 | // transfer parameters. 28 | transferTo string 29 | transferAmount string 30 | 31 | opt *requestOptions 32 | } 33 | 34 | const ( 35 | // NativeContractModule native contract module. 36 | NativeContractModule = "native" 37 | // WasmContractModule wasm contract module. 38 | WasmContractModule = "wasm" 39 | // EvmContractModule evm contract module. 40 | EvmContractModule = "evm" 41 | 42 | // GoRuntime go contract runtime. 43 | GoRuntime = "go" 44 | // CRuntime c++ contract runtime. 45 | CRuntime = "c" 46 | // JavaRuntime java contract runtime. 47 | JavaRuntime = "java" 48 | 49 | // EvmJSONEncoded evm contract invoke abi encoded. 50 | EvmJSONEncoded = "jsonEncoded" 51 | // EvmJSONEncodedTrue evm contract invoke abi encoded. 52 | EvmJSONEncodedTrue = "true" 53 | 54 | // XkernelModule xkernel contract module 55 | XkernelModule = "kernel" 56 | // Xkernel3Module xkernel contract module 57 | Xkernel3Module = "xkernel" 58 | // XkernelDeployMethod xkernel contract deploy contract method. 59 | XkernelDeployMethod = "Deploy" 60 | // XkernelUpgradeMethod xkernel contract upgrade contract method. 61 | XkernelUpgradeMethod = "Upgrade" 62 | // XkernelNewAccountMethod xkernel contract create contract account method. 63 | XkernelNewAccountMethod = "NewAccount" 64 | // XkernelSetAccountACLMethod xkernel contract set account ACL method. 65 | XkernelSetAccountACLMethod = "SetAccountAcl" 66 | // XkernelSetMethodACLMethod xkernel contract set method ACL method. 67 | XkernelSetMethodACLMethod = "SetMethodAcl" 68 | 69 | // ArgAccountName account name field. 70 | ArgAccountName = "account_name" 71 | // ArgContractName contract name field. 72 | ArgContractName = "contract_name" 73 | // ArgContractCode contract code field. 74 | ArgContractCode = "contract_code" 75 | // ArgContractDesc contract desc field. 76 | ArgContractDesc = "contract_desc" 77 | // ArgInitArgs contract init args field. 78 | ArgInitArgs = "init_args" 79 | // ArgContractAbi evm abi field. 80 | ArgContractAbi = "contract_abi" 81 | ) 82 | 83 | func initOpts(opts ...RequestOption) (*requestOptions, error) { 84 | opt := &requestOptions{} 85 | for _, param := range opts { 86 | err := param(opt) 87 | if err != nil { 88 | return nil, fmt.Errorf("option failed: %v", err) 89 | } 90 | } 91 | return opt, nil 92 | } 93 | 94 | // NewRequest new custom request. 95 | func NewRequest( 96 | initiator *account.Account, 97 | module, contractName, methodName string, 98 | args map[string][]byte, 99 | transferTo, transferAmount string, 100 | opts ...RequestOption, 101 | ) (*Request, error) { 102 | 103 | if initiator == nil { 104 | return nil, errors.New("initiator can not be nil") 105 | } 106 | 107 | opt, err := initOpts(opts...) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | if opt.onlyFeeFromAccount && !initiator.HasContractAccount() { 113 | return nil, errors.Wrap(common.ErrInvalidAccount, 114 | "initiator contract account can not be nil when set fee from account.") 115 | } 116 | 117 | return &Request{ 118 | initiatorAccount: initiator, 119 | module: module, 120 | contractName: contractName, 121 | methodName: methodName, 122 | args: args, 123 | transferTo: transferTo, 124 | transferAmount: transferAmount, 125 | opt: opt, 126 | }, nil 127 | } 128 | 129 | // SetInitiatorAccount set request initiator. 130 | func (r *Request) SetInitiatorAccount(account *account.Account) error { 131 | r.initiatorAccount = account 132 | return nil 133 | } 134 | 135 | // SetArgs set request args. NOTE: this is pb.InvokeRPCRequest args, not contract invoke args. 136 | func (r *Request) SetArgs(args map[string][]byte) error { 137 | r.args = args 138 | return nil 139 | } 140 | 141 | // SetModule set request contract module. 142 | func (r *Request) SetModule(module string) error { 143 | r.module = module 144 | return nil 145 | } 146 | 147 | // SetContractName set 148 | func (r *Request) SetContractName(contractName string) error { 149 | r.contractName = contractName 150 | return nil 151 | } 152 | 153 | // SetTransferTo set 154 | func (r *Request) SetTransferTo(to string) error { 155 | r.transferTo = to 156 | return nil 157 | } 158 | 159 | // SetTransferAmount set 160 | func (r *Request) SetTransferAmount(amount string) error { 161 | r.transferAmount = amount 162 | return nil 163 | } 164 | 165 | // NewTransferRequest set 166 | func NewTransferRequest(from *account.Account, to, amount string, opts ...RequestOption) (*Request, error) { 167 | if from == nil { 168 | return nil, common.ErrInvalidInitiator 169 | } 170 | 171 | if to == "" { 172 | return nil, common.ErrInvalidParam 173 | } 174 | 175 | amount, ok := common.IsValidAmount(amount) 176 | if !ok { 177 | return nil, common.ErrInvalidAmount 178 | } 179 | 180 | return NewRequest(from, "", "", "", nil, to, amount, opts...) 181 | } 182 | 183 | // NewDeployContractRequest new request for deploy contract, wasm, evm and native. 184 | func NewDeployContractRequest(from *account.Account, name string, abi, code []byte, args map[string]string, contractType, runtime string, opts ...RequestOption) (*Request, error) { 185 | if from == nil || !from.HasContractAccount() { 186 | return nil, common.ErrInvalidAccount 187 | } 188 | 189 | if name == "" || contractType == "" || len(code) == 0 { 190 | return nil, common.ErrInvalidParam 191 | } 192 | 193 | reqArgs := generateDeployArgs(args, abi, code, contractType, runtime, from.GetContractAccount(), name) 194 | 195 | return NewRequest(from, Xkernel3Module, "", XkernelDeployMethod, reqArgs, "", "", opts...) 196 | } 197 | 198 | // NewInvokeContractRequest new request for invoke contract, wasm, evm and native. 199 | func NewInvokeContractRequest(from *account.Account, module, name, method string, args map[string]string, opts ...RequestOption) (*Request, error) { 200 | if from == nil { 201 | return nil, errors.New("invalid initiator") 202 | } 203 | 204 | if module == "" && name == "" && method == "" { 205 | return nil, common.ErrInvalidParam 206 | } 207 | 208 | reqArgs, err := generateInvokeArgs(args, module) 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | return NewRequest(from, module, name, method, reqArgs, "", "", opts...) 214 | } 215 | 216 | // NewUpgradeContractRequest new upgrade contract request. NOTE: evm contract upgrade disabled! 217 | func NewUpgradeContractRequest(from *account.Account, module, name string, code []byte, opts ...RequestOption) (*Request, error) { 218 | if from == nil || !from.HasContractAccount() { 219 | return nil, common.ErrInvalidAccount 220 | } 221 | 222 | if module == "" || name == "" || len(code) == 0 { 223 | return nil, common.ErrInvalidParam 224 | } 225 | 226 | reqArgs := generateDeployArgs(nil, nil, code, module, "", from.GetContractAccount(), name) 227 | return NewRequest(from, Xkernel3Module, "", XkernelUpgradeMethod, reqArgs, "", "", opts...) 228 | } 229 | 230 | // NewCreateContractAccountRequest new request for create contract account. 231 | func NewCreateContractAccountRequest(from *account.Account, contractAccount string, opts ...RequestOption) (*Request, error) { 232 | if from == nil || from.HasContractAccount() { 233 | return nil, common.ErrInvalidAccount 234 | } 235 | 236 | if contractAccount == "" { 237 | return nil, common.ErrInvalidAccount 238 | } 239 | 240 | args, err := genAccountACLArgs(getDefaultACL(from.Address), contractAccount) 241 | if err != nil { 242 | return nil, err 243 | } 244 | return NewRequest(from, Xkernel3Module, "", XkernelNewAccountMethod, args, "", "", opts...) 245 | } 246 | 247 | // NewSetMethodACLRequest new request for set method ACL. 248 | func NewSetMethodACLRequest(from *account.Account, name, method string, acl *ACL, opts ...RequestOption) (*Request, error) { 249 | if from == nil { 250 | return nil, common.ErrInvalidAccount 251 | } 252 | 253 | if acl == nil { 254 | return nil, errors.New("invalid ACL") 255 | } 256 | 257 | if method == "" || name == "" { 258 | return nil, common.ErrInvalidParam 259 | } 260 | 261 | args, err := genMethodACLArgs(acl, name, method) 262 | if err != nil { 263 | return nil, err 264 | } 265 | return NewRequest(from, Xkernel3Module, "", XkernelSetMethodACLMethod, args, "", "", opts...) 266 | } 267 | 268 | // NewSetAccountACLRequest new request for set contract account acl. 269 | func NewSetAccountACLRequest(from *account.Account, acl *ACL, opts ...RequestOption) (*Request, error) { 270 | if from == nil || !from.HasContractAccount() { 271 | return nil, common.ErrInvalidAccount 272 | } 273 | 274 | args, err := genAccountACLArgs(acl, from.GetContractAccount()) 275 | if err != nil { 276 | return nil, err 277 | } 278 | return NewRequest(from, Xkernel3Module, "", XkernelSetAccountACLMethod, args, "", "", opts...) 279 | } 280 | 281 | func generateDeployArgs(arg map[string]string, abi, code []byte, module, runtime, contractAccount, contractName string) map[string][]byte { 282 | argstmp := map[string][]byte{} 283 | if module == EvmContractModule { 284 | argsTmp := make(map[string]interface{}, len(arg)) 285 | for k, v := range arg { 286 | argsTmp[k] = v 287 | } 288 | argstmp, _ = convertToXuper3EvmArgs(argsTmp) 289 | } else { 290 | argstmp = convertToXuperContractArgs(arg) 291 | } 292 | 293 | initArgs, _ := json.Marshal(argstmp) 294 | 295 | desc := &pb.WasmCodeDesc{ 296 | ContractType: module, 297 | Runtime: runtime, 298 | } 299 | contractDesc, _ := proto.Marshal(desc) 300 | 301 | args := map[string][]byte{ 302 | "account_name": []byte(contractAccount), 303 | "contract_name": []byte(contractName), 304 | "contract_code": code, 305 | "contract_desc": contractDesc, 306 | "init_args": initArgs, 307 | } 308 | 309 | if module == EvmContractModule { 310 | args["contract_abi"] = abi 311 | } 312 | 313 | return args 314 | } 315 | 316 | func convertToXuperContractArgs(args map[string]string) map[string][]byte { 317 | argmap := make(map[string][]byte) 318 | for k, v := range args { 319 | argmap[k] = []byte(v) 320 | } 321 | return argmap 322 | } 323 | 324 | func convertToXuper3EvmArgs(args map[string]interface{}) (map[string][]byte, error) { 325 | input, err := json.Marshal(args) 326 | if err != nil { 327 | return nil, err 328 | } 329 | 330 | // 此处与 server 端结构相同,如果 jsonEncoded 字段修改,server 端也要修改(core/contract/evm/creator.go)。 331 | ret := map[string][]byte{ 332 | "input": input, 333 | EvmJSONEncoded: []byte(EvmJSONEncodedTrue), 334 | } 335 | return ret, nil 336 | } 337 | 338 | func generateInvokeArgs(arg map[string]string, module string) (map[string][]byte, error) { 339 | if module == EvmContractModule { 340 | // todo 341 | argsTmp := make(map[string]interface{}, len(arg)) 342 | for k, v := range arg { 343 | argsTmp[k] = v 344 | } 345 | return convertToXuper3EvmArgs(argsTmp) 346 | 347 | } 348 | return convertToXuperContractArgs(arg), nil 349 | } 350 | 351 | func genAccountACLArgs(acl *ACL, contractAccount string) (map[string][]byte, error) { 352 | ACLBytes, err := json.Marshal(acl) 353 | if err != nil { 354 | return nil, errors.New("invalid ACL") 355 | } 356 | 357 | args := map[string][]byte{ 358 | "account_name": []byte(contractAccount), 359 | "acl": ACLBytes, 360 | } 361 | return args, nil 362 | } 363 | 364 | func genMethodACLArgs(acl *ACL, name, method string) (map[string][]byte, error) { 365 | ACLStr, err := json.Marshal(acl) 366 | if err != nil { 367 | return nil, errors.New("invalid ACL") 368 | } 369 | 370 | args := map[string][]byte{ 371 | "contract_name": []byte(name), 372 | "method_name": []byte(method), 373 | "acl": []byte(ACLStr), 374 | } 375 | return args, nil 376 | } 377 | -------------------------------------------------------------------------------- /xuper/xuperclient.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019. Baidu Inc. All Rights Reserved. 2 | 3 | // Package xuper xuperchain client and generate tx or post tx. 4 | // 5 | // You can transfer to someone, deploy/invoke/query contract(wasm, native and evm), 6 | // create contract account or set contract account ACL or set contract method ACL, query info from node. 7 | // 8 | // If you need multisign, you can use Transaction sign method add signature, more example in xuper-sdk-go/example/. 9 | package xuper 10 | 11 | import ( 12 | "context" 13 | "crypto/tls" 14 | "crypto/x509" 15 | "fmt" 16 | "io" 17 | "io/ioutil" 18 | "log" 19 | "math/big" 20 | "regexp" 21 | 22 | "github.com/golang/protobuf/proto" 23 | "github.com/pkg/errors" 24 | 25 | "github.com/xuperchain/xuper-sdk-go/v2/account" 26 | "github.com/xuperchain/xuper-sdk-go/v2/common" 27 | "github.com/xuperchain/xuper-sdk-go/v2/common/config" 28 | "github.com/xuperchain/xuperchain/service/pb" 29 | 30 | "google.golang.org/grpc" 31 | "google.golang.org/grpc/credentials" 32 | "google.golang.org/grpc/encoding/gzip" 33 | ) 34 | 35 | // XClient xuperchain client. 36 | type XClient struct { 37 | node string 38 | xc pb.XchainClient 39 | xconn *grpc.ClientConn 40 | 41 | ec pb.XendorserClient 42 | esc pb.EventServiceClient 43 | econn *grpc.ClientConn 44 | 45 | cfg *config.CommConfig 46 | opt *clientOptions 47 | } 48 | 49 | // New new xuper client. 50 | // 51 | // Parameters: 52 | // - `node`: node GRPC URL. 53 | func New(node string, opts ...ClientOption) (*XClient, error) { 54 | opt := &clientOptions{} 55 | for _, param := range opts { 56 | err := param(opt) 57 | if err != nil { 58 | return nil, fmt.Errorf("option failed: %v", err) 59 | } 60 | } 61 | 62 | xclient := &XClient{ 63 | node: node, 64 | opt: opt, 65 | } 66 | 67 | err := xclient.init() 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return xclient, nil 73 | } 74 | 75 | func (x *XClient) init() error { 76 | var err error 77 | 78 | if x.opt.configFile != "" { 79 | x.cfg, err = config.GetConfig(x.opt.configFile) 80 | if err != nil { 81 | return err 82 | } 83 | } else { 84 | x.cfg = config.GetInstance() 85 | } 86 | 87 | // init xuper client, endorser client, grpc tls & gzip. 88 | return x.initConn() 89 | } 90 | 91 | func (x *XClient) initConn() error { 92 | grpcOpts := []grpc.DialOption{} 93 | 94 | if x.opt.grpcTLS != nil && x.opt.grpcTLS.serverName != "" { // TLS enabled 95 | certificate, err := tls.LoadX509KeyPair(x.opt.grpcTLS.certFile, x.opt.grpcTLS.keyFile) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | certPool := x509.NewCertPool() 101 | ca, err := ioutil.ReadFile(x.opt.grpcTLS.cacertFile) 102 | if err != nil { 103 | return err 104 | } 105 | if ok := certPool.AppendCertsFromPEM(ca); !ok { 106 | return errors.New("certPool add ca cert failed") 107 | } 108 | 109 | creds := credentials.NewTLS(&tls.Config{ 110 | Certificates: []tls.Certificate{certificate}, 111 | ServerName: x.opt.grpcTLS.serverName, 112 | RootCAs: certPool, 113 | }) 114 | 115 | grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(creds)) 116 | } else { 117 | grpcOpts = append(grpcOpts, grpc.WithInsecure()) 118 | } 119 | 120 | if x.opt.useGrpcGZIP { // gzip enabled 121 | grpcOpts = append(grpcOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) 122 | } 123 | maxRecvMshSize := 64<<20 - 1 124 | if x.cfg.MaxRecvMsgSize != 0 { 125 | maxRecvMshSize = x.cfg.MaxRecvMsgSize 126 | } 127 | grpcOpts = append(grpcOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxRecvMshSize))) 128 | 129 | conn, err := grpc.Dial( 130 | x.node, 131 | grpcOpts..., 132 | ) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | x.xconn = conn 138 | x.xc = pb.NewXchainClient(conn) 139 | x.esc = pb.NewEventServiceClient(conn) 140 | 141 | if x.cfg.ComplianceCheck.IsNeedComplianceCheck { // endorser no TLS, mayble future. 142 | econn, err := grpc.Dial(x.cfg.EndorseServiceHost, grpc.WithInsecure(), grpc.WithMaxMsgSize(64<<20-1)) 143 | if err != nil { 144 | return err 145 | } 146 | x.econn = econn 147 | x.ec = pb.NewXendorserClient(econn) 148 | } 149 | 150 | return nil 151 | } 152 | 153 | // Close close xuper client all connections. 154 | func (x *XClient) Close() error { 155 | if x.xc != nil && x.xconn != nil { 156 | err := x.xconn.Close() 157 | if err != nil { 158 | return err 159 | } 160 | } 161 | 162 | if x.ec != nil && x.econn != nil { 163 | err := x.econn.Close() 164 | if err != nil { 165 | return err 166 | } 167 | } 168 | return nil 169 | } 170 | 171 | // DeployNativeGoContract deploy native go contract. 172 | // 173 | // Parameters: 174 | // - `from`: Transaction initiator. 175 | // - `name`: Contract name. 176 | // - `code`: Contract code bytes. 177 | // - `args`: Contract init args. 178 | func (x *XClient) DeployNativeGoContract(from *account.Account, name string, code []byte, args map[string]string, opts ...RequestOption) (*Transaction, error) { 179 | req, err := NewDeployContractRequest(from, name, nil, code, args, NativeContractModule, GoRuntime, opts...) 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | return x.Do(req) 185 | } 186 | 187 | // DeployNativeJavaContract deploy native java contract. 188 | // 189 | // Parameters: 190 | // - `from`: Transaction initiator. 191 | // - `name`: Contract name. 192 | // - `code`: Contract code bytes. 193 | // - `args`: Contract init args. 194 | func (x *XClient) DeployNativeJavaContract(from *account.Account, name string, code []byte, args map[string]string, opts ...RequestOption) (*Transaction, error) { 195 | req, err := NewDeployContractRequest(from, name, nil, code, args, NativeContractModule, JavaRuntime, opts...) 196 | if err != nil { 197 | return nil, err 198 | } 199 | 200 | return x.Do(req) 201 | } 202 | 203 | // DeployWasmContract deploy wasm c++ contract. 204 | // 205 | // Parameters: 206 | // - `from`: Transaction initiator. 207 | // - `name`: Contract name. 208 | // - `code`: Contract code bytes. 209 | // - `args`: Contract init args. 210 | func (x *XClient) DeployWasmContract(from *account.Account, name string, code []byte, args map[string]string, opts ...RequestOption) (*Transaction, error) { 211 | req, err := NewDeployContractRequest(from, name, nil, code, args, WasmContractModule, CRuntime, opts...) 212 | if err != nil { 213 | return nil, err 214 | } 215 | 216 | return x.Do(req) 217 | } 218 | 219 | // DeployEVMContract deploy evm contract. 220 | // 221 | // Parameters: 222 | // - `from`: Transaction initiator. 223 | // - `name`: Contract name. 224 | // - `abi` : Solidity contract abi. 225 | // - `bin` : Solidity contract bin. 226 | // - `args`: Contract init args. 227 | func (x *XClient) DeployEVMContract(from *account.Account, name string, abi, bin []byte, args map[string]string, opts ...RequestOption) (*Transaction, error) { 228 | req, err := NewDeployContractRequest(from, name, abi, bin, args, EvmContractModule, "", opts...) 229 | if err != nil { 230 | return nil, err 231 | } 232 | 233 | return x.Do(req) 234 | } 235 | 236 | // UpgradeWasmContract upgrade wasm contract. 237 | // 238 | // Parameters: 239 | // - `from`: Transaction initiator. 240 | // - `name`: Contract name. 241 | // - `code`: Contract code bytes. 242 | // - `args`: Contract init args. 243 | func (x *XClient) UpgradeWasmContract(from *account.Account, name string, code []byte, opts ...RequestOption) (*Transaction, error) { 244 | req, err := NewUpgradeContractRequest(from, WasmContractModule, name, code, opts...) 245 | if err != nil { 246 | return nil, err 247 | } 248 | 249 | return x.Do(req) 250 | } 251 | 252 | // UpgradeNativeContract upgrade native contract. 253 | // 254 | // Parameters: 255 | // - `from`: Transaction initiator. 256 | // - `name`: Contract name. 257 | // - `code`: Contract code bytes. 258 | // - `args`: Contract init args. 259 | func (x *XClient) UpgradeNativeContract(from *account.Account, name string, code []byte, opts ...RequestOption) (*Transaction, error) { 260 | req, err := NewUpgradeContractRequest(from, NativeContractModule, name, code, opts...) 261 | if err != nil { 262 | return nil, err 263 | } 264 | 265 | return x.Do(req) 266 | } 267 | 268 | // InvokeWasmContract invoke wasm c++ contract. 269 | // 270 | // Parameters: 271 | // - `from` : Transaction initiator. 272 | // - `name` : Contract name. 273 | // - `method`: Contract method. 274 | // - `args` : Contract invoke args. 275 | func (x *XClient) InvokeWasmContract(from *account.Account, name, method string, args map[string]string, opts ...RequestOption) (*Transaction, error) { 276 | req, err := NewInvokeContractRequest(from, WasmContractModule, name, method, args, opts...) 277 | if err != nil { 278 | return nil, err 279 | } 280 | 281 | return x.Do(req) 282 | } 283 | 284 | // InvokeNativeContract invoke native contract. 285 | // 286 | // Parameters: 287 | // - `from` : Transaction initiator. 288 | // - `name` : Contract name. 289 | // - `method`: Contract method. 290 | // - `args` : Contract invoke args. 291 | func (x *XClient) InvokeNativeContract(from *account.Account, name, method string, args map[string]string, opts ...RequestOption) (*Transaction, error) { 292 | req, err := NewInvokeContractRequest(from, NativeContractModule, name, method, args, opts...) 293 | if err != nil { 294 | return nil, err 295 | } 296 | 297 | return x.Do(req) 298 | } 299 | 300 | // InvokeEVMContract invoke evm contract. 301 | // 302 | // Parameters: 303 | // - `from` : Transaction initiator. 304 | // - `name` : Contract name. 305 | // - `method`: Contract method. 306 | // - `args` : Contract invoke args. 307 | func (x *XClient) InvokeEVMContract(from *account.Account, name, method string, args map[string]string, opts ...RequestOption) (*Transaction, error) { 308 | req, err := NewInvokeContractRequest(from, EvmContractModule, name, method, args, opts...) 309 | if err != nil { 310 | return nil, err 311 | } 312 | 313 | return x.Do(req) 314 | } 315 | 316 | // QueryWasmContract query wasm c++ contract. 317 | // 318 | // Parameters: 319 | // - `from` : Transaction initiator. 320 | // - `name` : Contract name. 321 | // - `method`: Contract method. 322 | // - `args` : Contract invoke args. 323 | func (x *XClient) QueryWasmContract(from *account.Account, name, method string, args map[string]string, opts ...RequestOption) (*Transaction, error) { 324 | req, err := NewInvokeContractRequest(from, WasmContractModule, name, method, args, opts...) 325 | if err != nil { 326 | return nil, err 327 | } 328 | 329 | return x.PreExecTx(req) 330 | } 331 | 332 | // QueryNativeContract query native contract. 333 | // 334 | // Parameters: 335 | // - `from` : Transaction initiator. 336 | // - `name` : Contract name. 337 | // - `method`: Contract method. 338 | // - `args` : Contract invoke args. 339 | func (x *XClient) QueryNativeContract(from *account.Account, name, method string, args map[string]string, opts ...RequestOption) (*Transaction, error) { 340 | req, err := NewInvokeContractRequest(from, NativeContractModule, name, method, args, opts...) 341 | if err != nil { 342 | return nil, err 343 | } 344 | return x.PreExecTx(req) 345 | } 346 | 347 | // QueryEVMContract query evm contract. 348 | // 349 | // Parameters: 350 | // - `from` : Transaction initiator. 351 | // - `name` : Contract name. 352 | // - `method`: Contract method. 353 | // - `args` : Contract invoke args. 354 | func (x *XClient) QueryEVMContract(from *account.Account, name, method string, args map[string]string, opts ...RequestOption) (*Transaction, error) { 355 | req, err := NewInvokeContractRequest(from, EvmContractModule, name, method, args, opts...) 356 | if err != nil { 357 | return nil, err 358 | } 359 | return x.PreExecTx(req) 360 | } 361 | 362 | // Transfer to another address. 363 | // 364 | // Parameters: 365 | // - `from` : Transaction initiator. 366 | // - `to` : Transfer receiving address. 367 | // - `amount`: Transfer amount. 368 | func (x *XClient) Transfer(from *account.Account, to, amount string, opts ...RequestOption) (*Transaction, error) { 369 | req, err := NewTransferRequest(from, to, amount, opts...) 370 | if err != nil { 371 | return nil, err 372 | } 373 | 374 | return x.Do(req) 375 | } 376 | 377 | // CreateContractAccount create contract account for initiator. 378 | // 379 | // Parameters: 380 | // - `from` : Transaction initiator. NOTE: from must be NOT set contract account, if you set please remove it. 381 | // - `contractAccount`:The contract account you want to create, such as: XC8888888899999999@xuper. 382 | func (x *XClient) CreateContractAccount(from *account.Account, contractAccount string, opts ...RequestOption) (*Transaction, error) { 383 | if ok, _ := regexp.MatchString(`^XC\d{16}@*`, contractAccount); !ok { 384 | return nil, common.ErrInvalidContractAccount 385 | } 386 | 387 | subRegexp := regexp.MustCompile(`\d{16}`) 388 | contractAccountByte := subRegexp.Find([]byte(contractAccount)) 389 | contractAccount = string(contractAccountByte) 390 | req, err := NewCreateContractAccountRequest(from, contractAccount, opts...) 391 | if err != nil { 392 | return nil, err 393 | } 394 | 395 | return x.Do(req) 396 | } 397 | 398 | // SetAccountACL update contract account acl. NOTE: from account must be set contract account. 399 | // 400 | // Parameters: 401 | // - `from`: Transaction initiator. 402 | // - `acl` : The ACL you want to set. 403 | func (x *XClient) SetAccountACL(from *account.Account, acl *ACL, opts ...RequestOption) (*Transaction, error) { 404 | req, err := NewSetAccountACLRequest(from, acl, opts...) 405 | if err != nil { 406 | return nil, err 407 | } 408 | return x.Do(req) 409 | } 410 | 411 | // SetMethodACL update contract method acl. 412 | // 413 | // Parameters: 414 | // - `from` : Transaction initiator. 415 | // - `name` : Contract name. 416 | // - `method`: Contract method. 417 | // - `acl` : The ACL you want to set. 418 | func (x *XClient) SetMethodACL(from *account.Account, name, method string, acl *ACL, opts ...RequestOption) (*Transaction, error) { 419 | req, err := NewSetMethodACLRequest(from, name, method, acl, opts...) 420 | if err != nil { 421 | return nil, err 422 | } 423 | 424 | return x.Do(req) 425 | } 426 | 427 | // Do generete tx & post tx. 428 | func (x *XClient) Do(req *Request) (*Transaction, error) { 429 | transaction, err := x.GenerateTx(req) 430 | if err != nil { 431 | return nil, err 432 | } 433 | 434 | // build transaction only. 435 | if req.opt.notPost { 436 | return transaction, nil 437 | } 438 | 439 | // post tx. 440 | return x.PostTx(transaction) 441 | } 442 | 443 | // GenerateTx generate Transaction. 444 | func (x *XClient) GenerateTx(req *Request) (*Transaction, error) { 445 | proposal, err := NewProposal(x, req, x.cfg) 446 | if err != nil { 447 | return nil, err 448 | } 449 | return proposal.Build() 450 | } 451 | 452 | // PreExecTx preExec for query. 453 | func (x *XClient) PreExecTx(req *Request) (*Transaction, error) { 454 | proposal, err := NewProposal(x, req, x.cfg) 455 | if err != nil { 456 | return nil, err 457 | } 458 | err = proposal.PreExecWithSelectUtxo() 459 | if err != nil { 460 | return nil, err 461 | } 462 | 463 | var cr *pb.ContractResponse 464 | if len(proposal.preResp.GetResponse().GetResponses()) > 0 { 465 | cr = proposal.preResp.GetResponse().GetResponses()[len(proposal.preResp.GetResponse().GetResponses())-1] 466 | } 467 | 468 | return &Transaction{ 469 | ContractResponse: cr, 470 | }, nil 471 | } 472 | 473 | // PostTx post tx to node. 474 | func (x *XClient) PostTx(tx *Transaction) (*Transaction, error) { 475 | return tx, x.postTx(tx.Tx, tx.Bcname) 476 | } 477 | 478 | // WatchBlockEvent new watcher for block event. 479 | func (x *XClient) WatchBlockEvent(opts ...BlockEventOption) (*Watcher, error) { 480 | watcher, err := x.newWatcher(opts...) 481 | if err != nil { 482 | return nil, err 483 | } 484 | buf, _ := proto.Marshal(watcher.opt.blockFilter) 485 | request := &pb.SubscribeRequest{ 486 | Type: pb.SubscribeType_BLOCK, 487 | Filter: buf, 488 | } 489 | 490 | stream, err := x.esc.Subscribe(context.TODO(), request) 491 | if err != nil { 492 | return nil, err 493 | } 494 | 495 | filteredBlockChan := make(chan *FilteredBlock, watcher.opt.blockChanBufferSize) 496 | exit := make(chan struct{}) 497 | watcher.exit = exit 498 | watcher.FilteredBlockChan = filteredBlockChan 499 | 500 | go func() { 501 | defer func() { 502 | close(filteredBlockChan) 503 | if err := stream.CloseSend(); err != nil { 504 | log.Printf("Unregister block event failed, close stream error: %v", err) 505 | } else { 506 | log.Printf("Unregister block event success...") 507 | } 508 | }() 509 | for { 510 | select { 511 | case <-exit: 512 | return 513 | default: 514 | event, err := stream.Recv() 515 | if err == io.EOF { 516 | return 517 | } 518 | if err != nil { 519 | log.Printf("Get block event err: %v", err) 520 | return 521 | } 522 | var block pb.FilteredBlock 523 | err = proto.Unmarshal(event.Payload, &block) 524 | if err != nil { 525 | log.Printf("Get block event err: %v", err) 526 | return 527 | } 528 | if len(block.GetTxs()) == 0 && watcher.opt.skipEmptyTx { 529 | continue 530 | } 531 | filteredBlockChan <- fromFilteredBlockPB(&block) 532 | } 533 | } 534 | }() 535 | return watcher, nil 536 | } 537 | 538 | func (x *XClient) newWatcher(opts ...BlockEventOption) (*Watcher, error) { 539 | opt, err := initEventOpts(opts...) 540 | if err != nil { 541 | return nil, err 542 | } 543 | 544 | watcher := &Watcher{ 545 | opt: opt, 546 | } 547 | return watcher, nil 548 | } 549 | 550 | func (x *XClient) postTx(tx *pb.Transaction, bcname string) error { 551 | ctx := context.Background() 552 | c := x.xc 553 | txStatus := &pb.TxStatus{ 554 | Bcname: bcname, 555 | Status: pb.TransactionStatus_UNCONFIRM, 556 | Tx: tx, 557 | Txid: tx.Txid, 558 | } 559 | res, err := c.PostTx(ctx, txStatus) 560 | if err != nil { 561 | return errors.Wrap(err, "xuperclient post tx failed") 562 | } 563 | if res.Header.Error != pb.XChainErrorEnum_SUCCESS { 564 | return fmt.Errorf("Failed to post tx: %s", res.Header.Error.String()) 565 | } 566 | return nil 567 | } 568 | 569 | // QueryTxByID query the tx by txID 570 | // 571 | // Parameters 572 | // - `txID` : transaction id 573 | func (x *XClient) QueryTxByID(txID string, opts ...QueryOption) (*pb.Transaction, error) { 574 | return x.queryTxByID(txID, opts...) 575 | } 576 | 577 | // QueryBlockByID query the block by blockID 578 | // 579 | // Parameters: 580 | // - `blockID` : block id 581 | func (x *XClient) QueryBlockByID(blockID string, opts ...QueryOption) (*pb.Block, error) { 582 | return x.queryBlockByID(blockID, opts...) 583 | } 584 | 585 | // QueryContractCount query the number of contracts 586 | func (x *XClient) QueryContractCount(opts ...QueryOption) (*pb.ContractStatDataResponse, error) { 587 | return x.queryContractCount(opts...) 588 | } 589 | 590 | // QueryBlockByHeight query the block by block height 591 | // 592 | // Parameters: 593 | // - `height` : block height 594 | func (x *XClient) QueryBlockByHeight(height int64, opts ...QueryOption) (*pb.Block, error) { 595 | return x.queryBlockByHeight(height, opts...) 596 | } 597 | 598 | // QueryAccountACL query the ACL by account 599 | // 600 | // Parameters: 601 | // - `account` : account, such as XC1111111111111111@xuper 602 | func (x *XClient) QueryAccountACL(account string, opts ...QueryOption) (*ACL, error) { 603 | return x.queryAccountACL(account, opts...) 604 | } 605 | 606 | // QueryMethodACL query the ACL by method 607 | // 608 | // Parameters: 609 | // - `name` : contract name 610 | // - `account` : account 611 | func (x *XClient) QueryMethodACL(name, method string, opts ...QueryOption) (*ACL, error) { 612 | return x.queryMethodACL(name, method, opts...) 613 | } 614 | 615 | // QueryAccountContracts query all contracts for account 616 | // 617 | // Parameters: 618 | // - `account` : account,such as XC1111111111111111@xuper 619 | func (x *XClient) QueryAccountContracts(account string, opts ...QueryOption) ([]*pb.ContractStatus, error) { 620 | return x.queryAccountContracts(account, opts...) 621 | } 622 | 623 | // QueryAddressContracts query all contracts for address 624 | // 625 | // Parameters: 626 | // - `address` : address 627 | // 628 | // Returns: 629 | // - `map` : contractAccount => contractStatusList 630 | // - `error`: error 631 | func (x *XClient) QueryAddressContracts(address string, opts ...QueryOption) (map[string]*pb.ContractList, error) { 632 | return x.queryAddressContracts(address, opts...) 633 | } 634 | 635 | // QueryBalance query balance by the address 636 | // 637 | // Parameters: 638 | // - `address` : address 639 | func (x *XClient) QueryBalance(address string, opts ...QueryOption) (*big.Int, error) { 640 | return x.queryBalance(address, opts...) 641 | } 642 | 643 | // QueryBalanceDetail query the balance detail by address 644 | // 645 | // Parameters: 646 | // - `address` : address 647 | func (x *XClient) QueryBalanceDetail(address string, opts ...QueryOption) ([]*BalanceDetail, error) { 648 | return x.queryBalanceDetail(address, opts...) 649 | } 650 | 651 | // QuerySystemStatus query the system status 652 | func (x *XClient) QuerySystemStatus(opts ...QueryOption) (*pb.SystemsStatusReply, error) { 653 | return x.querySystemStatus(opts...) 654 | } 655 | 656 | // QueryBlockChains query block chains 657 | func (x *XClient) QueryBlockChains(opts ...QueryOption) ([]string, error) { 658 | return x.queryBlockChains(opts...) 659 | } 660 | 661 | // QueryBlockChainStatus query the block chain status 662 | func (x *XClient) QueryBlockChainStatus(opts ...QueryOption) (*pb.BCStatus, error) { 663 | return x.queryBlockChainStatus(opts...) 664 | } 665 | 666 | // QueryNetURL query the net URL 667 | func (x *XClient) QueryNetURL(opts ...QueryOption) (string, error) { 668 | return x.queryNetURL(opts...) 669 | } 670 | 671 | // QueryAccountByAK query the account by AK 672 | // 673 | // Parameters: 674 | // - `address` : address 675 | func (x *XClient) QueryAccountByAK(address string, opts ...QueryOption) ([]string, error) { 676 | return x.queryAccountByAK(address, opts...) 677 | } 678 | -------------------------------------------------------------------------------- /xuper/proposal_test.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "math/big" 7 | "strconv" 8 | "testing" 9 | "time" 10 | 11 | "github.com/pkg/errors" 12 | "github.com/xuperchain/xuper-sdk-go/v2/account" 13 | "github.com/xuperchain/xuper-sdk-go/v2/common/config" 14 | "github.com/xuperchain/xuperchain/service/pb" 15 | "google.golang.org/grpc" 16 | ) 17 | 18 | func TestBuild(t *testing.T) { 19 | xc := &XClient{ 20 | xc: &MockXClient{}, 21 | cfg: &config.CommConfig{ 22 | TxVersion: 1, 23 | ComplianceCheck: config.ComplianceCheckConfig{ 24 | IsNeedComplianceCheck: false, 25 | }, 26 | }, 27 | } 28 | 29 | acc, _ := account.CreateAccount(1, 1) 30 | a, e := xc.Transfer(acc, "a", "10") 31 | if e != nil { 32 | t.Error(e) 33 | } else { 34 | t.Log(a) 35 | } 36 | } 37 | func TestNewProposal(t *testing.T) { 38 | type Case struct { 39 | xclient *XClient 40 | request *Request 41 | cfg *config.CommConfig 42 | expectError error 43 | expectProposal *Proposal 44 | } 45 | 46 | cases := []Case{ 47 | { 48 | xclient: nil, 49 | request: nil, 50 | cfg: nil, 51 | expectError: errors.New("new proposal failed, parameters can not be nil"), 52 | expectProposal: nil, 53 | }, 54 | { 55 | xclient: &XClient{}, 56 | request: nil, 57 | cfg: nil, 58 | expectError: errors.New("new proposal failed, parameters can not be nil"), 59 | expectProposal: nil, 60 | }, 61 | { 62 | xclient: nil, 63 | request: &Request{}, 64 | cfg: nil, 65 | expectError: errors.New("new proposal failed, parameters can not be nil"), 66 | expectProposal: nil, 67 | }, 68 | { 69 | xclient: &XClient{}, 70 | request: &Request{}, 71 | cfg: &config.CommConfig{}, 72 | expectError: nil, 73 | expectProposal: &Proposal{txVersion: 3}, 74 | }, 75 | { 76 | xclient: &XClient{}, 77 | request: &Request{}, 78 | cfg: &config.CommConfig{ 79 | TxVersion: 1, 80 | ComplianceCheck: config.ComplianceCheckConfig{ 81 | IsNeedComplianceCheck: true, 82 | }, 83 | }, 84 | expectError: nil, 85 | expectProposal: &Proposal{txVersion: 1}, 86 | }, 87 | } 88 | 89 | for _, c := range cases { 90 | p, err := NewProposal(c.xclient, c.request, c.cfg) 91 | if c.expectError == nil { 92 | if err != nil { 93 | t.Error("new proposal assert error failed") 94 | } 95 | if p == nil { 96 | t.Error("new proposal assert proposal failed") 97 | } 98 | if p.txVersion != c.expectProposal.txVersion { 99 | t.Error("new proposal assert proposal tx version failed") 100 | } 101 | 102 | } else { 103 | if err.Error() != c.expectError.Error() { 104 | t.Error("new proposal assert error failed") 105 | } 106 | if p != c.expectProposal { 107 | t.Error("new proposal assert proposal failed") 108 | } 109 | } 110 | } 111 | } 112 | 113 | func TestGetInitiator(t *testing.T) { 114 | type Case struct { 115 | proposal *Proposal 116 | expectInitiator string 117 | } 118 | 119 | acc := &account.Account{ 120 | Address: "abc", 121 | } 122 | acc.SetContractAccount("XC1234567887654321@xuper") 123 | 124 | cases := []Case{ 125 | { 126 | proposal: &Proposal{ 127 | request: &Request{ 128 | initiatorAccount: &account.Account{ 129 | Address: "abc", 130 | }, 131 | opt: &requestOptions{ 132 | onlyFeeFromAccount: false, 133 | }, 134 | }, 135 | cfg: &config.CommConfig{ 136 | TxVersion: 1, 137 | ComplianceCheck: config.ComplianceCheckConfig{ 138 | IsNeedComplianceCheck: false, 139 | }, 140 | }, 141 | }, 142 | expectInitiator: "abc", 143 | }, 144 | { 145 | proposal: &Proposal{ 146 | request: &Request{ 147 | initiatorAccount: &account.Account{ 148 | Address: "abc", 149 | }, 150 | opt: &requestOptions{ 151 | onlyFeeFromAccount: false, 152 | }, 153 | }, 154 | cfg: &config.CommConfig{ 155 | TxVersion: 1, 156 | ComplianceCheck: config.ComplianceCheckConfig{ 157 | IsNeedComplianceCheck: true, 158 | }, 159 | }, 160 | }, 161 | expectInitiator: "abc", 162 | }, 163 | { 164 | proposal: &Proposal{ 165 | request: &Request{ 166 | initiatorAccount: acc, 167 | opt: &requestOptions{ 168 | onlyFeeFromAccount: false, 169 | }, 170 | }, 171 | cfg: &config.CommConfig{ 172 | TxVersion: 1, 173 | ComplianceCheck: config.ComplianceCheckConfig{ 174 | IsNeedComplianceCheck: false, 175 | }, 176 | }, 177 | }, 178 | expectInitiator: "XC1234567887654321@xuper", 179 | }, 180 | } 181 | 182 | for _, c := range cases { 183 | if c.expectInitiator != c.proposal.getInitiator() { 184 | t.Error("getInitiator assert failed") 185 | } 186 | } 187 | } 188 | 189 | func TestCalcTotalAmount(t *testing.T) { 190 | type Case struct { 191 | proposal *Proposal 192 | expectAmount int64 193 | expectError string 194 | } 195 | 196 | cases := []Case{ 197 | { 198 | proposal: &Proposal{ 199 | request: &Request{ 200 | transferAmount: "a", 201 | }, 202 | }, 203 | expectAmount: 0, 204 | expectError: `strconv.ParseInt: parsing "a": invalid syntax`, 205 | }, 206 | { 207 | proposal: &Proposal{ 208 | request: &Request{ 209 | transferAmount: "0", 210 | opt: &requestOptions{ 211 | onlyFeeFromAccount: false, 212 | fee: "a", 213 | }, 214 | }, 215 | }, 216 | expectAmount: 0, 217 | expectError: `strconv.ParseInt: parsing "a": invalid syntax`, 218 | }, 219 | { 220 | proposal: &Proposal{ 221 | request: &Request{ 222 | transferAmount: "0", 223 | opt: &requestOptions{ 224 | onlyFeeFromAccount: false, 225 | fee: "0", 226 | contractInvokeAmount: "a", 227 | }, 228 | }, 229 | }, 230 | expectAmount: 0, 231 | expectError: `strconv.ParseInt: parsing "a": invalid syntax`, 232 | }, 233 | { 234 | proposal: &Proposal{ 235 | request: &Request{ 236 | transferAmount: "0", 237 | opt: &requestOptions{ 238 | onlyFeeFromAccount: false, 239 | fee: "0", 240 | contractInvokeAmount: "0", 241 | }, 242 | }, 243 | cfg: &config.CommConfig{ 244 | ComplianceCheck: config.ComplianceCheckConfig{ 245 | IsNeedComplianceCheck: true, 246 | IsNeedComplianceCheckFee: true, 247 | ComplianceCheckEndorseServiceFee: 1, 248 | }, 249 | }, 250 | }, 251 | expectAmount: 1, 252 | }, 253 | { 254 | proposal: &Proposal{ 255 | request: &Request{ 256 | transferAmount: "3", 257 | opt: &requestOptions{ 258 | onlyFeeFromAccount: false, 259 | fee: "10", 260 | contractInvokeAmount: "8", 261 | }, 262 | }, 263 | cfg: &config.CommConfig{ 264 | ComplianceCheck: config.ComplianceCheckConfig{ 265 | IsNeedComplianceCheck: true, 266 | IsNeedComplianceCheckFee: true, 267 | ComplianceCheckEndorseServiceFee: 1, 268 | }, 269 | }, 270 | }, 271 | expectAmount: 22, 272 | }, 273 | } 274 | 275 | for _, c := range cases { 276 | amount, err := c.proposal.calcTotalAmount() 277 | if err != nil { 278 | if err.Error() != c.expectError { 279 | t.Error("calcTotalAmount err assert failed") 280 | } 281 | } else { 282 | if c.expectAmount != amount { 283 | t.Errorf("calcTotalAmount amount assert failed: expect: %d, acture:%d", c.expectAmount, amount) 284 | } 285 | } 286 | } 287 | } 288 | 289 | func TestGenInvokeRPCRequest(t *testing.T) { 290 | type Case struct { 291 | proposal *Proposal 292 | expectBcname string 293 | expectInitiator string 294 | expectAuthRequires []string 295 | expectInvokeRequest pb.InvokeRequest 296 | expectError bool 297 | } 298 | 299 | cases := []Case{ 300 | { 301 | proposal: &Proposal{ 302 | request: &Request{ 303 | module: "xkernel", 304 | initiatorAccount: &account.Account{ 305 | Address: "hello", 306 | }, 307 | opt: &requestOptions{ 308 | otherAuthRequire: []string{"a", "b"}, 309 | }, 310 | }, 311 | cfg: &config.CommConfig{ 312 | ComplianceCheck: config.ComplianceCheckConfig{ 313 | IsNeedComplianceCheck: false, 314 | IsNeedComplianceCheckFee: true, 315 | ComplianceCheckEndorseServiceFee: 1, 316 | ComplianceCheckEndorseServiceAddr: "word", 317 | }, 318 | }, 319 | }, 320 | expectInvokeRequest: pb.InvokeRequest{}, 321 | expectBcname: "xuper", 322 | expectInitiator: "hello", 323 | expectAuthRequires: []string{"hello", "a", "b"}, 324 | }, 325 | { 326 | proposal: &Proposal{ 327 | request: &Request{ 328 | module: "xkernel", 329 | initiatorAccount: &account.Account{ 330 | Address: "hello", 331 | }, 332 | opt: &requestOptions{ 333 | otherAuthRequire: []string{"a", "b"}, 334 | }, 335 | }, 336 | cfg: &config.CommConfig{ 337 | ComplianceCheck: config.ComplianceCheckConfig{ 338 | IsNeedComplianceCheck: true, 339 | IsNeedComplianceCheckFee: true, 340 | ComplianceCheckEndorseServiceFee: 1, 341 | ComplianceCheckEndorseServiceAddr: "word", 342 | }, 343 | }, 344 | }, 345 | expectInvokeRequest: pb.InvokeRequest{}, 346 | expectBcname: "xuper", 347 | expectInitiator: "hello", 348 | expectAuthRequires: []string{"word", "hello", "a", "b"}, 349 | }, 350 | } 351 | 352 | for _, c := range cases { 353 | r, err := c.proposal.genInvokeRPCRequest() 354 | if err != nil { 355 | if !c.expectError { 356 | t.Error(err) 357 | } 358 | } else { 359 | if r == nil { 360 | continue 361 | } 362 | if c.expectBcname != r.Bcname { 363 | t.Error("genInvokeRPCRequest assert bcname failed") 364 | } 365 | if c.expectInitiator != r.Initiator { 366 | t.Error("genInvokeRPCRequest assert bcname failed") 367 | } 368 | for i, v := range r.AuthRequire { 369 | if c.expectAuthRequires[i] != v { 370 | t.Error("genInvokeRPCRequest assert AuthRequire failed") 371 | } 372 | } 373 | } 374 | } 375 | } 376 | 377 | type MockXchainClient interface { 378 | pb.XchainClient 379 | } 380 | 381 | var _ MockXchainClient = new(MockXClient) 382 | 383 | // 实现 MockXchainClient 接口, 384 | type MockXClient struct { 385 | } 386 | 387 | // SelectUTXOBySize merge many utxos into a few of utxos 388 | func (mcx *MockXClient) SelectUTXOBySize(ctx context.Context, in *pb.UtxoInput, opts ...grpc.CallOption) (*pb.UtxoOutput, error) { 389 | return nil, nil 390 | } 391 | 392 | // PostTx post Transaction to a node 393 | func (mcx *MockXClient) PostTx(ctx context.Context, in *pb.TxStatus, opts ...grpc.CallOption) (*pb.CommonReply, error) { 394 | return &pb.CommonReply{ 395 | Header: newHeader(), 396 | }, nil 397 | } 398 | func (mcx *MockXClient) QueryACL(ctx context.Context, in *pb.AclStatus, opts ...grpc.CallOption) (*pb.AclStatus, error) { 399 | return &pb.AclStatus{ 400 | Header: newHeader(), 401 | AccountName: in.GetAccountName(), 402 | ContractName: in.GetContractName(), 403 | MethodName: in.GetMethodName(), 404 | Acl: &pb.Acl{ 405 | Pm: &pb.PermissionModel{Rule: 1, AcceptValue: 1.0}, 406 | AksWeight: map[string]float64{"a": 1.0}, 407 | }, 408 | }, nil 409 | } 410 | func (mcx *MockXClient) QueryUtxoRecord(ctx context.Context, in *pb.UtxoRecordDetail, opts ...grpc.CallOption) (*pb.UtxoRecordDetail, error) { 411 | return nil, nil 412 | } 413 | func (mcx *MockXClient) QueryContractStatData(ctx context.Context, in *pb.ContractStatDataRequest, opts ...grpc.CallOption) (*pb.ContractStatDataResponse, error) { 414 | return nil, nil 415 | } 416 | func (mcx *MockXClient) GetAccountContracts(ctx context.Context, in *pb.GetAccountContractsRequest, opts ...grpc.CallOption) (*pb.GetAccountContractsResponse, error) { 417 | return &pb.GetAccountContractsResponse{ 418 | Header: newHeader(), 419 | }, nil 420 | } 421 | 422 | // QueryTx query Transaction by TxStatus, 423 | // Bcname and Txid are required for this 424 | func (mcx *MockXClient) QueryTx(ctx context.Context, in *pb.TxStatus, opts ...grpc.CallOption) (*pb.TxStatus, error) { 425 | return &pb.TxStatus{ 426 | Header: newHeader(), 427 | Txid: in.GetTxid(), 428 | Tx: &pb.Transaction{ 429 | Txid: in.GetTxid(), 430 | }, 431 | }, nil 432 | } 433 | 434 | // GetBalance get balance of an address, 435 | // Address is required for this 436 | func (mcx *MockXClient) GetBalance(ctx context.Context, in *pb.AddressStatus, opts ...grpc.CallOption) (*pb.AddressStatus, error) { 437 | return &pb.AddressStatus{ 438 | Header: newHeader(), 439 | Address: in.GetAddress(), 440 | Bcs: []*pb.TokenDetail{{Bcname: "xuper", Balance: "100"}}, 441 | }, nil 442 | } 443 | 444 | // GetFrozenBalance get two kinds of balance 445 | // 1. Still be frozen of an address 446 | // 2. Available now of an address 447 | // Address is required for this 448 | func (mcx *MockXClient) GetBalanceDetail(ctx context.Context, in *pb.AddressBalanceStatus, opts ...grpc.CallOption) (*pb.AddressBalanceStatus, error) { 449 | return &pb.AddressBalanceStatus{ 450 | Header: newHeader(), 451 | Address: in.GetAddress(), 452 | Tfds: []*pb.TokenFrozenDetails{{Bcname: "xuper", Tfd: []*pb.TokenFrozenDetail{{Balance: "100"}}}}, 453 | }, nil 454 | } 455 | 456 | // GetFrozenBalance get balance that still be frozen of an address, 457 | // Address is required for this 458 | func (mcx *MockXClient) GetFrozenBalance(ctx context.Context, in *pb.AddressStatus, opts ...grpc.CallOption) (*pb.AddressStatus, error) { 459 | return &pb.AddressStatus{ 460 | Header: newHeader(), 461 | Address: in.GetAddress(), 462 | Bcs: []*pb.TokenDetail{{Bcname: "xuper", Balance: "100"}}, 463 | }, nil 464 | } 465 | 466 | // GetBlock get block by blockid and return if the block in trunk or in branch 467 | func (mcx *MockXClient) GetBlock(ctx context.Context, in *pb.BlockID, opts ...grpc.CallOption) (*pb.Block, error) { 468 | return &pb.Block{ 469 | Header: newHeader(), 470 | Blockid: in.GetBlockid(), 471 | Block: &pb.InternalBlock{ 472 | Blockid: in.GetBlockid(), 473 | }, 474 | }, nil 475 | } 476 | 477 | // GetBlockByHeight get block by height and return if the block in trunk or in 478 | // branch 479 | func (mcx *MockXClient) GetBlockByHeight(ctx context.Context, in *pb.BlockHeight, opts ...grpc.CallOption) (*pb.Block, error) { 480 | return &pb.Block{ 481 | Header: newHeader(), 482 | Blockid: []byte("aa"), 483 | Block: &pb.InternalBlock{ 484 | Blockid: []byte("aa"), 485 | Height: in.GetHeight(), 486 | }, 487 | }, nil 488 | } 489 | func (mcx *MockXClient) GetBlockChainStatus(ctx context.Context, in *pb.BCStatus, opts ...grpc.CallOption) (*pb.BCStatus, error) { 490 | return &pb.BCStatus{ 491 | Header: newHeader(), 492 | Bcname: in.GetBcname(), 493 | Block: &pb.InternalBlock{ 494 | Blockid: []byte("aa"), 495 | Height: 188, 496 | }, 497 | }, nil 498 | } 499 | 500 | // Get blockchains query blockchains 501 | func (mcx *MockXClient) GetBlockChains(ctx context.Context, in *pb.CommonIn, opts ...grpc.CallOption) (*pb.BlockChains, error) { 502 | return &pb.BlockChains{ 503 | Header: newHeader(), 504 | Blockchains: []string{"xuper"}, 505 | }, nil 506 | } 507 | 508 | // GetSystemStatus query system status 509 | func (mcx *MockXClient) GetSystemStatus(ctx context.Context, in *pb.CommonIn, opts ...grpc.CallOption) (*pb.SystemsStatusReply, error) { 510 | return &pb.SystemsStatusReply{ 511 | Header: newHeader(), 512 | SystemsStatus: &pb.SystemsStatus{}, 513 | }, nil 514 | } 515 | 516 | func (mcx *MockXClient) GetConsensusStatus(ctx context.Context, in *pb.ConsensusStatRequest, opts ...grpc.CallOption) (*pb.ConsensusStatus, error) { 517 | return nil, nil 518 | } 519 | 520 | // GetNetURL return net url 521 | func (mcx *MockXClient) GetNetURL(ctx context.Context, in *pb.CommonIn, opts ...grpc.CallOption) (*pb.RawUrl, error) { 522 | return nil, nil 523 | } 524 | 525 | // 新的Select utxos接口, 不需要签名,可以支持选择账户的utxo 526 | func (mcx *MockXClient) SelectUTXO(ctx context.Context, in *pb.UtxoInput, opts ...grpc.CallOption) (*pb.UtxoOutput, error) { 527 | need, ok := big.NewInt(0).SetString(in.GetTotalNeed(), 10) 528 | if !ok { 529 | return nil, errors.New("invalid totalNeed") 530 | } 531 | need.Add(need, big.NewInt(100)) 532 | 533 | a := big.NewInt(0).SetBytes(need.Bytes()).SetInt64(10) 534 | 535 | utxoList := []*pb.Utxo{ 536 | { 537 | Amount: big.NewInt(10).Bytes(), 538 | ToAddr: []byte(in.Address), 539 | RefTxid: []byte("a"), 540 | }, { 541 | Amount: a.Bytes(), 542 | ToAddr: []byte(in.Address), 543 | RefTxid: []byte("a"), 544 | }, 545 | } 546 | 547 | response := &pb.UtxoOutput{ 548 | Header: newHeader(), 549 | TotalSelected: need.String(), 550 | UtxoList: utxoList, 551 | } 552 | return response, nil 553 | } 554 | 555 | // PreExecWithSelectUTXO preExec & selectUtxo 556 | func (mcx *MockXClient) PreExecWithSelectUTXO(ctx context.Context, in *pb.PreExecWithSelectUTXORequest, opts ...grpc.CallOption) (*pb.PreExecWithSelectUTXOResponse, error) { 557 | 558 | output, _ := mcx.SelectUTXO(ctx, &pb.UtxoInput{ 559 | Header: newHeader(), 560 | Bcname: in.GetBcname(), 561 | Address: in.Address, 562 | TotalNeed: strconv.Itoa(int(in.GetTotalAmount())), 563 | }) 564 | 565 | cr, _ := mcx.PreExec(ctx, in.Request) 566 | response := &pb.PreExecWithSelectUTXOResponse{ 567 | Header: newHeader(), 568 | Bcname: in.GetBcname(), 569 | Response: cr.GetResponse(), 570 | UtxoOutput: output, 571 | } 572 | return response, nil 573 | } 574 | 575 | // DposCandidates get all candidates of the tdpos consensus 576 | func (mcx *MockXClient) DposCandidates(ctx context.Context, in *pb.DposCandidatesRequest, opts ...grpc.CallOption) (*pb.DposCandidatesResponse, error) { 577 | return nil, nil 578 | } 579 | 580 | // DposNominateRecords get all records nominated by an user 581 | func (mcx *MockXClient) DposNominateRecords(ctx context.Context, in *pb.DposNominateRecordsRequest, opts ...grpc.CallOption) (*pb.DposNominateRecordsResponse, error) { 582 | return nil, nil 583 | } 584 | 585 | // DposNomineeRecords get nominated record of a candidate 586 | func (mcx *MockXClient) DposNomineeRecords(ctx context.Context, in *pb.DposNomineeRecordsRequest, opts ...grpc.CallOption) (*pb.DposNomineeRecordsResponse, error) { 587 | return nil, nil 588 | } 589 | 590 | // DposVoteRecords get all vote records voted by an user 591 | func (mcx *MockXClient) DposVoteRecords(ctx context.Context, in *pb.DposVoteRecordsRequest, opts ...grpc.CallOption) (*pb.DposVoteRecordsResponse, error) { 592 | return nil, nil 593 | } 594 | 595 | // DposVotedRecords get all vote records of a candidate 596 | func (mcx *MockXClient) DposVotedRecords(ctx context.Context, in *pb.DposVotedRecordsRequest, opts ...grpc.CallOption) (*pb.DposVotedRecordsResponse, error) { 597 | return nil, nil 598 | } 599 | 600 | // DposCheckResults get check results of a specific term 601 | func (mcx *MockXClient) DposCheckResults(ctx context.Context, in *pb.DposCheckResultsRequest, opts ...grpc.CallOption) (*pb.DposCheckResultsResponse, error) { 602 | return nil, nil 603 | } 604 | 605 | // DposStatus get dpos status 606 | func (mcx *MockXClient) DposStatus(ctx context.Context, in *pb.DposStatusRequest, opts ...grpc.CallOption) (*pb.DposStatusResponse, error) { 607 | return nil, nil 608 | } 609 | 610 | // GetAccountByAK get account sets contain a specific address 611 | func (mcx *MockXClient) GetAccountByAK(ctx context.Context, in *pb.AK2AccountRequest, opts ...grpc.CallOption) (*pb.AK2AccountResponse, error) { 612 | return &pb.AK2AccountResponse{ 613 | Header: newHeader(), 614 | Bcname: in.GetBcname(), 615 | Account: []string{"XC1111@xuper"}, 616 | }, nil 617 | } 618 | 619 | // GetAddressContracts get contracts of accounts contain a specific address 620 | func (mcx *MockXClient) GetAddressContracts(ctx context.Context, in *pb.AddressContractsRequest, opts ...grpc.CallOption) (*pb.AddressContractsResponse, error) { 621 | return &pb.AddressContractsResponse{ 622 | Header: newHeader(), 623 | }, nil 624 | } 625 | 626 | //预执行合约 627 | func (mcx *MockXClient) PreExec(ctx context.Context, in *pb.InvokeRPCRequest, opts ...grpc.CallOption) (*pb.InvokeRPCResponse, error) { 628 | cr := &pb.ContractResponse{ 629 | Status: 200, 630 | Message: "a", 631 | Body: []byte("ok"), 632 | } 633 | crb, _ := json.Marshal(cr) 634 | ir := &pb.InvokeResponse{ 635 | GasUsed: 10, 636 | Requests: in.GetRequests(), 637 | Inputs: []*pb.TxInputExt{{Bucket: "a", Key: []byte("a"), RefTxid: []byte("b"), RefOffset: 0}}, 638 | Outputs: []*pb.TxOutputExt{{Bucket: "a", Key: []byte("a"), Value: []byte("b")}}, 639 | Response: [][]byte{crb, crb}, 640 | } 641 | 642 | response := &pb.InvokeRPCResponse{ 643 | Header: newHeader(), 644 | Bcname: in.GetBcname(), 645 | Response: ir, 646 | } 647 | return response, nil 648 | } 649 | 650 | func newHeader() *pb.Header { 651 | //XChainErrorEnum_SUCCESS 652 | return &pb.Header{ 653 | Error: pb.XChainErrorEnum_SUCCESS, 654 | } 655 | } 656 | 657 | type MockEndorserClient interface { 658 | pb.XendorserClient 659 | } 660 | 661 | type MockEClient struct{} 662 | 663 | func (mec *MockEClient) EndorserCall(ctx context.Context, in *pb.EndorserRequest, opts ...grpc.CallOption) (*pb.EndorserResponse, error) { 664 | peur := new(pb.PreExecWithSelectUTXORequest) 665 | json.Unmarshal(in.GetRequestData(), peur) 666 | ta := strconv.Itoa(int(peur.GetTotalAmount())) 667 | 668 | data := []byte{} 669 | if in.RequestName == "PreExecWithFee" { 670 | mxc := &MockXClient{} 671 | mxc.SelectUTXO(ctx, &pb.UtxoInput{ 672 | Header: in.GetHeader(), 673 | Bcname: "xuper", 674 | Address: peur.Address, 675 | TotalNeed: ta, 676 | }) 677 | cr := &pb.ContractResponse{ 678 | Status: 200, 679 | Message: "a", 680 | Body: []byte("ok"), 681 | } 682 | crb, _ := json.Marshal(cr) 683 | ir := &pb.InvokeResponse{ 684 | GasUsed: 10, 685 | Inputs: []*pb.TxInputExt{{Bucket: "a", Key: []byte("a"), RefTxid: []byte("b"), RefOffset: 0}}, 686 | Outputs: []*pb.TxOutputExt{{Bucket: "a", Key: []byte("a"), Value: []byte("b")}}, 687 | Response: [][]byte{crb}, 688 | Requests: peur.GetRequest().GetRequests(), 689 | } 690 | per := &pb.PreExecWithSelectUTXOResponse{ 691 | Header: in.GetHeader(), 692 | Bcname: in.GetBcName(), 693 | Response: ir, 694 | UtxoOutput: &pb.UtxoOutput{ 695 | UtxoList: []*pb.Utxo{ 696 | { 697 | ToAddr: []byte(peur.Address), 698 | Amount: big.NewInt(100).Bytes(), 699 | }, 700 | }, 701 | TotalSelected: "1000", 702 | }, 703 | } 704 | data, _ = json.Marshal(per) 705 | } 706 | resp := &pb.EndorserResponse{ 707 | Header: in.Header, 708 | ResponseName: in.GetRequestName(), 709 | EndorserSign: &pb.SignatureInfo{PublicKey: "pubkey", Sign: []byte("endirserSign")}, 710 | ResponseData: data, 711 | } 712 | return resp, nil 713 | } 714 | 715 | type MockEventClient interface { 716 | pb.EventServiceClient 717 | } 718 | 719 | type MockESClient struct { 720 | grpc.ClientStream 721 | } 722 | 723 | type eventServiceSubscribeClient struct { 724 | grpc.ClientStream 725 | } 726 | 727 | func (x *eventServiceSubscribeClient) Recv() (*pb.Event, error) { 728 | m := new(pb.Event) 729 | // if err := x.ClientStream.RecvMsg(m); err != nil { 730 | // return nil, err 731 | // } 732 | time.Sleep(time.Millisecond * 100) 733 | return m, nil 734 | } 735 | 736 | func (mesc *MockESClient) Subscribe(ctx context.Context, in *pb.SubscribeRequest, opts ...grpc.CallOption) (pb.EventService_SubscribeClient, error) { 737 | return &eventServiceSubscribeClient{}, nil 738 | } 739 | -------------------------------------------------------------------------------- /xuper/proposal.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "math/big" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/pkg/errors" 13 | 14 | "github.com/xuperchain/xuper-sdk-go/v2/common" 15 | "github.com/xuperchain/xuper-sdk-go/v2/common/config" 16 | "github.com/xuperchain/xuper-sdk-go/v2/crypto" 17 | "github.com/xuperchain/xuperchain/service/pb" 18 | ) 19 | 20 | const ( 21 | defaultChainName = "xuper" 22 | ) 23 | 24 | // Proposal 代表单个请求,构造交易,但不 post。 25 | type Proposal struct { 26 | xclient *XClient 27 | request *Request 28 | 29 | preResp *pb.PreExecWithSelectUTXOResponse 30 | feePreResp *pb.UtxoOutput 31 | tx *Transaction 32 | complianceCheckTx *pb.Transaction 33 | 34 | cfg *config.CommConfig 35 | 36 | txVersion int32 37 | } 38 | 39 | // NewProposal new Proposal instance. 40 | func NewProposal(xclient *XClient, request *Request, cfg *config.CommConfig) (*Proposal, error) { 41 | if xclient == nil || request == nil || cfg == nil { 42 | return nil, errors.New("new proposal failed, parameters can not be nil") 43 | } 44 | 45 | // 开放网络交易版本根据配置文件来,非开放网络交易使用 common 中的 TxVersion 也就是版本3. 46 | v := int32(common.TxVersion) 47 | if cfg.ComplianceCheck.IsNeedComplianceCheck { 48 | v = cfg.TxVersion 49 | } 50 | 51 | return &Proposal{ 52 | xclient: xclient, 53 | request: request, 54 | cfg: cfg, 55 | txVersion: v, 56 | }, nil 57 | } 58 | 59 | // Build 发起预执行,构造交易。 60 | func (p *Proposal) Build() (*Transaction, error) { 61 | err := p.PreExecWithSelectUtxo() // T_T!,开放网络所有交易都是通过 AK 支付手续费,除了开放网络,其他的根据是否设置了合约账户,以及是否只是合约账户支付手续费来判断。 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | tx, err := p.GenCompleteTx() 67 | if err != nil { 68 | return nil, err 69 | } 70 | p.tx = tx 71 | 72 | return tx, nil 73 | } 74 | 75 | // PreExecWithSelectUtxo 预执行并选择 utxo,如果有背书则调用 EndorserCall。 76 | func (p *Proposal) PreExecWithSelectUtxo() error { 77 | 78 | req, err := p.genPreExecUtxoRequest() 79 | if err != nil { 80 | return err 81 | } 82 | 83 | ctx := context.Background() 84 | preExecWithSelectUTXOResponse := new(pb.PreExecWithSelectUTXOResponse) 85 | 86 | if p.cfg.ComplianceCheck.IsNeedComplianceCheck { 87 | requestData, err := json.Marshal(req) 88 | if err != nil { 89 | return err 90 | } 91 | endorserRequest := &pb.EndorserRequest{ 92 | RequestName: "PreExecWithFee", 93 | BcName: req.Bcname, 94 | RequestData: requestData, 95 | } 96 | c := p.xclient.ec 97 | endorserResponse, err := c.EndorserCall(ctx, endorserRequest) 98 | if err != nil { 99 | return errors.Wrap(err, "EndorserCall PreExecWithFee failed") 100 | } 101 | responseData := endorserResponse.ResponseData 102 | err = json.Unmarshal(responseData, preExecWithSelectUTXOResponse) 103 | if err != nil { 104 | return err 105 | } 106 | } else { 107 | c := p.xclient.xc 108 | var err error 109 | preExecWithSelectUTXOResponse, err = c.PreExecWithSelectUTXO(ctx, req) 110 | if err != nil { 111 | return errors.Wrap(err, "PreExecWithSelectUTXO failed") 112 | } 113 | 114 | // AK 发起交易,仅使用合约账户支付手续费时,需要选择 utxo。 115 | if p.request.opt.onlyFeeFromAccount { 116 | amount, ok := big.NewInt(0).SetString(p.request.opt.fee, 10) 117 | if !ok { 118 | return errors.Wrap(common.ErrInvalidAmount, "invalid request fee") 119 | } 120 | amount.Add(amount, big.NewInt(preExecWithSelectUTXOResponse.GetResponse().GetGasUsed())) 121 | 122 | feeReq := p.genSelectUtxoRequest(p.request.initiatorAccount.GetContractAccount(), amount.String()) 123 | 124 | p.feePreResp, err = c.SelectUTXO(ctx, feeReq) 125 | if err != nil { 126 | return errors.Wrap(err, "SelectUTXO from contract account failed") 127 | } 128 | } 129 | } 130 | 131 | for _, res := range preExecWithSelectUTXOResponse.GetResponse().GetResponses() { 132 | if res.Status >= 400 { 133 | return fmt.Errorf("contract invoke error status:%d message:%s", res.Status, res.Message) 134 | } 135 | } 136 | 137 | p.preResp = preExecWithSelectUTXOResponse 138 | return nil 139 | } 140 | 141 | // GenCompleteTx 根据预执行结果构造完整的交易。 142 | func (p *Proposal) GenCompleteTx() (*Transaction, error) { 143 | var ( 144 | tx *pb.Transaction 145 | digestHash []byte 146 | err error 147 | 148 | preResp = p.preResp 149 | ) 150 | 151 | // public method should check proposal's preResp. 152 | if preResp == nil { 153 | return nil, errors.New("proposal preResp can not be nil") 154 | } 155 | 156 | if p.cfg.ComplianceCheck.IsNeedComplianceCheck { 157 | tx, err = p.genTxWithComplianceCheck() 158 | if err != nil { 159 | return nil, err 160 | } 161 | } else { 162 | tx, err = p.genTx() 163 | if err != nil { 164 | return nil, err 165 | } 166 | } 167 | 168 | var ContractResponse *pb.ContractResponse 169 | if len(preResp.GetResponse().GetResponses()) != 0 { 170 | // 如果没有背书,那么一个合约调用应该有一个 response。 171 | // 有背书或者有 reserved contract 时,会有多个 response,最后一个 response 为本次交易的合约执行结果。 172 | // server 端实现代码在 xuperchain 项目:core/utxo/utxo.go:PreExec 接口。 173 | ContractResponse = preResp.GetResponse().GetResponses()[len(preResp.GetResponse().GetResponses())-1] 174 | } 175 | 176 | // initiator sign tx and calc tx ID. 177 | digestHash, err = p.signTx(tx) 178 | if err != nil { 179 | return nil, err 180 | } 181 | 182 | transaction := &Transaction{ 183 | Tx: tx, 184 | ContractResponse: ContractResponse, 185 | Bcname: p.getChainName(), 186 | Fee: p.request.opt.fee, 187 | GasUsed: preResp.GetResponse().GetGasUsed(), 188 | DigestHash: digestHash, 189 | } 190 | 191 | return transaction, nil 192 | } 193 | 194 | func (p *Proposal) genTxWithComplianceCheck() (*pb.Transaction, error) { 195 | var ( 196 | complianceCheckTx *pb.Transaction 197 | err error 198 | ) 199 | 200 | if p.cfg.ComplianceCheck.IsNeedComplianceCheckFee { 201 | complianceCheckTx, err = p.genComplianceCheckTx() 202 | if err != nil { 203 | return nil, err 204 | } 205 | p.complianceCheckTx = complianceCheckTx 206 | } 207 | 208 | tx, err := p.genTx() 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | endorserSign, err := p.complianceCheck(tx) 214 | if err != nil { 215 | return nil, err 216 | } 217 | tx.AuthRequireSigns = append(tx.AuthRequireSigns, endorserSign) 218 | 219 | return tx, nil 220 | } 221 | 222 | func (p *Proposal) complianceCheck(tx *pb.Transaction) (*pb.SignatureInfo, error) { 223 | ctx := context.Background() 224 | txStatus := &pb.TxStatus{ 225 | Bcname: p.getChainName(), 226 | Tx: tx, 227 | } 228 | 229 | requestData, err := json.Marshal(txStatus) 230 | if err != nil { 231 | log.Printf("json encode txStatus failed: %v", err) 232 | return nil, err 233 | } 234 | 235 | endorserRequest := &pb.EndorserRequest{ 236 | RequestName: "ComplianceCheck", 237 | BcName: p.getChainName(), 238 | Fee: p.complianceCheckTx, 239 | RequestData: requestData, 240 | } 241 | 242 | endorserResponse, err := p.xclient.ec.EndorserCall(ctx, endorserRequest) 243 | if err != nil { 244 | return nil, errors.Wrap(err, "EndorserCall ComplianceCheck failed") 245 | } 246 | return endorserResponse.GetEndorserSign(), nil 247 | } 248 | 249 | func (p *Proposal) genComplianceCheckTx() (*pb.Transaction, error) { 250 | complianceCheckFee := p.cfg.ComplianceCheck.ComplianceCheckEndorseServiceFee 251 | complianceCheckFeeAddr := p.cfg.ComplianceCheck.ComplianceCheckEndorseServiceFeeAddr 252 | utxoOutput := p.preResp.GetUtxoOutput() 253 | 254 | checkTxOutput, err := p.generateComplianceCheckTxOutput(complianceCheckFeeAddr, strconv.Itoa(complianceCheckFee)) 255 | if err != nil { 256 | return nil, err 257 | } 258 | 259 | complianceCheckFeeBigInt := new(big.Int).SetInt64(int64(complianceCheckFee)) 260 | txInputs, deltaTxOutput, err := p.generateComplianceCheckTxInput(utxoOutput, complianceCheckFeeBigInt) 261 | 262 | if deltaTxOutput != nil { 263 | checkTxOutput = append(checkTxOutput, deltaTxOutput) 264 | } 265 | 266 | err = common.SetSeed() 267 | if err != nil { 268 | return nil, errors.Wrap(err, "Set seed failed") 269 | } 270 | 271 | tx := &pb.Transaction{ 272 | Desc: []byte(""), 273 | Version: p.txVersion, 274 | Coinbase: false, 275 | Nonce: common.GetNonce(), 276 | Timestamp: time.Now().UnixNano(), 277 | TxInputs: txInputs, 278 | TxOutputs: checkTxOutput, 279 | Initiator: p.getInitiator(), 280 | AuthRequire: []string{p.getInitiator()}, 281 | } 282 | 283 | // initiator sign tx and calc tx ID. 284 | _, err = p.signTx(tx) 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | return tx, nil 290 | } 291 | 292 | func (p *Proposal) signTx(tx *pb.Transaction) ([]byte, error) { 293 | initiator := p.request.initiatorAccount 294 | 295 | cryptoClient := crypto.GetCryptoClient() 296 | privateKey, err := cryptoClient.GetEcdsaPrivateKeyFromJsonStr(initiator.PrivateKey) 297 | if err != nil { 298 | return nil, err 299 | } 300 | 301 | digestHash, err := common.MakeTxDigestHash(tx) 302 | if err != nil { 303 | return nil, err 304 | } 305 | 306 | sign, err := cryptoClient.SignECDSA(privateKey, digestHash) 307 | 308 | signatureInfo := &pb.SignatureInfo{ 309 | PublicKey: initiator.PublicKey, 310 | Sign: sign, 311 | } 312 | 313 | var signatureInfos []*pb.SignatureInfo 314 | signatureInfos = append(signatureInfos, signatureInfo) 315 | 316 | tx.InitiatorSigns = signatureInfos 317 | 318 | if len(tx.GetAuthRequireSigns()) == 0 { 319 | tx.AuthRequireSigns = signatureInfos 320 | } else { 321 | tx.AuthRequireSigns = append(tx.AuthRequireSigns, signatureInfos...) 322 | } 323 | 324 | // make txid 325 | tx.Txid, err = common.MakeTransactionID(tx) 326 | if err != nil { 327 | return nil, errors.Wrap(err, "Make transaction ID failed.") 328 | } 329 | 330 | return digestHash, nil 331 | } 332 | 333 | // generateTxOutput generate txoutput part 334 | func (p *Proposal) generateComplianceCheckTxOutput(to, amount string) ([]*pb.TxOutput, error) { 335 | accounts := []*pb.TxDataAccount{} 336 | if to != "" { 337 | account := &pb.TxDataAccount{ 338 | Address: to, 339 | Amount: amount, 340 | FrozenHeight: 0, 341 | } 342 | accounts = append(accounts, account) 343 | } 344 | 345 | bigZero := big.NewInt(0) 346 | txOutputs := []*pb.TxOutput{} 347 | for _, acc := range accounts { 348 | amount, ok := big.NewInt(0).SetString(acc.Amount, 10) 349 | if !ok { 350 | return nil, common.ErrInvalidAmount 351 | } 352 | cmpRes := amount.Cmp(bigZero) 353 | if cmpRes < 0 { 354 | return nil, errors.New("Invalid negative number") 355 | } else if cmpRes == 0 { 356 | continue 357 | } 358 | txOutput := &pb.TxOutput{} 359 | txOutput.Amount = amount.Bytes() 360 | txOutput.ToAddr = []byte(acc.Address) 361 | txOutput.FrozenHeight = acc.FrozenHeight 362 | txOutputs = append(txOutputs, txOutput) 363 | } 364 | 365 | return txOutputs, nil 366 | } 367 | 368 | // generateTxInput generate txinput part 369 | func (p *Proposal) generateComplianceCheckTxInput(utxoOutputs *pb.UtxoOutput, totalNeed *big.Int) ([]*pb.TxInput, *pb.TxOutput, error) { 370 | var txInputs []*pb.TxInput 371 | var txOutput *pb.TxOutput 372 | for _, utxo := range utxoOutputs.GetUtxoList() { 373 | txInput := &pb.TxInput{} 374 | txInput.RefTxid = utxo.RefTxid 375 | txInput.RefOffset = utxo.RefOffset 376 | txInput.FromAddr = utxo.ToAddr 377 | txInput.Amount = utxo.Amount 378 | txInputs = append(txInputs, txInput) 379 | } 380 | 381 | utxoTotal, ok := big.NewInt(0).SetString(utxoOutputs.GetTotalSelected(), 10) 382 | if !ok { 383 | return nil, nil, fmt.Errorf("Invalid utxoOutputs.TotalSelected: %s", utxoOutputs.GetTotalSelected()) 384 | } 385 | 386 | // input > output, generate output-input to me 387 | if utxoTotal.Cmp(totalNeed) > 0 { 388 | delta := utxoTotal.Sub(utxoTotal, totalNeed) 389 | txOutput = &pb.TxOutput{ 390 | ToAddr: []byte(p.getInitiator()), 391 | Amount: delta.Bytes(), 392 | } 393 | } 394 | 395 | return txInputs, txOutput, nil 396 | } 397 | 398 | func (p *Proposal) calcSelfAmount(totalSelected *big.Int) (string, error) { 399 | totalNeed := big.NewInt(0) 400 | amount := big.NewInt(0) 401 | preResp := p.preResp 402 | 403 | // amount 404 | if p.request.opt.contractInvokeAmount != "" { 405 | invokeAmount, ok := big.NewInt(0).SetString(p.request.opt.contractInvokeAmount, 10) 406 | if !ok { 407 | return "", common.ErrInvalidAmount 408 | } 409 | amount.Add(amount, invokeAmount) 410 | } 411 | 412 | if p.request.transferAmount != "" { 413 | transferAmount, ok := big.NewInt(0).SetString(p.request.transferAmount, 10) 414 | if !ok { 415 | return "", common.ErrInvalidAmount 416 | } 417 | amount.Add(amount, transferAmount) 418 | } 419 | 420 | // fee 421 | if !p.request.opt.onlyFeeFromAccount { 422 | if p.request.opt.fee != "" { 423 | fee, ok := big.NewInt(0).SetString(p.request.opt.fee, 10) 424 | if !ok { 425 | return "", common.ErrInvalidAmount 426 | } 427 | amount.Add(amount, fee) 428 | } 429 | 430 | // gas 431 | gasUsed := big.NewInt(preResp.GetResponse().GetGasUsed()) 432 | amount.Add(amount, gasUsed) 433 | } 434 | 435 | // total 436 | totalNeed.Add(totalNeed, amount) 437 | 438 | selfAmount := totalSelected.Sub(totalSelected, totalNeed) 439 | 440 | return selfAmount.String(), nil 441 | } 442 | 443 | func (p *Proposal) genTx() (*pb.Transaction, error) { 444 | utxoOutput := &pb.UtxoOutput{} 445 | totalSelected := big.NewInt(0) 446 | preResp := p.preResp 447 | 448 | utxolist := []*pb.Utxo{} 449 | 450 | if p.complianceCheckTx != nil { 451 | for index, txOutput := range p.complianceCheckTx.TxOutputs { 452 | if string(txOutput.ToAddr) == p.getInitiator() { 453 | utxo := &pb.Utxo{ 454 | Amount: txOutput.Amount, 455 | ToAddr: txOutput.ToAddr, 456 | RefTxid: p.complianceCheckTx.Txid, 457 | RefOffset: int32(index), 458 | } 459 | utxolist = append(utxolist, utxo) 460 | 461 | utxoAmount := big.NewInt(0).SetBytes(utxo.Amount) 462 | totalSelected.Add(totalSelected, utxoAmount) 463 | } 464 | } 465 | utxoOutput = &pb.UtxoOutput{ 466 | UtxoList: utxolist, 467 | TotalSelected: totalSelected.String(), 468 | } 469 | 470 | } else { 471 | if preResp.UtxoOutput != nil { 472 | utxoOutput.UtxoList = preResp.GetUtxoOutput().GetUtxoList() 473 | utxoOutput.TotalSelected = preResp.GetUtxoOutput().GetTotalSelected() 474 | 475 | var ok bool 476 | totalSelected, ok = big.NewInt(0).SetString(preResp.GetUtxoOutput().GetTotalSelected(), 10) 477 | if !ok { 478 | return nil, common.ErrInvalidAmount 479 | } 480 | } 481 | 482 | // fee from account 483 | if p.feePreResp != nil { 484 | utxoOutput.UtxoList = append(utxoOutput.UtxoList, p.feePreResp.GetUtxoList()...) 485 | } 486 | } 487 | 488 | selfAmount, err := p.calcSelfAmount(totalSelected) 489 | 490 | txOutputs, err := p.generateMultiTxOutputs(selfAmount, big.NewInt(preResp.GetResponse().GetGasUsed())) 491 | if err != nil { 492 | return nil, err 493 | } 494 | txOutputs = append(txOutputs, preResp.GetResponse().UtxoOutputs...) 495 | 496 | txInputs := p.genPureTxInputs(utxoOutput) 497 | txInputs = append(txInputs, preResp.GetResponse().UtxoInputs...) 498 | 499 | authRequire := make([]string, 0, 1) 500 | if p.complianceCheckTx != nil { 501 | authRequire = append(authRequire, p.cfg.ComplianceCheck.ComplianceCheckEndorseServiceAddr) 502 | } 503 | 504 | authRequire = append(authRequire, p.request.initiatorAccount.GetAuthRequire()) 505 | 506 | if len(p.request.opt.otherAuthRequire) > 0 { 507 | authRequire = append(authRequire, p.request.opt.otherAuthRequire...) 508 | } 509 | 510 | err = common.SetSeed() 511 | if err != nil { 512 | return nil, errors.Wrap(err, "Set seed failed") 513 | } 514 | 515 | tx := &pb.Transaction{ 516 | Desc: []byte(p.request.opt.desc), 517 | Version: p.txVersion, 518 | Coinbase: false, 519 | Nonce: common.GetNonce(), 520 | Timestamp: time.Now().UnixNano(), 521 | TxInputs: txInputs, 522 | TxOutputs: txOutputs, 523 | Initiator: p.getInitiator(), 524 | AuthRequire: authRequire, 525 | TxInputsExt: preResp.GetResponse().GetInputs(), 526 | TxOutputsExt: preResp.GetResponse().GetOutputs(), 527 | ContractRequests: preResp.GetResponse().GetRequests(), 528 | } 529 | 530 | return tx, nil 531 | } 532 | 533 | func (p *Proposal) genPureTxInputs(utxoOutputs *pb.UtxoOutput) []*pb.TxInput { 534 | var txInputs []*pb.TxInput 535 | for _, utxo := range utxoOutputs.UtxoList { 536 | txInput := &pb.TxInput{} 537 | txInput.RefTxid = utxo.RefTxid 538 | txInput.RefOffset = utxo.RefOffset 539 | txInput.FromAddr = utxo.ToAddr 540 | txInput.Amount = utxo.Amount 541 | txInputs = append(txInputs, txInput) 542 | } 543 | 544 | return txInputs 545 | } 546 | 547 | func (p *Proposal) genSelectUtxoRequest(address, amount string) *pb.UtxoInput { 548 | return &pb.UtxoInput{ 549 | Bcname: p.getChainName(), 550 | Address: address, 551 | TotalNeed: amount, 552 | } 553 | } 554 | 555 | func (p *Proposal) genPreExecUtxoRequest() (*pb.PreExecWithSelectUTXORequest, error) { 556 | utxoAddr := p.getInitiator() 557 | 558 | totalAmount, err := p.calcTotalAmount() 559 | if err != nil { 560 | return nil, err 561 | } 562 | 563 | invokeRPCReq, err := p.genInvokeRPCRequest() 564 | if err != nil { 565 | return nil, err 566 | } 567 | 568 | req := &pb.PreExecWithSelectUTXORequest{ 569 | Bcname: p.getChainName(), 570 | Address: utxoAddr, 571 | TotalAmount: totalAmount, 572 | Request: invokeRPCReq, 573 | } 574 | 575 | return req, nil 576 | } 577 | 578 | func (p *Proposal) generateMultiTxOutputs(selfAmount string, gasUsed *big.Int) ([]*pb.TxOutput, error) { 579 | realSelfAmount, ok := new(big.Int).SetString(selfAmount, 10) 580 | if !ok { 581 | return nil, common.ErrInvalidAmount 582 | } 583 | 584 | var txOutputs []*pb.TxOutput 585 | req := p.request 586 | 587 | // 1. transfer 588 | if req.transferTo != "" { 589 | txOutput, err := p.makeTxOutput(req.transferTo, req.transferAmount) 590 | if err != nil { 591 | return nil, err 592 | } 593 | txOutputs = append(txOutputs, txOutput) 594 | } 595 | 596 | // 2. transfer to contract 597 | if req.opt.contractInvokeAmount != "" { 598 | txOutput, err := p.makeTxOutput(req.contractName, req.opt.contractInvokeAmount) 599 | if err != nil { 600 | return nil, err 601 | } 602 | txOutputs = append(txOutputs, txOutput) 603 | } 604 | 605 | // 3. self 606 | if realSelfAmount.Cmp(big.NewInt(0)) > 0 { 607 | txOutput, err := p.makeTxOutput(p.getInitiator(), selfAmount) 608 | if err != nil { 609 | return nil, err 610 | } 611 | txOutputs = append(txOutputs, txOutput) 612 | } 613 | 614 | // 4. fee & gasUsed 615 | feeOutput, err := p.makeFeeTxOutput() 616 | if err != nil { 617 | return nil, err 618 | } 619 | txOutputs = append(txOutputs, feeOutput...) 620 | 621 | return txOutputs, nil 622 | } 623 | 624 | func (p *Proposal) makeFeeTxOutput() ([]*pb.TxOutput, error) { 625 | txOutputs := make([]*pb.TxOutput, 0, 1) 626 | fee, err := p.calcAllFee() 627 | if err != nil { 628 | return nil, err 629 | } 630 | 631 | // no gasUsed & fee. 632 | if fee.Cmp(big.NewInt(0)) <= 0 { 633 | return txOutputs, nil 634 | } 635 | 636 | if fee.Cmp(big.NewInt(0)) > 0 { 637 | txOutput, err := p.makeTxOutput("$", fee.String()) 638 | if err != nil { 639 | return nil, err 640 | } 641 | txOutputs = append(txOutputs, txOutput) 642 | } 643 | 644 | // fee from contract account, calc account self output. 645 | if p.request.opt.onlyFeeFromAccount && p.feePreResp != nil { 646 | total, ok := big.NewInt(0).SetString(p.feePreResp.GetTotalSelected(), 10) 647 | if !ok { 648 | return nil, errors.New("invalid proposal feePreResp totalSelected") 649 | } 650 | feeSelf := total.Sub(total, fee) 651 | 652 | txOutput, err := p.makeTxOutput(p.request.initiatorAccount.GetContractAccount(), feeSelf.String()) 653 | if err != nil { 654 | return nil, err 655 | } 656 | txOutputs = append(txOutputs, txOutput) 657 | } 658 | 659 | return txOutputs, nil 660 | } 661 | 662 | func (p *Proposal) calcAllFee() (*big.Int, error) { 663 | allFee := big.NewInt(0) 664 | if p.request.opt.fee != "" { 665 | fee, ok := big.NewInt(0).SetString(p.request.opt.fee, 10) 666 | if !ok { 667 | return nil, common.ErrInvalidAmount 668 | } 669 | allFee.Add(allFee, fee) 670 | } 671 | 672 | // gas 673 | gasUsed := big.NewInt(p.preResp.GetResponse().GetGasUsed()) 674 | allFee.Add(allFee, gasUsed) 675 | 676 | return allFee, nil 677 | } 678 | 679 | func (p *Proposal) makeTxOutput(addr, amount string) (*pb.TxOutput, error) { 680 | txOutput := new(pb.TxOutput) 681 | txOutput.ToAddr = []byte(addr) 682 | realToAmount, ok := new(big.Int).SetString(amount, 10) 683 | if !ok { 684 | return nil, common.ErrInvalidAmount 685 | } 686 | txOutput.Amount = realToAmount.Bytes() 687 | 688 | return txOutput, nil 689 | } 690 | 691 | func (p *Proposal) getChainName() string { 692 | chainName := defaultChainName 693 | if p.request.opt.bcname != "" { 694 | chainName = p.request.opt.bcname 695 | } 696 | 697 | return chainName 698 | } 699 | 700 | func (p *Proposal) getInitiator() string { 701 | initiator := p.request.initiatorAccount.Address 702 | if p.cfg.ComplianceCheck.IsNeedComplianceCheck || p.request.opt.onlyFeeFromAccount || p.request.opt.gasFromAddress { 703 | return initiator 704 | } 705 | 706 | if p.request.initiatorAccount.HasContractAccount() { 707 | initiator = p.request.initiatorAccount.GetContractAccount() 708 | } 709 | 710 | return initiator 711 | } 712 | 713 | func (p *Proposal) genInvokeRequests() ([]*pb.InvokeRequest, error) { 714 | r := p.request 715 | if r.contractName == "" && r.opt.contractInvokeAmount != "" { 716 | return nil, errors.New("can not set contract invoke amount") 717 | } 718 | 719 | if r.module == "" { 720 | return nil, nil 721 | } 722 | 723 | invokeReq := &pb.InvokeRequest{ 724 | ModuleName: r.module, 725 | ContractName: r.contractName, 726 | MethodName: r.methodName, 727 | Args: r.args, 728 | Amount: r.opt.contractInvokeAmount, 729 | } 730 | 731 | return []*pb.InvokeRequest{invokeReq}, nil 732 | } 733 | 734 | func (p *Proposal) genInvokeRPCRequest() (*pb.InvokeRPCRequest, error) { 735 | invokeRequests, err := p.genInvokeRequests() 736 | if err != nil { 737 | return nil, err 738 | } 739 | 740 | authRequires := make([]string, 0, 1) 741 | if p.cfg.ComplianceCheck.IsNeedComplianceCheck { 742 | authRequires = append(authRequires, p.cfg.ComplianceCheck.ComplianceCheckEndorseServiceAddr) 743 | } 744 | 745 | authRequires = append(authRequires, p.request.initiatorAccount.GetAuthRequire()) 746 | 747 | if len(p.request.opt.otherAuthRequire) > 0 { 748 | authRequires = append(authRequires, p.request.opt.otherAuthRequire...) 749 | } 750 | 751 | invokeRPCReq := &pb.InvokeRPCRequest{ 752 | Bcname: p.getChainName(), 753 | Requests: invokeRequests, 754 | Initiator: p.getInitiator(), 755 | AuthRequire: authRequires, 756 | } 757 | 758 | return invokeRPCReq, nil 759 | } 760 | 761 | func (p *Proposal) calcTotalAmount() (int64, error) { 762 | var totalAmount int64 763 | req := p.request 764 | if req.transferAmount != "" { 765 | if amount, err := strconv.ParseInt(req.transferAmount, 10, 64); err == nil { 766 | totalAmount += amount 767 | } else { 768 | return 0, err 769 | } 770 | } 771 | 772 | if !req.opt.onlyFeeFromAccount && req.opt.fee != "" { 773 | if amount, err := strconv.ParseInt(req.opt.fee, 10, 64); err == nil { 774 | totalAmount += amount 775 | } else { 776 | return 0, err 777 | } 778 | } 779 | 780 | if req.opt.contractInvokeAmount != "" { 781 | if amount, err := strconv.ParseInt(req.opt.contractInvokeAmount, 10, 64); err == nil { 782 | totalAmount += amount 783 | } else { 784 | return 0, err 785 | } 786 | } 787 | 788 | // endorser logic 789 | if p.cfg.ComplianceCheck.IsNeedComplianceCheck && p.cfg.ComplianceCheck.IsNeedComplianceCheckFee { 790 | totalAmount += int64(p.cfg.ComplianceCheck.ComplianceCheckEndorseServiceFee) 791 | } 792 | 793 | return totalAmount, nil 794 | } 795 | -------------------------------------------------------------------------------- /xuper/xuperclient_test.go: -------------------------------------------------------------------------------- 1 | package xuper 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "math/big" 8 | "testing" 9 | "time" 10 | 11 | "github.com/xuperchain/xuper-sdk-go/v2/account" 12 | "github.com/xuperchain/xuper-sdk-go/v2/common/config" 13 | "github.com/xuperchain/xuperchain/service/pb" 14 | ) 15 | 16 | func TestNewXClient(t *testing.T) { 17 | x, err := New("127.0.0.1:9999") 18 | if err != nil { 19 | t.Error("New xuperclient asser failed:", err) 20 | } 21 | x.Close() 22 | 23 | _, err = New("127.0.0.1:9999", WithConfigFile("./conf/sdk.yaml")) 24 | if err == nil { 25 | t.Error("New xuperclient asser failed:", err) 26 | } 27 | 28 | x, err = New("127.0.0.1:9999", WithGrpcGZIP()) 29 | if err != nil { 30 | t.Error("New xuperclient asser failed:", err) 31 | } 32 | x.Close() 33 | _, err = New("127.0.0.1:9999", WithGrpcGZIP(), WithGrpcTLS("aaa", "aaa", "aaa", "aaa")) 34 | if err == nil { 35 | t.Error("New xuperclient asser failed:", err) 36 | } 37 | } 38 | 39 | func String(t *pb.Transaction) string { 40 | b, err := json.Marshal(*t) 41 | if err != nil { 42 | return fmt.Sprintf("%+v", *t) 43 | } 44 | var out bytes.Buffer 45 | err = json.Indent(&out, b, "", " ") 46 | if err != nil { 47 | return fmt.Sprintf("%+v", *t) 48 | } 49 | return out.String() 50 | } 51 | 52 | func newClient() *XClient { 53 | if testNode == "" { 54 | return &XClient{ 55 | xc: &MockXClient{}, 56 | ec: &MockEClient{}, 57 | esc: &MockESClient{}, 58 | } 59 | } 60 | return xclient 61 | } 62 | 63 | func TestXClient_WatchBlockEvent(t *testing.T) { 64 | client := newClient() 65 | watcher, err := client.WatchBlockEvent() 66 | if err != nil { 67 | t.Error(err) 68 | } 69 | 70 | go func() { 71 | select { 72 | case _, ok := <-watcher.FilteredBlockChan: 73 | if !ok { 74 | t.Error("unexpected closed channel") 75 | } 76 | case <-time.After(5 * time.Second): 77 | t.Error("timed out waiting for block event") 78 | } 79 | }() 80 | 81 | time.Sleep(time.Second * 5) 82 | } 83 | 84 | func TestXClient_QueryTxByID(t *testing.T) { 85 | txID := "b1ae1868c4e46651657b5aa9be20ab284d36161c9cc311787a9e81e391dc2bed" 86 | client := newClient() 87 | 88 | tx, err := client.QueryTxByID(txID) 89 | if err != nil { 90 | t.Fatalf("err:%v\n", err) 91 | } 92 | 93 | fmt.Printf("tx:%v\n", tx) 94 | } 95 | 96 | func TestXClient_QueryBlockByHeight(t *testing.T) { 97 | client := newClient() 98 | height := int64(213822) 99 | 100 | block, err := client.QueryBlockByHeight(height) 101 | if err != nil { 102 | t.Fatalf("err:%v\n", err.Error()) 103 | } 104 | 105 | fmt.Printf("block:%v\n", block) 106 | } 107 | 108 | func TestXClient_QueryBlockByID(t *testing.T) { 109 | client := newClient() 110 | blockID := "b4213754f1f645a9bbcf5cfe13de65d019dd843b76929782d78045fff3cceace" 111 | 112 | block, err := client.QueryBlockByID(blockID) 113 | if err != nil { 114 | t.Fatalf("err:%v\n", err.Error()) 115 | } 116 | 117 | fmt.Printf("block:%+v\n", block) 118 | } 119 | 120 | func TestXClient_QueryAccountAcl(t *testing.T) { //todo 121 | client := newClient() 122 | account := "XC1111111111111111@xuper" 123 | 124 | acl, err := client.QueryAccountACL(account) 125 | if err != nil { 126 | t.Fatalf("err:%v\n", err.Error()) 127 | } 128 | 129 | fmt.Printf("acl:%v\n", acl) 130 | } 131 | 132 | func TestXClient_QueryMethodAcl(t *testing.T) { // todo 133 | name := "test0611" 134 | method := "testUint256Event" 135 | client := newClient() 136 | 137 | acl, err := client.QueryMethodACL(name, method) 138 | if err != nil { 139 | t.Fatalf("err:%s\n", err.Error()) 140 | } 141 | 142 | fmt.Printf("%v\n", acl) 143 | } 144 | 145 | func TestXClient_QueryAccountContracts(t *testing.T) { 146 | account := "XC1111111111111111@xuper" 147 | client := newClient() 148 | 149 | contracts, err := client.QueryAccountContracts(account) 150 | if err != nil { 151 | t.Fatalf("err:%s\n", err.Error()) 152 | } 153 | 154 | for _, contract := range contracts { 155 | fmt.Printf("%v\n", contract) 156 | } 157 | } 158 | 159 | func TestXClient_QueryAddressContracts(t *testing.T) { 160 | address := "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN" 161 | client := newClient() 162 | 163 | contracts, err := client.QueryAddressContracts(address) 164 | if err != nil { 165 | t.Fatalf("err:%s\n", err) 166 | } 167 | 168 | for _, contract := range contracts { 169 | fmt.Printf("%v\n", contract) 170 | } 171 | } 172 | 173 | func TestXClient_QueryBalance(t *testing.T) { 174 | address := "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN" 175 | client := newClient() 176 | 177 | bal, err := client.QueryBalance(address) 178 | if err != nil { 179 | t.Fatalf("err:%s\n", err.Error()) 180 | } 181 | 182 | fmt.Printf("%v\n", bal) 183 | } 184 | 185 | func TestXClient_QueryBalanceDetail(t *testing.T) { 186 | address := "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN" 187 | client := newClient() 188 | 189 | addrBalanceStatus, err := client.QueryBalanceDetail(address) 190 | if err != nil { 191 | t.Fatalf("err:%s\n", err.Error()) 192 | } 193 | 194 | fmt.Printf("%v\n", addrBalanceStatus) 195 | } 196 | 197 | func TestXClient_QuerySystemStatus(t *testing.T) { 198 | client := newClient() 199 | 200 | reply, err := client.QuerySystemStatus() 201 | if err != nil { 202 | t.Fatalf("err:%s\n", err) 203 | } 204 | 205 | fmt.Printf("reply:%v\n", reply) 206 | } 207 | 208 | func TestXClient_QueryBlockChains(t *testing.T) { 209 | client := newClient() 210 | 211 | bcStatus, err := client.QueryBlockChains() 212 | if err != nil { 213 | t.Fatalf("err:%v", err) 214 | } 215 | 216 | fmt.Printf("bcStatus:%v\n", bcStatus) 217 | } 218 | 219 | func TestXClient_QueryBlockChainStatus(t *testing.T) { 220 | client := newClient() 221 | 222 | bcStatus, err := client.QueryBlockChainStatus() 223 | if err != nil { 224 | t.Fatalf("err:%v\n", err) 225 | } 226 | 227 | fmt.Printf("bcStatus:%v\n", bcStatus) 228 | } 229 | 230 | func TestXClient_QueryNetURL(t *testing.T) { 231 | client := newClient() 232 | 233 | rawURL, err := client.QueryNetURL() 234 | if err != nil { 235 | t.Fatalf("err:%v\n", err) 236 | } 237 | 238 | fmt.Printf("rawURL:%v\n", rawURL) 239 | } 240 | 241 | func TestXClient_QueryAccountByAK(t *testing.T) { 242 | address := "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN" 243 | client := newClient() 244 | 245 | resp, err := client.QueryAccountByAK(address) 246 | if err != nil { 247 | t.Fatalf("err:%v\n", err) 248 | } 249 | 250 | fmt.Printf("resp:%v\n", resp) 251 | } 252 | 253 | var ( 254 | testNode string = "" // 如果搭建了 xchain 节点,URL 写在这里就会发送交易到这个节点。//10.12.199.82:8701 255 | wasmCppCounterFile = "" // 还需要编译counter wasm合约然后拷贝过来。 256 | ) 257 | 258 | var ( 259 | // 地址:QZcRXTPwtYo2UtZXNRppEC3Mto2AvmgKZ,有节点的话别忘了给这个账户转钱。 260 | mnemonic = "刮 腰 秀 算 图 称 洛 校 新 韩 哈 门" 261 | aks = []*account.Account{} 262 | contractAcc = []string{ 263 | "XC0000000000000000@xuper", 264 | "XC1111111111111111@xuper", 265 | "XC2222222222222222@xuper", 266 | "XC3333333333333333@xuper", 267 | "XC4444444444444444@xuper", 268 | "XC5555555555555555@xuper", 269 | "XC6666666666666666@xuper", 270 | "XC7777777777777777@xuper", 271 | "XC8888888888888888@xuper", 272 | "XC9999999999999999@xuper", 273 | } 274 | 275 | xclient *XClient = nil 276 | ) 277 | 278 | // 创建10个AK,每个AK创建一个合约账户从0-9. 279 | // 如果启动了节点,需要先手动给 QZcRXTPwtYo2UtZXNRppEC3Mto2AvmgKZ 这个地址转账大约100000000000。 280 | // 助记词:刮 腰 秀 算 图 称 洛 校 新 韩 哈 门 281 | func init() { 282 | for i := 0; i < len(contractAcc); i++ { 283 | acc, _ := account.CreateAccount(1, 1) 284 | aks = append(aks, acc) 285 | if testNode != "" { 286 | var err error 287 | richAcc, _ := account.RetrieveAccount(mnemonic, 1) 288 | if xclient == nil { 289 | xclient, err = New(testNode) 290 | if err != nil { 291 | panic(err) 292 | } 293 | } 294 | 295 | // 转账 296 | _, err = xclient.Transfer(richAcc, acc.Address, "1000000000") 297 | if err != nil { 298 | panic(err) 299 | } 300 | _, err = xclient.CreateContractAccount(acc, contractAcc[i]) 301 | if err != nil { 302 | panic(err) 303 | } 304 | // 转账给合约账户 305 | _, err = xclient.Transfer(richAcc, contractAcc[i], "1000000000") 306 | if err != nil { 307 | panic(err) 308 | } 309 | } 310 | } 311 | if testNode != "" { 312 | fmt.Println("Test real node client start...") 313 | } else { 314 | fmt.Println("Test mock client start...") 315 | } 316 | } 317 | 318 | func TestTransfers(t *testing.T) { 319 | type Case struct { 320 | from *account.Account 321 | to string 322 | amount string 323 | fee *big.Int 324 | opts []RequestOption 325 | cfg *config.CommConfig 326 | hasContractAcc bool 327 | onlyFeeFromContractAccc bool 328 | desc string 329 | } 330 | 331 | cases := []Case{ 332 | { 333 | from: aks[0], 334 | to: aks[1].Address, 335 | amount: "100", 336 | cfg: &config.CommConfig{ 337 | ComplianceCheck: config.ComplianceCheckConfig{}, 338 | }, 339 | desc: "正常转账100,没有 fee。", 340 | }, 341 | { 342 | from: aks[0], 343 | to: aks[1].Address, 344 | amount: "100", 345 | cfg: &config.CommConfig{ 346 | ComplianceCheck: config.ComplianceCheckConfig{}, 347 | }, 348 | fee: big.NewInt(100), 349 | desc: "正常转账 fee 100", 350 | opts: []RequestOption{WithFee("100")}, 351 | }, 352 | { 353 | from: aks[0], 354 | to: aks[1].Address, 355 | amount: "100", 356 | hasContractAcc: true, 357 | cfg: &config.CommConfig{ 358 | ComplianceCheck: config.ComplianceCheckConfig{}, 359 | }, 360 | fee: big.NewInt(100), 361 | desc: "有合约账户,转账 fee 100", 362 | opts: []RequestOption{WithFee("100")}, 363 | }, 364 | { 365 | from: aks[0], 366 | to: aks[1].Address, 367 | amount: "100", 368 | hasContractAcc: true, 369 | cfg: &config.CommConfig{ 370 | ComplianceCheck: config.ComplianceCheckConfig{}, 371 | }, 372 | fee: big.NewInt(100), 373 | desc: "有合约账户,转账 fee 100", 374 | opts: []RequestOption{WithFee("100"), WithFeeFromAccount()}, 375 | onlyFeeFromContractAccc: true, 376 | }, 377 | { 378 | from: aks[0], 379 | to: aks[1].Address, 380 | amount: "100", 381 | hasContractAcc: true, 382 | cfg: &config.CommConfig{ 383 | ComplianceCheck: config.ComplianceCheckConfig{ 384 | IsNeedComplianceCheck: true, 385 | IsNeedComplianceCheckFee: true, 386 | ComplianceCheckEndorseServiceFee: 100, 387 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 388 | ComplianceCheckEndorseServiceAddr: "bbb", 389 | }, 390 | }, 391 | fee: big.NewInt(100), 392 | desc: "正常转账 fee 100", 393 | opts: []RequestOption{WithFee("100")}, 394 | }, 395 | } 396 | 397 | for _, c := range cases { 398 | if testNode != "" { 399 | // 转账前先查询两个账户余额 400 | bal, err := xclient.QueryBalance(c.from.Address) 401 | if err != nil { 402 | t.Error(err) 403 | } 404 | fmt.Println(bal) 405 | 406 | if c.hasContractAcc { 407 | c.from.SetContractAccount(contractAcc[0]) 408 | } 409 | xclient.Transfer(c.from, c.to, c.amount, c.opts...) 410 | c.from.RemoveContractAccount() 411 | time.Sleep(time.Millisecond * 500) 412 | // 转账后再查询两个账户余额 413 | } else { 414 | // mock 不检查余额。 415 | xc := newClient() 416 | xc.cfg = c.cfg 417 | if c.hasContractAcc { 418 | c.from.SetContractAccount(contractAcc[0]) 419 | } 420 | _, err := xc.Transfer(c.from, c.to, c.amount, c.opts...) 421 | if err != nil { 422 | t.Error(err) 423 | } 424 | c.from.RemoveContractAccount() 425 | xc.cfg = nil 426 | } 427 | } 428 | } 429 | 430 | func string2Bigint(v string) *big.Int { 431 | b, ok := big.NewInt(0).SetString(v, 10) 432 | if !ok { 433 | panic("string 2 bigint failed:" + v) 434 | } 435 | return b 436 | } 437 | 438 | func TestDeployContract(t *testing.T) { 439 | 440 | type Case struct { 441 | from *account.Account 442 | name string 443 | runtime string 444 | abi []byte 445 | code []byte 446 | args map[string]string 447 | fee *big.Int 448 | opts []RequestOption 449 | cfg *config.CommConfig 450 | hasContractAcc bool 451 | onlyFeeFromContractAccc bool 452 | desc string 453 | } 454 | 455 | cases := []Case{ 456 | { 457 | from: aks[1], 458 | name: "hello", 459 | code: []byte("code"), 460 | runtime: GoRuntime, 461 | cfg: &config.CommConfig{ 462 | ComplianceCheck: config.ComplianceCheckConfig{}, 463 | }, 464 | hasContractAcc: true, 465 | desc: "部署 go native 合约。", 466 | }, 467 | { 468 | from: aks[1], 469 | name: "hello", 470 | code: []byte("code"), 471 | runtime: JavaRuntime, 472 | cfg: &config.CommConfig{ 473 | ComplianceCheck: config.ComplianceCheckConfig{}, 474 | }, 475 | hasContractAcc: true, 476 | onlyFeeFromContractAccc: true, 477 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10")}, 478 | desc: "部署 java native 合约,account 支付 fee,fee=10。", 479 | }, 480 | { 481 | from: aks[1], 482 | name: "hello", 483 | code: []byte("code"), 484 | runtime: EvmContractModule, 485 | cfg: &config.CommConfig{ 486 | ComplianceCheck: config.ComplianceCheckConfig{ 487 | IsNeedComplianceCheck: true, 488 | }, 489 | }, 490 | hasContractAcc: true, 491 | desc: "部署 evm 合约,需要背书,不需要背书手续费。", 492 | }, 493 | { 494 | from: aks[1], 495 | name: "hello", 496 | code: []byte("code"), 497 | runtime: CRuntime, 498 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10")}, 499 | cfg: &config.CommConfig{ 500 | ComplianceCheck: config.ComplianceCheckConfig{ 501 | IsNeedComplianceCheck: true, 502 | IsNeedComplianceCheckFee: true, 503 | ComplianceCheckEndorseServiceFee: 100, 504 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 505 | ComplianceCheckEndorseServiceAddr: "bbb", 506 | }, 507 | }, 508 | hasContractAcc: true, 509 | desc: "部署 wasm 合约,背书手续费100", 510 | }, 511 | { 512 | from: aks[1], 513 | name: "hello", 514 | code: []byte("code"), 515 | runtime: CRuntime, 516 | cfg: &config.CommConfig{ 517 | ComplianceCheck: config.ComplianceCheckConfig{ 518 | IsNeedComplianceCheck: true, 519 | IsNeedComplianceCheckFee: true, 520 | ComplianceCheckEndorseServiceFee: 100, 521 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 522 | ComplianceCheckEndorseServiceAddr: "bbb", 523 | }, 524 | }, 525 | hasContractAcc: true, 526 | desc: "部署 wasm 合约,背书手续费100,account 支付手续费,fee=10。", 527 | }, 528 | } 529 | 530 | for _, c := range cases { 531 | if testNode != "" { 532 | panic("Please implement me") 533 | } else { 534 | // mock 不检查余额。 535 | xc := newClient() 536 | xc.cfg = c.cfg 537 | if c.hasContractAcc { 538 | c.from.SetContractAccount(contractAcc[1]) 539 | } 540 | 541 | switch c.runtime { 542 | case JavaRuntime: 543 | _, err := xc.DeployNativeJavaContract(c.from, c.name, c.code, c.args, c.opts...) 544 | if err != nil { 545 | t.Error(err) 546 | } 547 | 548 | _, err = xc.UpgradeNativeContract(c.from, c.name, c.code, c.opts...) 549 | if err != nil { 550 | t.Error(err) 551 | } 552 | case GoRuntime: 553 | _, err := xc.DeployNativeGoContract(c.from, c.name, c.code, c.args, c.opts...) 554 | if err != nil { 555 | t.Error(err) 556 | } 557 | 558 | case CRuntime: 559 | _, err := xc.DeployWasmContract(c.from, c.name, c.code, c.args, c.opts...) 560 | if err != nil { 561 | t.Error(err) 562 | } 563 | 564 | _, err = xc.UpgradeWasmContract(c.from, c.name, c.code, c.opts...) 565 | if err != nil { 566 | t.Error(err) 567 | } 568 | case EvmContractModule: 569 | _, err := xc.DeployEVMContract(c.from, c.name, c.abi, c.code, c.args, c.opts...) 570 | if err != nil { 571 | t.Error(err) 572 | } 573 | default: 574 | } 575 | c.from.RemoveContractAccount() 576 | xc.cfg = nil 577 | } 578 | } 579 | } 580 | 581 | func TestInvokeContract(t *testing.T) { 582 | type Case struct { 583 | from *account.Account 584 | name string 585 | module string 586 | method string 587 | args map[string]string 588 | fee *big.Int 589 | opts []RequestOption 590 | cfg *config.CommConfig 591 | hasContractAcc bool 592 | onlyFeeFromContractAccc bool 593 | desc string 594 | } 595 | 596 | cases := []Case{ 597 | { 598 | from: aks[1], 599 | name: "hello", 600 | method: "a", 601 | module: NativeContractModule, 602 | cfg: &config.CommConfig{ 603 | ComplianceCheck: config.ComplianceCheckConfig{}, 604 | }, 605 | hasContractAcc: true, 606 | desc: "调用 native 合约。", 607 | }, 608 | { 609 | from: aks[1], 610 | name: "hello", 611 | method: "a", 612 | module: WasmContractModule, 613 | cfg: &config.CommConfig{ 614 | ComplianceCheck: config.ComplianceCheckConfig{}, 615 | }, 616 | hasContractAcc: true, 617 | onlyFeeFromContractAccc: true, 618 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10")}, 619 | desc: "调用 wasm 合约,account 支付 fee,fee=10。", 620 | }, 621 | { 622 | from: aks[1], 623 | name: "hello", 624 | method: "a", 625 | module: WasmContractModule, 626 | cfg: &config.CommConfig{ 627 | ComplianceCheck: config.ComplianceCheckConfig{ 628 | IsNeedComplianceCheck: true, 629 | }, 630 | }, 631 | hasContractAcc: true, 632 | desc: "调用 wasm 合约,需要背书,不需要背书手续费。", 633 | }, 634 | { 635 | from: aks[1], 636 | name: "hello", 637 | method: "a", 638 | module: WasmContractModule, 639 | opts: []RequestOption{WithContractInvokeAmount("10"), WithBcname("xuper"), 640 | WithDesc("haha"), WithOtherAuthRequires([]string{"a", "b"})}, 641 | cfg: &config.CommConfig{ 642 | ComplianceCheck: config.ComplianceCheckConfig{ 643 | IsNeedComplianceCheck: true, 644 | IsNeedComplianceCheckFee: true, 645 | ComplianceCheckEndorseServiceFee: 100, 646 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 647 | ComplianceCheckEndorseServiceAddr: "bbb", 648 | }, 649 | }, 650 | hasContractAcc: true, 651 | desc: "调用 wasm 合约,背书手续费100", 652 | }, 653 | { 654 | from: aks[1], 655 | name: "hello", 656 | method: "a", 657 | module: EvmContractModule, 658 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10")}, 659 | cfg: &config.CommConfig{ 660 | ComplianceCheck: config.ComplianceCheckConfig{ 661 | IsNeedComplianceCheck: true, 662 | IsNeedComplianceCheckFee: true, 663 | ComplianceCheckEndorseServiceFee: 100, 664 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 665 | ComplianceCheckEndorseServiceAddr: "bbb", 666 | }, 667 | }, 668 | hasContractAcc: true, 669 | desc: "调用 wasm 合约,背书手续费100,account 支付手续费,fee=10。", 670 | }, 671 | } 672 | 673 | for _, c := range cases { 674 | if testNode != "" { 675 | panic("Please implement me") 676 | } else { 677 | // mock 不检查余额。 678 | xc := newClient() 679 | xc.cfg = c.cfg 680 | if c.hasContractAcc { 681 | c.from.SetContractAccount(contractAcc[1]) 682 | } 683 | 684 | switch c.module { 685 | case NativeContractModule: 686 | _, err := xc.InvokeNativeContract(c.from, c.name, c.method, c.args, c.opts...) 687 | if err != nil { 688 | t.Error(err) 689 | } 690 | 691 | _, err = xc.QueryNativeContract(c.from, c.name, c.method, c.args, c.opts...) 692 | if err != nil { 693 | t.Error(err) 694 | } 695 | case WasmContractModule: 696 | _, err := xc.InvokeWasmContract(c.from, c.name, c.method, c.args, c.opts...) 697 | if err != nil { 698 | t.Error(err) 699 | } 700 | _, err = xc.QueryWasmContract(c.from, c.name, c.method, c.args, c.opts...) 701 | if err != nil { 702 | t.Error(err) 703 | } 704 | case EvmContractModule: 705 | _, err := xc.InvokeEVMContract(c.from, c.name, c.method, c.args, c.opts...) 706 | if err != nil { 707 | t.Error(err) 708 | } 709 | _, err = xc.QueryEVMContract(c.from, c.name, c.method, c.args, c.opts...) 710 | if err != nil { 711 | t.Error(err) 712 | } 713 | default: 714 | } 715 | c.from.RemoveContractAccount() 716 | xc.cfg = nil 717 | } 718 | } 719 | } 720 | 721 | func TestACLSet(t *testing.T) { 722 | type Case struct { 723 | from *account.Account 724 | name string 725 | method string 726 | acl *ACL 727 | fee *big.Int 728 | opts []RequestOption 729 | cfg *config.CommConfig 730 | hasContractAcc bool 731 | onlyFeeFromContractAccc bool 732 | desc string 733 | } 734 | 735 | cases := []Case{ 736 | { 737 | from: aks[1], 738 | name: "hello", 739 | method: "a", 740 | acl: getDefaultACL("a"), 741 | cfg: &config.CommConfig{ 742 | ComplianceCheck: config.ComplianceCheckConfig{}, 743 | }, 744 | hasContractAcc: true, 745 | desc: "设置合约方法 ACL。", 746 | }, 747 | { 748 | from: aks[1], 749 | acl: getDefaultACL("a"), 750 | cfg: &config.CommConfig{ 751 | ComplianceCheck: config.ComplianceCheckConfig{}, 752 | }, 753 | hasContractAcc: true, 754 | desc: "设置合约账户 ACL。", 755 | }, 756 | { 757 | from: aks[1], 758 | name: "hello", 759 | method: "a", 760 | acl: getDefaultACL("a"), 761 | cfg: &config.CommConfig{ 762 | ComplianceCheck: config.ComplianceCheckConfig{}, 763 | }, 764 | hasContractAcc: true, 765 | onlyFeeFromContractAccc: true, 766 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10"), WithBcname("xuper"), 767 | WithDesc("haha"), WithOtherAuthRequires([]string{"a", "b"})}, 768 | desc: "设置方法 ACL,account 支付 fee,fee=10。", 769 | }, 770 | { 771 | from: aks[1], 772 | acl: getDefaultACL("a"), 773 | cfg: &config.CommConfig{ 774 | ComplianceCheck: config.ComplianceCheckConfig{ 775 | IsNeedComplianceCheck: true, 776 | }, 777 | }, 778 | hasContractAcc: true, 779 | desc: "设置合约账户 ACL,需要背书,不需要背书手续费。", 780 | }, 781 | { 782 | from: aks[1], 783 | name: "hello", 784 | method: "a", 785 | acl: getDefaultACL("a"), 786 | opts: []RequestOption{}, 787 | cfg: &config.CommConfig{ 788 | ComplianceCheck: config.ComplianceCheckConfig{ 789 | IsNeedComplianceCheck: true, 790 | IsNeedComplianceCheckFee: true, 791 | ComplianceCheckEndorseServiceFee: 100, 792 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 793 | ComplianceCheckEndorseServiceAddr: "bbb", 794 | }, 795 | }, 796 | hasContractAcc: true, 797 | desc: "调用 wasm 合约,背书手续费100", 798 | }, 799 | { 800 | from: aks[1], 801 | name: "hello", 802 | method: "a", 803 | acl: getDefaultACL("a"), 804 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10")}, 805 | cfg: &config.CommConfig{ 806 | ComplianceCheck: config.ComplianceCheckConfig{ 807 | IsNeedComplianceCheck: true, 808 | IsNeedComplianceCheckFee: true, 809 | ComplianceCheckEndorseServiceFee: 100, 810 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 811 | ComplianceCheckEndorseServiceAddr: "bbb", 812 | }, 813 | }, 814 | hasContractAcc: true, 815 | desc: "调用 wasm 合约,背书手续费100,account 支付手续费,fee=10。", 816 | }, 817 | } 818 | 819 | for _, c := range cases { 820 | if testNode != "" { 821 | panic("Please implement me") 822 | } else { 823 | // mock 不检查余额。 824 | xc := newClient() 825 | xc.cfg = c.cfg 826 | if c.hasContractAcc { 827 | c.from.SetContractAccount(contractAcc[1]) 828 | } 829 | 830 | if c.name != "" { 831 | _, err := xc.SetMethodACL(c.from, c.name, c.method, c.acl, c.opts...) 832 | if err != nil { 833 | t.Error(err) 834 | } 835 | } else { 836 | _, err := xc.SetAccountACL(c.from, c.acl, c.opts...) 837 | if err != nil { 838 | t.Error(err) 839 | } 840 | } 841 | c.from.RemoveContractAccount() 842 | xc.cfg = nil 843 | } 844 | } 845 | } 846 | 847 | func TestCreateAccount(t *testing.T) { 848 | type Case struct { 849 | from *account.Account 850 | account string 851 | acl *ACL 852 | fee *big.Int 853 | opts []RequestOption 854 | cfg *config.CommConfig 855 | hasContractAcc bool 856 | onlyFeeFromContractAccc bool 857 | desc string 858 | hasError bool 859 | } 860 | 861 | cases := []Case{ 862 | { 863 | from: aks[1], 864 | account: "", 865 | acl: getDefaultACL("a"), 866 | cfg: &config.CommConfig{ 867 | ComplianceCheck: config.ComplianceCheckConfig{}, 868 | }, 869 | hasError: true, 870 | desc: "创建合约账户,账户参数为空。", 871 | }, 872 | { 873 | from: aks[1], 874 | account: "XC1234567812345678@xuper", 875 | acl: getDefaultACL("a"), 876 | hasError: true, 877 | cfg: &config.CommConfig{ 878 | ComplianceCheck: config.ComplianceCheckConfig{}, 879 | }, 880 | hasContractAcc: true, 881 | desc: "创建合约账户,from 设置了合约账户。", 882 | }, 883 | { 884 | from: aks[1], 885 | account: "hello", 886 | acl: getDefaultACL("a"), 887 | hasError: true, 888 | cfg: &config.CommConfig{ 889 | ComplianceCheck: config.ComplianceCheckConfig{}, 890 | }, 891 | onlyFeeFromContractAccc: true, 892 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10"), WithBcname("xuper"), 893 | WithDesc("haha"), WithOtherAuthRequires([]string{"a", "b"})}, 894 | desc: "创建合约账户,账户不符合规则。", 895 | }, 896 | { 897 | from: aks[1], 898 | acl: getDefaultACL("a"), 899 | account: "XC1234567812345678@xuper", 900 | cfg: &config.CommConfig{ 901 | ComplianceCheck: config.ComplianceCheckConfig{ 902 | IsNeedComplianceCheck: true, 903 | }, 904 | }, 905 | hasContractAcc: false, 906 | desc: "创建合约账户 ok。", 907 | }, 908 | { 909 | from: aks[1], 910 | account: "XC1234567812345678@xuper", 911 | acl: getDefaultACL("a"), 912 | opts: []RequestOption{}, 913 | cfg: &config.CommConfig{ 914 | ComplianceCheck: config.ComplianceCheckConfig{ 915 | IsNeedComplianceCheck: true, 916 | IsNeedComplianceCheckFee: true, 917 | ComplianceCheckEndorseServiceFee: 100, 918 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 919 | ComplianceCheckEndorseServiceAddr: "bbb", 920 | }, 921 | }, 922 | desc: "创建合约账户,背书手续费100", 923 | }, 924 | { 925 | from: aks[1], 926 | account: "XC1234567812345678@xuper", 927 | acl: getDefaultACL("a"), 928 | opts: []RequestOption{WithFeeFromAccount(), WithFee("10")}, 929 | cfg: &config.CommConfig{ 930 | ComplianceCheck: config.ComplianceCheckConfig{ 931 | IsNeedComplianceCheck: true, 932 | IsNeedComplianceCheckFee: true, 933 | ComplianceCheckEndorseServiceFee: 100, 934 | ComplianceCheckEndorseServiceFeeAddr: "aaa", 935 | ComplianceCheckEndorseServiceAddr: "bbb", 936 | }, 937 | }, 938 | hasError: true, 939 | desc: "创建合约账户,背书手续费100,account 支付手续费,fee=10。", 940 | }, 941 | } 942 | 943 | for _, c := range cases { 944 | if testNode != "" { 945 | panic("Please implement me") 946 | } else { 947 | // mock 不检查余额。 948 | xc := newClient() 949 | xc.cfg = c.cfg 950 | if c.hasContractAcc { 951 | c.from.SetContractAccount(contractAcc[1]) 952 | } 953 | 954 | tx, err := xc.CreateContractAccount(c.from, c.account, c.opts...) 955 | if c.hasError { 956 | if err == nil { 957 | t.Error("Create contract assert err filed", c.desc) 958 | } 959 | } else { 960 | if err != nil { 961 | t.Error("Create contract assert err filed", err, c.desc) 962 | } 963 | 964 | if len(tx.Tx.GetTxid()) == 0 { 965 | t.Error("Create contract assert tx filed", c.desc) 966 | } 967 | } 968 | c.from.RemoveContractAccount() 969 | xc.cfg = nil 970 | } 971 | } 972 | } 973 | 974 | func TestRequest(t *testing.T) { 975 | r := new(Request) 976 | r.SetArgs(map[string][]byte{"a": []byte("a")}) 977 | r.SetContractName("counter") 978 | acc, _ := account.CreateAccount(1, 1) 979 | r.SetInitiatorAccount(acc) 980 | r.SetModule("xx") 981 | r.SetTransferAmount("10") 982 | r.SetTransferTo("bob") 983 | 984 | if r.contractName != "counter" { 985 | t.Error("Request set assert failed") 986 | } 987 | 988 | if r.module != "xx" { 989 | t.Error("Request set assert failed") 990 | } 991 | 992 | if r.transferTo != "bob" { 993 | t.Error("Request set assert failed") 994 | } 995 | 996 | if r.transferAmount != "10" { 997 | t.Error("Request set assert failed") 998 | } 999 | 1000 | if r.initiatorAccount.Address != acc.Address { 1001 | t.Error("Request set assert failed") 1002 | } 1003 | 1004 | } 1005 | --------------------------------------------------------------------------------