From 78fda61ff30886f193b88105d211136cac588fd6 Mon Sep 17 00:00:00 2001 From: wz Date: Tue, 13 Aug 2019 15:25:31 +0800 Subject: [PATCH] Merger utxo (#383) * add merger utxo * add chain tx * delete code * Function implementation * modify param * modify readme * fix review and fix tet * fix test --- README.md | 5 ++++ account/builder.go | 2 +- cmd/utxomerge/README.md | 24 ++++++++++++++++ cmd/utxomerge/main.go | 45 ++++++++++++++++++++++++++++++ test/builder_test.go | 22 ++++----------- toolbar/apinode/transaction.go | 60 ++++++++++++++++++++++++++++++++++++++++ toolbar/mergeutxo/merger_utxo.go | 41 +++++++++++++++++++++++++++ 7 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 cmd/utxomerge/README.md create mode 100644 cmd/utxomerge/main.go create mode 100755 toolbar/mergeutxo/merger_utxo.go diff --git a/README.md b/README.md index 52870ed3..e8e838eb 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,11 @@ cast in consensus around, and choose how many rounds of consensus to allocate th [Tool usage details](./cmd/votereward/README.md) + +### Merger utxo +UTXO has been merged to solve the problem that too much UTXO input causes a failed send transaction to fail. +[details](./toolbar/merger_utxo/README.md) + ## License [AGPL v3](./LICENSE) diff --git a/account/builder.go b/account/builder.go index 143ad24f..ca533a04 100644 --- a/account/builder.go +++ b/account/builder.go @@ -18,7 +18,7 @@ import ( var ( //chainTxUtxoNum maximum utxo quantity in a tx - chainTxUtxoNum = 5 + chainTxUtxoNum = 20 //chainTxMergeGas chain tx gas chainTxMergeGas = uint64(10000000) ) diff --git a/cmd/utxomerge/README.md b/cmd/utxomerge/README.md new file mode 100644 index 00000000..1ee0a3de --- /dev/null +++ b/cmd/utxomerge/README.md @@ -0,0 +1,24 @@ +tool use + +params + +```shell +merge utxo. + +Usage: + utxomerge [flags] + +Flags: + --account_id string The accountID of utxo needs to be merged + --address string The received address after merging utxo + --amount uint Total amount of merged utxo + -h, --help help for utxomerge + --host_port string The url for the node. Default:http://127.0.0.1:9889 (default "http://127.0.0.1:9889") + --password string Password of the account +``` + +example: + +```shell +./utxomerge --host_port http://127.0.0.1:9889 --account_id 9e54300d-f81d-4c5f-bef3-4e771042d394 --password 123456 --address sp1q8u7xu3e389awrnct0x4flx0h3v7mrfnmpu858p --amount 200000000000 +``` \ No newline at end of file diff --git a/cmd/utxomerge/main.go b/cmd/utxomerge/main.go new file mode 100644 index 00000000..326595f3 --- /dev/null +++ b/cmd/utxomerge/main.go @@ -0,0 +1,45 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/tendermint/tmlibs/cli" + + "github.com/vapor/toolbar/mergeutxo" +) + +var RootCmd = &cobra.Command{ + Use: "utxomerge", + Short: "merge utxo.", + RunE: runReward, +} + +var ( + hostPort, accountID, password, address string + amount uint64 +) + +func init() { + RootCmd.Flags().StringVar(&hostPort, "host_port", "http://127.0.0.1:9889", "The url for the node. Default:http://127.0.0.1:9889") + RootCmd.Flags().StringVar(&accountID, "account_id", "", "The accountID of utxo needs to be merged") + RootCmd.Flags().StringVar(&password, "password", "", "Password of the account") + RootCmd.Flags().StringVar(&address, "address", "", "The received address after merging utxo") + RootCmd.Flags().Uint64Var(&amount, "amount", 0, "Total amount of merged utxo") +} + +func runReward(cmd *cobra.Command, args []string) error { + log.Info("This tool belongs to an open-source project, we can not guarantee this tool is bug-free. Please check the code before using, developers will not be responsible for any asset loss due to bug!") + txIDs, err := mergeutxo.MergeUTXO(hostPort, accountID, password, address, amount) + if err != nil { + log.Fatal(err) + } + + log.Info("Merge utxo successfully. txID: ", txIDs) + + return nil +} + +func main() { + cmd := cli.PrepareBaseCmd(RootCmd, "merge_utxo", "./") + cmd.Execute() +} diff --git a/test/builder_test.go b/test/builder_test.go index 4328dbc2..839a4a98 100644 --- a/test/builder_test.go +++ b/test/builder_test.go @@ -55,26 +55,16 @@ func TestBuildBtmTxChain(t *testing.T) { wantUtxo: 10 * chainTxMergeGas, }, { - inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24}, + inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24, 11, 16, 33, 59, 73, 89, 66}, wantInput: [][]uint64{ - []uint64{22, 123, 53, 234, 23}, - []uint64{4, 2423, 24, 23, 43}, - []uint64{34, 234, 234, 24, 454}, - []uint64{2516, 979}, - []uint64{234, 24, 197}, - []uint64{260, 2469, 310}, - []uint64{454, 3038}, + []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24, 11, 16, 33, 59, 73, 89}, + []uint64{66, 3778}, }, wantOutput: [][]uint64{ - []uint64{454}, - []uint64{2516}, - []uint64{979}, - []uint64{3494}, - []uint64{454}, - []uint64{3038}, - []uint64{3491}, + []uint64{3778}, + []uint64{3843}, }, - wantUtxo: 3494 * chainTxMergeGas, + wantUtxo: 3843 * chainTxMergeGas, }, } diff --git a/toolbar/apinode/transaction.go b/toolbar/apinode/transaction.go index 682b6ed5..4e9c2422 100644 --- a/toolbar/apinode/transaction.go +++ b/toolbar/apinode/transaction.go @@ -113,6 +113,18 @@ func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) { return result, n.request(url, payload, result) } +func (n *Node) BuildChainTxs(actions []interface{}) ([]*txbuilder.Template, error) { + url := "/build-chain-transactions" + + payload, err := json.Marshal(&buildTxReq{Actions: actions}) + if err != nil { + return nil, errors.Wrap(err, "Marshal spend request") + } + + result := []*txbuilder.Template{} + return result, n.request(url, payload, &result) +} + type signTxReq struct { Tx *txbuilder.Template `json:"transaction"` Password string `json:"password"` @@ -142,6 +154,35 @@ func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Temp return resp.Tx, nil } +type signTxsReq struct { + Txs []*txbuilder.Template `json:"transactions"` + Password string `json:"password"` +} + +type signTxsResp struct { + Txs []*txbuilder.Template `json:"transaction"` + SignComplete bool `json:"sign_complete"` +} + +func (n *Node) SignTxs(tpls []*txbuilder.Template, password string) ([]*txbuilder.Template, error) { + url := "/sign-transactions" + payload, err := json.Marshal(&signTxsReq{Txs: tpls, Password: password}) + if err != nil { + return nil, errors.Wrap(err, "json marshal") + } + + resp := &signTxsResp{} + if err := n.request(url, payload, resp); err != nil { + return nil, err + } + + if !resp.SignComplete { + return nil, errors.New("sign fail") + } + + return resp.Txs, nil +} + type submitTxReq struct { Tx *types.Tx `json:"raw_transaction"` } @@ -160,3 +201,22 @@ func (n *Node) SubmitTx(tx *types.Tx) (string, error) { res := &submitTxResp{} return res.TxID, n.request(url, payload, res) } + +type submitTxsReq struct { + Txs []*types.Tx `json:"raw_transactions"` +} + +type submitTxsResp struct { + TxsID []string `json:"tx_id"` +} + +func (n *Node) SubmitTxs(txs []*types.Tx) ([]string, error) { + url := "/submit-transactions" + payload, err := json.Marshal(submitTxsReq{Txs: txs}) + if err != nil { + return []string{}, errors.Wrap(err, "json marshal") + } + + res := &submitTxsResp{} + return res.TxsID, n.request(url, payload, res) +} diff --git a/toolbar/mergeutxo/merger_utxo.go b/toolbar/mergeutxo/merger_utxo.go new file mode 100755 index 00000000..d8ca307a --- /dev/null +++ b/toolbar/mergeutxo/merger_utxo.go @@ -0,0 +1,41 @@ +package mergeutxo + +import ( + "github.com/vapor/consensus" + "github.com/vapor/protocol/bc" + "github.com/vapor/protocol/bc/types" + "github.com/vapor/toolbar/apinode" +) + +func MergeUTXO(hostPort, accountID, password, address string, amount uint64) ([]string, error) { + actions := []interface{}{} + + actions = append(actions, &apinode.ControlAddressAction{ + Address: address, + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount}, + }) + + actions = append(actions, &apinode.SpendAccountAction{ + AccountID: accountID, + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount}, + }) + + node := apinode.NewNode(hostPort) + + tpls, err := node.BuildChainTxs(actions) + if err != nil { + return []string{}, err + } + + tpls, err = node.SignTxs(tpls, password) + if err != nil { + return []string{}, err + } + + txs := []*types.Tx{} + for _, tpl := range tpls { + txs = append(txs, tpl.Transaction) + } + + return node.SubmitTxs(txs) +} -- 2.11.0