OSDN Git Service

Revert "remove rpc package (#474)" (#475)
authorYongfeng LI <wliyongfeng@gmail.com>
Sat, 24 Mar 2018 03:05:12 +0000 (11:05 +0800)
committerPaladz <yzhu101@uottawa.ca>
Sat, 24 Mar 2018 03:05:12 +0000 (11:05 +0800)
This reverts commit c0563a5d0953c00d86885c4d11d0ca7fdb19c090.

blockchain/errors.go
blockchain/rpc/net.go [new file with mode: 0644]
blockchain/rpc/rpc.go [new file with mode: 0644]
blockchain/rpc/types/responses.go [new file with mode: 0644]
blockchain/rpc/types/responses_test.go [new file with mode: 0644]

index b63d2bb..1a0909c 100644 (file)
@@ -6,6 +6,7 @@ import (
        "github.com/bytom/account"
        "github.com/bytom/blockchain/query"
        "github.com/bytom/blockchain/query/filter"
+       "github.com/bytom/blockchain/rpc"
        "github.com/bytom/blockchain/signers"
        "github.com/bytom/blockchain/txbuilder"
        "github.com/bytom/errors"
@@ -47,6 +48,7 @@ var errorFormatter = httperror.Formatter{
                context.DeadlineExceeded:     {408, "BTM001", "Request timed out"},
                httpjson.ErrBadRequest:       {400, "BTM003", "Invalid request body"},
                txbuilder.ErrMissingFields:   {400, "BTM010", "One or more fields are missing"},
+               rpc.ErrWrongNetwork:          {502, "BTM104", "A peer core is operating on a different blockchain network"},
                protocol.ErrTheDistantFuture: {400, "BTM105", "Requested height is too far ahead"},
 
                // Signers error namespace (2xx)
diff --git a/blockchain/rpc/net.go b/blockchain/rpc/net.go
new file mode 100644 (file)
index 0000000..309c50b
--- /dev/null
@@ -0,0 +1,28 @@
+package rpc
+
+import (
+       ctypes "github.com/bytom/blockchain/rpc/types"
+       "github.com/bytom/p2p"
+)
+
+// NetInfo return p2p net status
+func NetInfo(p2pSwitch *p2p.Switch) (*ctypes.ResultNetInfo, error) {
+       listening := p2pSwitch.IsListening()
+       listeners := []string{}
+       for _, listener := range p2pSwitch.Listeners() {
+               listeners = append(listeners, listener.String())
+       }
+       peers := []ctypes.Peer{}
+       for _, peer := range p2pSwitch.Peers().List() {
+               peers = append(peers, ctypes.Peer{
+                       NodeInfo:         *peer.NodeInfo,
+                       IsOutbound:       peer.IsOutbound(),
+                       ConnectionStatus: peer.Connection().Status(),
+               })
+       }
+       return &ctypes.ResultNetInfo{
+               Listening: listening,
+               Listeners: listeners,
+               Peers:     peers,
+       }, nil
+}
diff --git a/blockchain/rpc/rpc.go b/blockchain/rpc/rpc.go
new file mode 100644 (file)
index 0000000..1465a20
--- /dev/null
@@ -0,0 +1,166 @@
+package rpc
+
+import (
+       "bytes"
+       "context"
+       "encoding/json"
+       "fmt"
+       "io"
+       "net/http"
+       "net/url"
+       "strings"
+       "time"
+
+       "github.com/bytom/errors"
+       "github.com/bytom/net/http/httperror"
+       "github.com/bytom/net/http/reqid"
+)
+
+// Bytom-specific header fields
+const (
+       HeaderBlockchainID = "Blockchain-ID"
+       HeaderCoreID       = "Bytom-Core-ID"
+       HeaderTimeout      = "RPC-Timeout"
+)
+
+// ErrWrongNetwork is returned when a peer's blockchain ID differs from
+// the RPC client's blockchain ID.
+var ErrWrongNetwork = errors.New("connected to a peer on a different network")
+
+// A Client is a Bytom RPC client. It performs RPCs over HTTP using JSON
+// request and responses. A Client must be configured with a secret token
+// to authenticate with other Cores on the network.
+type Client struct {
+       BaseURL      string
+       AccessToken  string
+       Username     string
+       BuildTag     string
+       BlockchainID string
+       CoreID       string
+
+       // If set, Client is used for outgoing requests.
+       // TODO(kr): make this required (crash on nil)
+       Client *http.Client
+}
+
+func (c Client) userAgent() string {
+       return fmt.Sprintf("Bytom; process=%s; buildtag=%s; blockchainID=%s",
+               c.Username, c.BuildTag, c.BlockchainID)
+}
+
+// ErrStatusCode is an error returned when an rpc fails with a non-200
+// response code.
+type ErrStatusCode struct {
+       URL        string
+       StatusCode int
+       ErrorData  *httperror.Response
+}
+
+func (e ErrStatusCode) Error() string {
+       return fmt.Sprintf("Request to `%s` responded with %d %s",
+               e.URL, e.StatusCode, http.StatusText(e.StatusCode))
+}
+
+// Call calls a remote procedure on another node, specified by the path.
+func (c *Client) Call(ctx context.Context, path string, request, response interface{}) error {
+       r, err := c.CallRaw(ctx, path, request)
+       if err != nil {
+               return err
+       }
+       defer r.Close()
+       if response != nil {
+               err = errors.Wrap(json.NewDecoder(r).Decode(response))
+       }
+       return err
+}
+
+// CallRaw calls a remote procedure on another node, specified by the path. It
+// returns a io.ReadCloser of the raw response body.
+func (c *Client) CallRaw(ctx context.Context, path string, request interface{}) (io.ReadCloser, error) {
+       u, err := url.Parse(c.BaseURL)
+       if err != nil {
+               return nil, errors.Wrap(err)
+       }
+       u.Path = path
+
+       var bodyReader io.Reader
+       if request != nil {
+               var jsonBody bytes.Buffer
+               if err := json.NewEncoder(&jsonBody).Encode(request); err != nil {
+                       return nil, errors.Wrap(err)
+               }
+               bodyReader = &jsonBody
+       }
+
+       req, err := http.NewRequest("POST", u.String(), bodyReader)
+       if err != nil {
+               return nil, errors.Wrap(err)
+       }
+
+       if c.AccessToken != "" {
+               var username, password string
+               toks := strings.SplitN(c.AccessToken, ":", 2)
+               if len(toks) > 0 {
+                       username = toks[0]
+               }
+               if len(toks) > 1 {
+                       password = toks[1]
+               }
+               req.SetBasicAuth(username, password)
+       }
+
+       // Propagate our request ID so that we can trace a request across nodes.
+       req.Header.Add("Request-ID", reqid.FromContext(ctx))
+       req.Header.Set("Content-Type", "application/json")
+       req.Header.Set("User-Agent", c.userAgent())
+       req.Header.Set(HeaderBlockchainID, c.BlockchainID)
+       req.Header.Set(HeaderCoreID, c.CoreID)
+
+       // Propagate our deadline if we have one.
+       deadline, ok := ctx.Deadline()
+       if ok {
+               req.Header.Set(HeaderTimeout, deadline.Sub(time.Now()).String())
+       }
+
+       client := c.Client
+       if client == nil {
+               client = http.DefaultClient
+       }
+       resp, err := client.Do(req.WithContext(ctx))
+       if err != nil && ctx.Err() != nil { // check if it timed out
+               return nil, errors.Wrap(ctx.Err())
+       } else if err != nil {
+               return nil, errors.Wrap(err)
+       }
+
+       if id := resp.Header.Get(HeaderBlockchainID); c.BlockchainID != "" && id != "" && c.BlockchainID != id {
+               resp.Body.Close()
+               return nil, errors.Wrap(ErrWrongNetwork)
+       }
+
+       if resp.StatusCode < 200 || resp.StatusCode >= 300 {
+               defer resp.Body.Close()
+
+               resErr := ErrStatusCode{
+                       URL:        cleanedURLString(u),
+                       StatusCode: resp.StatusCode,
+               }
+
+               // Attach formatted error message, if available
+               var errData httperror.Response
+               err := json.NewDecoder(resp.Body).Decode(&errData)
+               if err == nil && errData.ChainCode != "" {
+                       resErr.ErrorData = &errData
+               }
+
+               return nil, resErr
+       }
+
+       return resp.Body, nil
+}
+
+func cleanedURLString(u *url.URL) string {
+       var dup url.URL = *u
+       dup.User = nil
+       return dup.String()
+}
diff --git a/blockchain/rpc/types/responses.go b/blockchain/rpc/types/responses.go
new file mode 100644 (file)
index 0000000..b991693
--- /dev/null
@@ -0,0 +1,73 @@
+package core_types
+
+import (
+       "strings"
+       "time"
+
+       "github.com/bytom/p2p"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/types"
+       "github.com/tendermint/go-crypto"
+       "github.com/tendermint/go-wire/data"
+)
+
+type BlockNonce [8]byte
+
+type ResultBlockchainInfo struct {
+       LastHeight uint64 `json:"last_height"`
+}
+
+type ResultGenesis struct {
+       Genesis *types.GenesisDoc `json:"genesis"`
+}
+
+type ResultBlock struct {
+}
+
+type ResultStatus struct {
+       NodeInfo          *p2p.NodeInfo `json:"node_info"`
+       PubKey            crypto.PubKey `json:"pub_key"`
+       LatestBlockHash   data.Bytes    `json:"latest_block_hash"`
+       LatestAppHash     data.Bytes    `json:"latest_app_hash"`
+       LatestBlockHeight int           `json:"latest_block_height"`
+       LatestBlockTime   int64         `json:"latest_block_time"` // nano
+}
+
+func (s *ResultStatus) TxIndexEnabled() bool {
+       if s == nil || s.NodeInfo == nil {
+               return false
+       }
+       for _, s := range s.NodeInfo.Other {
+               info := strings.Split(s, "=")
+               if len(info) == 2 && info[0] == "tx_index" {
+                       return info[1] == "on"
+               }
+       }
+       return false
+}
+
+type ResultNetInfo struct {
+       Listening bool     `json:"listening"`
+       Listeners []string `json:"listeners"`
+       Peers     []Peer   `json:"peers"`
+}
+
+type ResultBlockHeaderInfo struct {
+       Version int32 `json:"version"`
+       //Height uint64    `json:"height"`
+       MerkleRoot        bc.Hash   `json:"merkleroot"`
+       PreviousBlockHash bc.Hash   `json:"prevblockhash"`
+       TimestampMS       time.Time `json:"timestamp"`
+       Bits              uint64    `json:"bits"`
+       Nonce             uint64    `json:"nonce"`
+}
+
+type ResultDialSeeds struct {
+       Log string `json:"log"`
+}
+
+type Peer struct {
+       p2p.NodeInfo     `json:"node_info"`
+       IsOutbound       bool                 `json:"is_outbound"`
+       ConnectionStatus p2p.ConnectionStatus `json:"connection_status"`
+}
diff --git a/blockchain/rpc/types/responses_test.go b/blockchain/rpc/types/responses_test.go
new file mode 100644 (file)
index 0000000..9f82bbd
--- /dev/null
@@ -0,0 +1,38 @@
+package core_types
+
+import (
+       "testing"
+
+       "github.com/bytom/p2p"
+       "github.com/stretchr/testify/assert"
+)
+
+func TestStatusIndexer(t *testing.T) {
+       assert := assert.New(t)
+
+       var status *ResultStatus
+       assert.False(status.TxIndexEnabled())
+
+       status = &ResultStatus{}
+       assert.False(status.TxIndexEnabled())
+
+       status.NodeInfo = &p2p.NodeInfo{}
+       assert.False(status.TxIndexEnabled())
+
+       cases := []struct {
+               expected bool
+               other    []string
+       }{
+               {false, nil},
+               {false, []string{}},
+               {false, []string{"a=b"}},
+               {false, []string{"tx_indexiskv", "some=dood"}},
+               {true, []string{"tx_index=on", "tx_index=other"}},
+               {true, []string{"^(*^(", "tx_index=on", "a=n=b=d="}},
+       }
+
+       for _, tc := range cases {
+               status.NodeInfo.Other = tc.other
+               assert.Equal(tc.expected, status.TxIndexEnabled())
+       }
+}