OSDN Git Service

refactor spend_account_unspent_output struct for add contract arguments (#1089)
authoroysheng <33340252+oysheng@users.noreply.github.com>
Mon, 25 Jun 2018 12:05:05 +0000 (20:05 +0800)
committerPaladz <yzhu101@uottawa.ca>
Mon, 25 Jun 2018 12:05:05 +0000 (20:05 +0800)
* refactor spend_account_unspent_output struct for add contract arguments
add the contract template LockWithPublicKey for build-transaction

* optimise arguments
add unit test

* optimise unit test

* optimise struct

account/builder.go
account/builder_test.go
cmd/bytomcli/commands/template.go [new file with mode: 0644]
cmd/bytomcli/commands/transaction.go

index 5672085..bf7e5cf 100644 (file)
@@ -2,6 +2,7 @@ package account
 
 import (
        "context"
+       "encoding/hex"
        "encoding/json"
 
        log "github.com/sirupsen/logrus"
@@ -11,6 +12,7 @@ import (
        "github.com/bytom/common"
        "github.com/bytom/consensus"
        "github.com/bytom/crypto/ed25519/chainkd"
+       chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/errors"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/types"
@@ -118,10 +120,27 @@ func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
 }
 
 type spendUTXOAction struct {
-       accounts *Manager
-       OutputID *bc.Hash `json:"output_id"`
+       accounts    *Manager
+       OutputID    *bc.Hash           `json:"output_id"`
+       Arguments   []contractArgument `json:"arguments"`
+       ClientToken *string            `json:"client_token"`
+}
 
-       ClientToken *string `json:"client_token"`
+// contractArgument for smart contract
+type contractArgument struct {
+       Type    string          `json:"type"`
+       RawData json.RawMessage `json:"raw_data"`
+}
+
+// rawTxSigArgument is signature-related argument for run contract
+type rawTxSigArgument struct {
+       RootXPub chainkd.XPub         `json:"xpub"`
+       Path     []chainjson.HexBytes `json:"derivation_path"`
+}
+
+// dataArgument is the other argument for run contract
+type dataArgument struct {
+       Value string `json:"value"`
 }
 
 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
@@ -148,6 +167,42 @@ func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilde
        if err != nil {
                return err
        }
+
+       if a.Arguments != nil {
+               sigInst = &txbuilder.SigningInstruction{}
+               for _, arg := range a.Arguments {
+                       switch arg.Type {
+                       case "raw_tx_signature":
+                               rawTxSig := &rawTxSigArgument{}
+                               if err = json.Unmarshal(arg.RawData, rawTxSig); err != nil {
+                                       return err
+                               }
+
+                               // convert path form chainjson.HexBytes to byte
+                               var path [][]byte
+                               for _, p := range rawTxSig.Path {
+                                       path = append(path, []byte(p))
+                               }
+                               sigInst.AddRawWitnessKeys([]chainkd.XPub{rawTxSig.RootXPub}, path, 1)
+
+                       case "data":
+                               data := &dataArgument{}
+                               if err = json.Unmarshal(arg.RawData, data); err != nil {
+                                       return err
+                               }
+
+                               value, err := hex.DecodeString(data.Value)
+                               if err != nil {
+                                       return err
+                               }
+                               sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(value))
+
+                       default:
+                               return errors.New("contract argument type is not exist")
+                       }
+               }
+       }
+
        return b.AddInput(txInput, sigInst)
 }
 
index 9ebe5ac..799c334 100644 (file)
@@ -1,10 +1,15 @@
 package account
 
 import (
+       "encoding/hex"
+       "encoding/json"
        "testing"
 
        "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/crypto/ed25519/chainkd"
+       chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/protocol/bc"
+       "github.com/bytom/testutil"
 )
 
 func TestMergeSpendAction(t *testing.T) {
@@ -350,3 +355,129 @@ func TestMergeSpendAction(t *testing.T) {
                }
        }
 }
+
+func TestSpendUTXOArguments(t *testing.T) {
+       hexXpub, err := hex.DecodeString("ba76bb52574b3f40315f2c01f1818a9072ced56e9d4b68acbef56a4d0077d08e5e34837963e4cdc54eb251aa34aad01e6ae48b140f6a2743fbb0a0abd9cf8aac")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       var xpub chainkd.XPub
+       copy(xpub[:], hexXpub)
+
+       rawTxSig := rawTxSigArgument{RootXPub: xpub, Path: []chainjson.HexBytes{{1, 1, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0}}}
+       rawTxSigMsg, err := json.Marshal(rawTxSig)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       data := dataArgument{Value: "7468697320697320612074657374"}
+       dataMsg, err := json.Marshal(data)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       cases := []struct {
+               rawAction  string
+               wantResult *spendUTXOAction
+       }{
+               {
+                       rawAction: `{ "type": "spend_account_unspent_output", "output_id": "e304de887423e4e684e483f5ae65236d47018b56cac94ef3fb8b5dd40c897e11",
+                               "arguments": [{"type": "raw_tx_signature", "raw_data": {"derivation_path": ["010100000000000000", "0100000000000000"],
+                   "xpub": "ba76bb52574b3f40315f2c01f1818a9072ced56e9d4b68acbef56a4d0077d08e5e34837963e4cdc54eb251aa34aad01e6ae48b140f6a2743fbb0a0abd9cf8aac"}}]}`,
+                       wantResult: &spendUTXOAction{
+                               OutputID: &bc.Hash{16358444424161912038, 9575923798912607085, 5116523856555233011, 18125684290607480337},
+                               Arguments: []contractArgument{
+                                       {
+                                               Type:    "raw_tx_signature",
+                                               RawData: rawTxSigMsg,
+                                       },
+                               },
+                       },
+               },
+               {
+                       rawAction: `{ "type": "spend_account_unspent_output", "output_id": "8669b5c2e0701ec1ca45cd413e46c4f1d5f794f9d9144f904f3e7da8c68c6410",
+                               "arguments": [{"type": "data", "raw_data": {"value": "7468697320697320612074657374"}}]}`,
+                       wantResult: &spendUTXOAction{
+                               OutputID: &bc.Hash{9685472322230689473, 14575281449155871985, 15417955650135936912, 5710139541391434768},
+                               Arguments: []contractArgument{
+                                       {
+                                               Type:    "data",
+                                               RawData: dataMsg,
+                                       },
+                               },
+                       },
+               },
+               {
+                       rawAction: `{ "type": "spend_account_unspent_output", "output_id": "8669b5c2e0701ec1ca45cd413e46c4f1d5f794f9d9144f904f3e7da8c68c6410",
+                               "arguments": [{"type": "signature", "raw_data": {"value": "7468697320697320612074657374"}}]}`,
+                       wantResult: &spendUTXOAction{
+                               OutputID: &bc.Hash{9685472322230689473, 14575281449155871985, 15417955650135936912, 5710139541391434768},
+                       },
+               },
+               {
+                       rawAction: `{ "type": "spend_account_unspent_output", "output_id": "8669b5c2e0701ec1ca45cd413e46c4f1d5f794f9d9144f904f3e7da8c68c6410"}`,
+                       wantResult: &spendUTXOAction{
+                               OutputID:  &bc.Hash{9685472322230689473, 14575281449155871985, 15417955650135936912, 5710139541391434768},
+                               Arguments: nil,
+                       },
+               },
+       }
+
+       for _, c := range cases {
+               var spendUTXOReq *spendUTXOAction
+               if err := json.Unmarshal([]byte(c.rawAction), &spendUTXOReq); err != nil {
+                       t.Fatalf("unmarshal spendUTXOAction error:%v", err)
+               }
+
+               if !testutil.DeepEqual(spendUTXOReq.OutputID, c.wantResult.OutputID) {
+                       t.Fatalf("OutputID gotResult=%v, wantResult=%v", spendUTXOReq.OutputID, c.wantResult.OutputID)
+               }
+
+               if spendUTXOReq.Arguments == nil {
+                       if c.wantResult.Arguments != nil {
+                               t.Fatalf("Arguments gotResult is nil, wantResult[%v] is not nil", c.wantResult.Arguments)
+                       }
+                       continue
+               }
+
+               for _, arg := range spendUTXOReq.Arguments {
+                       switch arg.Type {
+                       case "raw_tx_signature":
+                               rawTxSig := &rawTxSigArgument{}
+                               if err := json.Unmarshal(arg.RawData, rawTxSig); err != nil {
+                                       t.Fatalf("unmarshal rawTxSigArgument error:%v", err)
+                               }
+
+                               wantRawTxSig := &rawTxSigArgument{}
+                               if err := json.Unmarshal(c.wantResult.Arguments[0].RawData, wantRawTxSig); err != nil {
+                                       t.Fatalf("unmarshal want rawTxSigArgument error:%v", err)
+                               }
+
+                               if !testutil.DeepEqual(rawTxSig, wantRawTxSig) {
+                                       t.Fatalf("rawTxSigArgument gotResult=%v, wantResult=%v", rawTxSig, wantRawTxSig)
+                               }
+
+                       case "data":
+                               data := &dataArgument{}
+                               if err := json.Unmarshal(arg.RawData, data); err != nil {
+                                       t.Fatalf("unmarshal dataArgument error:%v", err)
+                               }
+
+                               wantData := &dataArgument{}
+                               if err := json.Unmarshal(c.wantResult.Arguments[0].RawData, wantData); err != nil {
+                                       t.Fatalf("unmarshal want dataArgument error:%v", err)
+                               }
+
+                               if !testutil.DeepEqual(data, wantData) {
+                                       t.Fatalf("dataArgument gotResult=%v, wantResult=%v", data, wantData)
+                               }
+
+                       default:
+                               if arg.Type == "raw_tx_signature" || arg.Type == "data" {
+                                       t.Fatalf("argument type [%v] is not exist", arg.Type)
+                               }
+                       }
+               }
+       }
+}
diff --git a/cmd/bytomcli/commands/template.go b/cmd/bytomcli/commands/template.go
new file mode 100644 (file)
index 0000000..3af595f
--- /dev/null
@@ -0,0 +1,16 @@
+package commands
+
+// contract is LockWithPublicKey
+var buildLockWithPublicKeyReqFmt = `
+       {"actions": [
+               {"type": "spend_account_unspent_output", "output_id": "%s", "arguments": [{"type": "raw_tx_signature", "raw_data": {derivation_path": ["%s", "%s"], "xpub": "%s"}}]},
+               {"type": "control_program", "asset_id": "%s", "amount": %s, "control_program": "%s"},
+               {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": %s, "account_id": "%s"}
+       ]}`
+
+var buildLockWithPublicKeyReqFmtByAlias = `
+       {"actions": [
+               {"type": "spend_account_unspent_output", "output_id": "%s", "arguments": [{"type": "raw_tx_signature", "raw_data": {"derivation_path": ["%s", "%s"], "xpub": "%s"}}]},
+               {"type": "control_program", "asset_alias": "%s", "amount": %s, "control_program": "%s"},
+               {"type": "spend_account", "asset_alias": "BTM", "amount": %s, "account_alias": "%s"}
+       ]}`
index 1e672eb..9c306c8 100644 (file)
@@ -17,10 +17,15 @@ import (
 )
 
 func init() {
-       buildTransactionCmd.PersistentFlags().StringVarP(&buildType, "type", "t", "", "transaction type, valid types: 'issue', 'spend'")
-       buildTransactionCmd.PersistentFlags().StringVarP(&receiverProgram, "receiver", "r", "", "program of receiver")
-       buildTransactionCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "address of receiver")
+       buildTransactionCmd.PersistentFlags().StringVarP(&buildType, "type", "t", "", "transaction type, valid types: 'issue', 'spend', 'address', 'retire', 'program', 'unlock'")
+       buildTransactionCmd.PersistentFlags().StringVarP(&receiverProgram, "receiver", "r", "", "program of receiver when type is spend")
+       buildTransactionCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "address of receiver when type is address")
+       buildTransactionCmd.PersistentFlags().StringVarP(&program, "program", "p", "", "program of receiver when type is program")
+       buildTransactionCmd.PersistentFlags().StringVarP(&arbitrary, "arbitrary", "v", "", "additional arbitrary data when type is retire")
        buildTransactionCmd.PersistentFlags().StringVarP(&btmGas, "gas", "g", "20000000", "gas of this transaction")
+       buildTransactionCmd.PersistentFlags().StringVarP(&contractName, "contract-name", "c", "",
+               "name of template contract, currently supported: 'LockWithPublicKey', 'LockWithMultiSig', 'LockWithPublicKeyHash',"+
+                       "\n\t\t\t       'RevealPreimage', 'TradeOffer', 'Escrow', 'CallOption', 'LoanCollateral'")
        buildTransactionCmd.PersistentFlags().BoolVar(&pretty, "pretty", false, "pretty print json result")
        buildTransactionCmd.PersistentFlags().BoolVar(&alias, "alias", false, "use alias build transaction")
 
@@ -45,6 +50,9 @@ var (
        account         = ""
        detail          = false
        unconfirmed     = false
+       arbitrary       = ""
+       program         = ""
+       contractName    = ""
 )
 
 var buildIssueReqFmt = `
@@ -78,15 +86,15 @@ var buildSpendReqFmtByAlias = `
 var buildRetireReqFmt = `
        {"actions": [
                {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"},
-               {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"},
-               {"type": "retire", "asset_id": "%s","amount": %s,"account_id": "%s"}
+               {"type": "spend_account", "asset_id": "%s", "amount": %s, "account_id": "%s"},
+               {"type": "retire", "asset_id": "%s", "amount": %s, "arbitrary": "%s"}
        ]}`
 
 var buildRetireReqFmtByAlias = `
        {"actions": [
                {"type": "spend_account", "asset_alias": "BTM", "amount":%s, "account_alias": "%s"},
-               {"type": "spend_account", "asset_alias": "%s","amount": %s,"account_alias": "%s"},
-               {"type": "retire", "asset_alias": "%s","amount": %s,"account_alias": "%s"}
+               {"type": "spend_account", "asset_alias": "%s", "amount": %s, "account_alias": "%s"},
+               {"type": "retire", "asset_alias": "%s", "amount": %s, "arbitrary": "%s"}
        ]}`
 
 var buildControlAddressReqFmt = `
@@ -103,10 +111,24 @@ var buildControlAddressReqFmtByAlias = `
                {"type": "control_address", "asset_alias": "%s", "amount": %s,"address": "%s"}
        ]}`
 
+var buildControlProgramReqFmt = `
+       {"actions": [
+               {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"},
+               {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"},
+               {"type": "control_program", "asset_id": "%s", "amount": %s, "control_program": "%s"}
+       ]}`
+
+var buildControlProgramReqFmtByAlias = `
+       {"actions": [
+               {"type": "spend_account", "asset_alias": "btm", "amount":%s, "account_alias": "%s"},
+               {"type": "spend_account", "asset_alias": "%s","amount": %s,"account_alias": "%s"},
+               {"type": "control_program", "asset_alias": "%s", "amount": %s, "control_program": "%s"}
+       ]}`
+
 var buildTransactionCmd = &cobra.Command{
        Use:   "build-transaction <accountID|alias> <assetID|alias> <amount>",
        Short: "Build one transaction template,default use account id and asset id",
-       Args:  cobra.RangeArgs(3, 4),
+       Args:  cobra.RangeArgs(3, 20),
        PreRun: func(cmd *cobra.Command, args []string) {
                cmd.MarkFlagRequired("type")
                if buildType == "spend" {
@@ -133,16 +155,47 @@ var buildTransactionCmd = &cobra.Command{
                        buildReqStr = fmt.Sprintf(buildSpendReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, receiverProgram)
                case "retire":
                        if alias {
-                               buildReqStr = fmt.Sprintf(buildRetireReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
+                               buildReqStr = fmt.Sprintf(buildRetireReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, arbitrary)
                                break
                        }
-                       buildReqStr = fmt.Sprintf(buildRetireReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
+                       buildReqStr = fmt.Sprintf(buildRetireReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, arbitrary)
                case "address":
                        if alias {
                                buildReqStr = fmt.Sprintf(buildControlAddressReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address)
                                break
                        }
                        buildReqStr = fmt.Sprintf(buildControlAddressReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address)
+               case "program":
+                       if alias {
+                               buildReqStr = fmt.Sprintf(buildControlProgramReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, program)
+                               break
+                       }
+                       buildReqStr = fmt.Sprintf(buildControlProgramReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, program)
+               case "unlock":
+                       usage := "Usage:\n  bytomcli build-transaction <accountID|alias> <assetID|alias> <amount> -c <contractName> <outputID>"
+                       switch contractName {
+                       case "LockWithPublicKey":
+                               if len(args) != 7 {
+                                       fmt.Println("%s <rootPub> <path1> <path2> [flags]\n", usage)
+                                       os.Exit(util.ErrLocalExe)
+                               }
+
+                               outputID := args[3]
+                               rootPub := args[4]
+                               path1 := args[5]
+                               path2 := args[6]
+
+                               if alias {
+                                       buildReqStr = fmt.Sprintf(buildLockWithPublicKeyReqFmtByAlias, outputID, path1, path2, rootPub, assetInfo, amount, program, btmGas, accountInfo)
+                                       break
+                               }
+                               buildReqStr = fmt.Sprintf(buildLockWithPublicKeyReqFmt, outputID, path1, path2, rootPub, assetInfo, amount, program, btmGas, accountInfo)
+
+                       default:
+                               jww.ERROR.Println("Invalid Contract template")
+                               os.Exit(util.ErrLocalExe)
+                       }
+
                default:
                        jww.ERROR.Println("Invalid transaction template type")
                        os.Exit(util.ErrLocalExe)