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