From 3aec4988213a49fba026a673801659094b7c1280 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 23 Jul 2019 11:25:46 +0800 Subject: [PATCH] fix review --- toolbar/api_node/block.go | 33 +++++++ toolbar/api_node/node.go | 37 +++++++ toolbar/api_node/node_test.go | 70 ++++++++++++++ toolbar/api_node/transaction.go | 138 +++++++++++++++++++++++++++ toolbar/vote_reward/synchron/block_keeper.go | 2 +- 5 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 toolbar/api_node/block.go create mode 100644 toolbar/api_node/node.go create mode 100644 toolbar/api_node/node_test.go create mode 100644 toolbar/api_node/transaction.go diff --git a/toolbar/api_node/block.go b/toolbar/api_node/block.go new file mode 100644 index 00000000..a9789a28 --- /dev/null +++ b/toolbar/api_node/block.go @@ -0,0 +1,33 @@ +package apinode + +import ( + "encoding/json" + + "github.com/vapor/api" + "github.com/vapor/errors" + "github.com/vapor/protocol/bc/types" +) + +func (n *Node) GetBlockByHash(hash string) (*types.Block, error) { + return n.getRawBlock(&getRawBlockReq{BlockHash: hash}) +} + +func (n *Node) GetBlockByHeight(height uint64) (*types.Block, error) { + return n.getRawBlock(&getRawBlockReq{BlockHeight: height}) +} + +type getRawBlockReq struct { + BlockHeight uint64 `json:"block_height"` + BlockHash string `json:"block_hash"` +} + +func (n *Node) getRawBlock(req *getRawBlockReq) (*types.Block, error) { + url := "/get-raw-block" + payload, err := json.Marshal(req) + if err != nil { + return nil, errors.Wrap(err, "json marshal") + } + + resp := &api.GetRawBlockResp{} + return resp.RawBlock, n.request(url, payload, resp) +} diff --git a/toolbar/api_node/node.go b/toolbar/api_node/node.go new file mode 100644 index 00000000..a0567232 --- /dev/null +++ b/toolbar/api_node/node.go @@ -0,0 +1,37 @@ +package apinode + +import ( + "encoding/json" + + "github.com/vapor/errors" + "github.com/vapor/toolbar/common" +) + +// Node can invoke the api which provide by the full node server +type Node struct { + hostPort string +} + +// NewNode create a api client with target server +func NewNode(hostPort string) *Node { + return &Node{hostPort: hostPort} +} + +type response struct { + Status string `json:"status"` + Data json.RawMessage `json:"data"` + ErrDetail string `json:"error_detail"` +} + +func (n *Node) request(path string, payload []byte, respData interface{}) error { + resp := &response{} + if err := common.Post(n.hostPort+path, payload, resp); err != nil { + return err + } + + if resp.Status != "success" { + return errors.New(resp.ErrDetail) + } + + return json.Unmarshal(resp.Data, respData) +} diff --git a/toolbar/api_node/node_test.go b/toolbar/api_node/node_test.go new file mode 100644 index 00000000..681bf029 --- /dev/null +++ b/toolbar/api_node/node_test.go @@ -0,0 +1,70 @@ +package apinode + +import ( + "encoding/json" + "testing" + + "github.com/vapor/consensus" + "github.com/vapor/errors" + "github.com/vapor/protocol/bc" +) + +func buildTxRequest(accountID string, outputs map[string]uint64) ([]byte, error) { + totalBTM := uint64(10000000) + actions := []interface{}{} + for address, amount := range outputs { + actions = append(actions, &ControlAddressAction{ + Address: address, + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount}, + }) + totalBTM += amount + } + + actions = append(actions, &SpendAccountAction{ + AccountID: accountID, + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM}, + }) + payload, err := json.Marshal(&buildTxReq{Actions: actions}) + if err != nil { + return nil, errors.Wrap(err, "Marshal spend request") + } + + return payload, nil +} + +type args struct { + accountID string + outputs map[string]uint64 +} + +func TestBuildTxRequest(t *testing.T) { + cases := []struct { + args args + want string + }{ + { + args: args{ + accountID: "9bb77612-350e-4d53-81e2-525b28247ba5", + outputs: map[string]uint64{"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf": 100}, + }, + want: `{"actions":[{"type":"control_address","address":"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100},{"type":"spend_account","account_id":"9bb77612-350e-4d53-81e2-525b28247ba5","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":10000100}]}`, + }, + { + args: args{ + accountID: "9bb77612-350e-4d53-81e2-525b28247ba5", + outputs: map[string]uint64{"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf": 100, "sp1qcgtxkhfzytul4lfttwex3skfqhm0tg6ms9da28": 200}, + }, + want: `{"actions":[{"type":"control_address","address":"sp1qlryy65a5apylphqp6axvhx7nd6y2zlexuvn7gf","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":100},{"type":"control_address","address":"sp1qcgtxkhfzytul4lfttwex3skfqhm0tg6ms9da28","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":200},{"type":"spend_account","account_id":"9bb77612-350e-4d53-81e2-525b28247ba5","asset_id":"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","amount":10000300}]}`, + }, + } + + for i, c := range cases { + tx, err := buildTxRequest(c.args.accountID, c.args.outputs) + if err != nil { + t.Fatal(err) + } + if string(tx) != string(c.want) { + t.Fatal(i, string(tx)) + } + } +} diff --git a/toolbar/api_node/transaction.go b/toolbar/api_node/transaction.go new file mode 100644 index 00000000..82ff3c95 --- /dev/null +++ b/toolbar/api_node/transaction.go @@ -0,0 +1,138 @@ +package apinode + +import ( + "encoding/json" + + "github.com/vapor/blockchain/txbuilder" + "github.com/vapor/consensus" + "github.com/vapor/errors" + "github.com/vapor/protocol/bc" + "github.com/vapor/protocol/bc/types" +) + +type SpendAccountAction struct { + AccountID string `json:"account_id"` + *bc.AssetAmount +} + +func (s *SpendAccountAction) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Type string `json:"type"` + AccountID string `json:"account_id"` + *bc.AssetAmount + }{ + Type: "spend_account", + AccountID: s.AccountID, + AssetAmount: s.AssetAmount, + }) +} + +type ControlAddressAction struct { + Address string `json:"address"` + *bc.AssetAmount +} + +func (c *ControlAddressAction) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + Type string `json:"type"` + Address string `json:"address"` + *bc.AssetAmount + }{ + Type: "control_address", + Address: c.Address, + AssetAmount: c.AssetAmount, + }) +} + +func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64) error { + totalBTM := uint64(10000000) + actions := []interface{}{} + for address, amount := range outputs { + actions = append(actions, &ControlAddressAction{ + Address: address, + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount}, + }) + totalBTM += amount + } + + actions = append(actions, &SpendAccountAction{ + AccountID: accountID, + AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: totalBTM}, + }) + + tpl, err := n.buildTx(actions) + if err != nil { + return err + } + + tpl, err = n.signTx(tpl, password) + if err != nil { + return err + } + + _, err = n.SubmitTx(tpl.Transaction) + return err +} + +type buildTxReq struct { + Actions []interface{} `json:"actions"` +} + +func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) { + url := "/build-transaction" + 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"` +} + +type signTxResp struct { + Tx *txbuilder.Template `json:"transaction"` + SignComplete bool `json:"sign_complete"` +} + +func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Template, error) { + url := "/sign-transaction" + payload, err := json.Marshal(&signTxReq{Tx: tpl, Password: password}) + if err != nil { + return nil, errors.Wrap(err, "json marshal") + } + + resp := &signTxResp{} + if err := n.request(url, payload, resp); err != nil { + return nil, err + } + + if !resp.SignComplete { + return nil, errors.New("sign fail") + } + + return resp.Tx, nil +} + +type submitTxReq struct { + Tx *types.Tx `json:"raw_transaction"` +} + +type submitTxResp struct { + TxID string `json:"tx_id"` +} + +func (n *Node) SubmitTx(tx *types.Tx) (string, error) { + url := "/submit-transaction" + payload, err := json.Marshal(submitTxReq{Tx: tx}) + if err != nil { + return "", errors.Wrap(err, "json marshal") + } + + res := &submitTxResp{} + return res.TxID, n.request(url, payload, res) +} diff --git a/toolbar/vote_reward/synchron/block_keeper.go b/toolbar/vote_reward/synchron/block_keeper.go index 99034809..2b78a663 100644 --- a/toolbar/vote_reward/synchron/block_keeper.go +++ b/toolbar/vote_reward/synchron/block_keeper.go @@ -8,7 +8,7 @@ import ( "github.com/vapor/errors" "github.com/vapor/protocol/bc/types" - "github.com/vapor/toolbar/apinode" + apinode "github.com/vapor/toolbar/api_node" "github.com/vapor/toolbar/common" "github.com/vapor/toolbar/vote_reward/config" "github.com/vapor/toolbar/vote_reward/database/orm" -- 2.11.0