--- /dev/null
+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)
+}
--- /dev/null
+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)
+}
--- /dev/null
+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))
+ }
+ }
+}
--- /dev/null
+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)
+}
"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"