import (
"context"
+ "encoding/hex"
"encoding/json"
log "github.com/sirupsen/logrus"
"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"
}
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 {
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)
}
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) {
}
}
}
+
+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)
+ }
+ }
+ }
+ }
+}
--- /dev/null
+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"}
+ ]}`
)
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")
account = ""
detail = false
unconfirmed = false
+ arbitrary = ""
+ program = ""
+ contractName = ""
)
var buildIssueReqFmt = `
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 = `
{"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" {
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)