github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/api/api.go (about) 1 package api 2 3 import ( 4 "crypto/tls" 5 "net" 6 "net/http" 7 "sync" 8 "time" 9 10 "github.com/kr/secureheader" 11 log "github.com/sirupsen/logrus" 12 cmn "github.com/tendermint/tmlibs/common" 13 14 "github.com/bytom/bytom/accesstoken" 15 "github.com/bytom/bytom/blockchain/txfeed" 16 cfg "github.com/bytom/bytom/config" 17 "github.com/bytom/bytom/dashboard/dashboard" 18 "github.com/bytom/bytom/dashboard/equity" 19 "github.com/bytom/bytom/errors" 20 "github.com/bytom/bytom/event" 21 "github.com/bytom/bytom/mining/cpuminer" 22 "github.com/bytom/bytom/mining/miningpool" 23 "github.com/bytom/bytom/net/http/authn" 24 "github.com/bytom/bytom/net/http/gzip" 25 "github.com/bytom/bytom/net/http/httpjson" 26 "github.com/bytom/bytom/net/http/static" 27 "github.com/bytom/bytom/net/websocket" 28 "github.com/bytom/bytom/netsync" 29 "github.com/bytom/bytom/p2p" 30 "github.com/bytom/bytom/protocol" 31 "github.com/bytom/bytom/wallet" 32 ) 33 34 var ( 35 errNotAuthenticated = errors.New("not authenticated") 36 httpReadTimeout = 2 * time.Minute 37 httpWriteTimeout = time.Hour 38 ) 39 40 const ( 41 // SUCCESS indicates the rpc calling is successful. 42 SUCCESS = "success" 43 // FAIL indicated the rpc calling is failed. 44 FAIL = "fail" 45 logModule = "api" 46 ) 47 48 // Response describes the response standard. 49 type Response struct { 50 Status string `json:"status,omitempty"` 51 Code string `json:"code,omitempty"` 52 Msg string `json:"msg,omitempty"` 53 ErrorDetail string `json:"error_detail,omitempty"` 54 Data interface{} `json:"data,omitempty"` 55 } 56 57 //NewSuccessResponse success response 58 func NewSuccessResponse(data interface{}) Response { 59 return Response{Status: SUCCESS, Data: data} 60 } 61 62 //FormatErrResp format error response 63 func FormatErrResp(err error) (response Response) { 64 response = Response{Status: FAIL} 65 root := errors.Root(err) 66 // Some types cannot be used as map keys, for example slices. 67 // If an error's underlying type is one of these, don't panic. 68 // Just treat it like any other missing entry. 69 defer func() { 70 if err := recover(); err != nil { 71 response.ErrorDetail = "" 72 } 73 }() 74 75 if info, ok := respErrFormatter[root]; ok { 76 response.Code = info.ChainCode 77 response.Msg = info.Message 78 response.ErrorDetail = err.Error() 79 } else { 80 response.Code = respErrFormatter[ErrDefault].ChainCode 81 response.Msg = respErrFormatter[ErrDefault].Message 82 response.ErrorDetail = err.Error() 83 } 84 return response 85 } 86 87 //NewErrorResponse error response 88 func NewErrorResponse(err error) Response { 89 response := FormatErrResp(err) 90 return response 91 } 92 93 type waitHandler struct { 94 h http.Handler 95 wg sync.WaitGroup 96 } 97 98 func (wh *waitHandler) Set(h http.Handler) { 99 wh.h = h 100 wh.wg.Done() 101 } 102 103 func (wh *waitHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 104 wh.wg.Wait() 105 wh.h.ServeHTTP(w, req) 106 } 107 108 // API is the scheduling center for server 109 type API struct { 110 sync NetSync 111 wallet *wallet.Wallet 112 accessTokens *accesstoken.CredentialStore 113 chain *protocol.Chain 114 server *http.Server 115 handler http.Handler 116 txFeedTracker *txfeed.Tracker 117 cpuMiner *cpuminer.CPUMiner 118 miningPool *miningpool.MiningPool 119 notificationMgr *websocket.WSNotificationManager 120 eventDispatcher *event.Dispatcher 121 } 122 123 func (a *API) initServer(config *cfg.Config) { 124 // The waitHandler accepts incoming requests, but blocks until its underlying 125 // handler is set, when the second phase is complete. 126 var coreHandler waitHandler 127 var handler http.Handler 128 129 coreHandler.wg.Add(1) 130 mux := http.NewServeMux() 131 mux.Handle("/", &coreHandler) 132 133 handler = AuthHandler(mux, a.accessTokens, config.Auth.Disable) 134 handler = RedirectHandler(handler) 135 136 secureheader.DefaultConfig.PermitClearLoopback = true 137 secureheader.DefaultConfig.HTTPSRedirect = false 138 secureheader.DefaultConfig.Next = handler 139 140 a.server = &http.Server{ 141 // Note: we should not set TLSConfig here; 142 // we took care of TLS with the listener in maybeUseTLS. 143 Handler: secureheader.DefaultConfig, 144 ReadTimeout: httpReadTimeout, 145 WriteTimeout: httpWriteTimeout, 146 // Disable HTTP/2 for now until the Go implementation is more stable. 147 // https://github.com/golang/go/issues/16450 148 // https://github.com/golang/go/issues/17071 149 TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, 150 } 151 152 coreHandler.Set(a) 153 } 154 155 // StartServer start the server 156 func (a *API) StartServer(address string) { 157 log.WithFields(log.Fields{"module": logModule, "api address:": address}).Info("Rpc listen") 158 listener, err := net.Listen("tcp", address) 159 if err != nil { 160 cmn.Exit(cmn.Fmt("Failed to register tcp port: %v", err)) 161 } 162 163 // The `Serve` call has to happen in its own goroutine because 164 // it's blocking and we need to proceed to the rest of the core setup after 165 // we call it. 166 go func() { 167 if err := a.server.Serve(listener); err != nil { 168 log.WithFields(log.Fields{"module": logModule, "error": errors.Wrap(err, "Serve")}).Error("Rpc server") 169 } 170 }() 171 } 172 173 type NetSync interface { 174 IsListening() bool 175 IsCaughtUp() bool 176 PeerCount() int 177 GetNetwork() string 178 BestPeer() *netsync.PeerInfo 179 DialPeerWithAddress(addr *p2p.NetAddress) error 180 GetPeerInfos() []*netsync.PeerInfo 181 StopPeer(peerID string) error 182 } 183 184 // NewAPI create and initialize the API 185 func NewAPI(sync NetSync, wallet *wallet.Wallet, txfeeds *txfeed.Tracker, cpuMiner *cpuminer.CPUMiner, miningPool *miningpool.MiningPool, chain *protocol.Chain, config *cfg.Config, token *accesstoken.CredentialStore, dispatcher *event.Dispatcher, notificationMgr *websocket.WSNotificationManager) *API { 186 api := &API{ 187 sync: sync, 188 wallet: wallet, 189 chain: chain, 190 accessTokens: token, 191 txFeedTracker: txfeeds, 192 cpuMiner: cpuMiner, 193 miningPool: miningPool, 194 195 eventDispatcher: dispatcher, 196 notificationMgr: notificationMgr, 197 } 198 api.buildHandler() 199 api.initServer(config) 200 201 return api 202 } 203 204 func (a *API) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 205 a.handler.ServeHTTP(rw, req) 206 } 207 208 // buildHandler is in charge of all the rpc handling. 209 func (a *API) buildHandler() { 210 walletEnable := false 211 m := http.NewServeMux() 212 if a.wallet != nil { 213 walletEnable = true 214 m.Handle("/create-account", jsonHandler(a.createAccount)) 215 m.Handle("/update-account-alias", jsonHandler(a.updateAccountAlias)) 216 m.Handle("/list-accounts", jsonHandler(a.listAccounts)) 217 m.Handle("/delete-account", jsonHandler(a.deleteAccount)) 218 219 m.Handle("/create-account-receiver", jsonHandler(a.createAccountReceiver)) 220 m.Handle("/list-addresses", jsonHandler(a.listAddresses)) 221 m.Handle("/validate-address", jsonHandler(a.validateAddress)) 222 m.Handle("/list-pubkeys", jsonHandler(a.listPubKeys)) 223 224 m.Handle("/get-mining-address", jsonHandler(a.getMiningAddress)) 225 m.Handle("/set-mining-address", jsonHandler(a.setMiningAddress)) 226 227 m.Handle("/get-coinbase-arbitrary", jsonHandler(a.getCoinbaseArbitrary)) 228 m.Handle("/set-coinbase-arbitrary", jsonHandler(a.setCoinbaseArbitrary)) 229 230 m.Handle("/create-asset", jsonHandler(a.createAsset)) 231 m.Handle("/update-asset-alias", jsonHandler(a.updateAssetAlias)) 232 m.Handle("/get-asset", jsonHandler(a.getAsset)) 233 m.Handle("/list-assets", jsonHandler(a.listAssets)) 234 235 m.Handle("/create-key", jsonHandler(a.pseudohsmCreateKey)) 236 m.Handle("/update-key-alias", jsonHandler(a.pseudohsmUpdateKeyAlias)) 237 m.Handle("/list-keys", jsonHandler(a.pseudohsmListKeys)) 238 m.Handle("/delete-key", jsonHandler(a.pseudohsmDeleteKey)) 239 m.Handle("/reset-key-password", jsonHandler(a.pseudohsmResetPassword)) 240 m.Handle("/check-key-password", jsonHandler(a.pseudohsmCheckPassword)) 241 m.Handle("/sign-message", jsonHandler(a.signMessage)) 242 243 m.Handle("/build-transaction", jsonHandler(a.build)) 244 m.Handle("/build-chain-transactions", jsonHandler(a.buildChainTxs)) 245 m.Handle("/sign-transaction", jsonHandler(a.signTemplate)) 246 m.Handle("/sign-transactions", jsonHandler(a.signTemplates)) 247 248 m.Handle("/get-transaction", jsonHandler(a.getTransaction)) 249 m.Handle("/list-transactions", jsonHandler(a.listTransactions)) 250 251 m.Handle("/list-balances", jsonHandler(a.listBalances)) 252 m.Handle("/list-unspent-outputs", jsonHandler(a.listUnspentOutputs)) 253 254 m.Handle("/decode-program", jsonHandler(a.decodeProgram)) 255 256 m.Handle("/backup-wallet", jsonHandler(a.backupWalletImage)) 257 m.Handle("/restore-wallet", jsonHandler(a.restoreWalletImage)) 258 m.Handle("/rescan-wallet", jsonHandler(a.rescanWallet)) 259 m.Handle("/wallet-info", jsonHandler(a.getWalletInfo)) 260 m.Handle("/recovery-wallet", jsonHandler(a.recoveryFromRootXPubs)) 261 } else { 262 log.Warn("Please enable wallet") 263 } 264 265 m.Handle("/", alwaysError(errors.New("not Found"))) 266 m.Handle("/error", jsonHandler(a.walletError)) 267 268 m.Handle("/create-access-token", jsonHandler(a.createAccessToken)) 269 m.Handle("/list-access-tokens", jsonHandler(a.listAccessTokens)) 270 m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken)) 271 m.Handle("/check-access-token", jsonHandler(a.checkAccessToken)) 272 273 m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed)) 274 m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed)) 275 m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed)) 276 m.Handle("/delete-transaction-feed", jsonHandler(a.deleteTxFeed)) 277 m.Handle("/list-transaction-feeds", jsonHandler(a.listTxFeeds)) 278 279 m.Handle("/submit-transaction", jsonHandler(a.submit)) 280 m.Handle("/submit-transactions", jsonHandler(a.submitTxs)) 281 m.Handle("/estimate-transaction-gas", jsonHandler(a.estimateTxGas)) 282 m.Handle("/estimate-chain-transaction-gas", jsonHandler(a.estimateChainTxGas)) 283 284 m.Handle("/get-unconfirmed-transaction", jsonHandler(a.getUnconfirmedTx)) 285 m.Handle("/list-unconfirmed-transactions", jsonHandler(a.listUnconfirmedTxs)) 286 m.Handle("/decode-raw-transaction", jsonHandler(a.decodeRawTransaction)) 287 288 m.Handle("/get-block", jsonHandler(a.getBlock)) 289 m.Handle("/get-raw-block", jsonHandler(a.getRawBlock)) 290 m.Handle("/get-block-hash", jsonHandler(a.getBestBlockHash)) 291 m.Handle("/get-block-header", jsonHandler(a.getBlockHeader)) 292 m.Handle("/get-block-count", jsonHandler(a.getBlockCount)) 293 m.Handle("/get-difficulty", jsonHandler(a.getDifficulty)) 294 m.Handle("/get-hash-rate", jsonHandler(a.getHashRate)) 295 296 m.Handle("/is-mining", jsonHandler(a.isMining)) 297 m.Handle("/set-mining", jsonHandler(a.setMining)) 298 299 m.Handle("/get-work", jsonHandler(a.getWork)) 300 m.Handle("/get-work-json", jsonHandler(a.getWorkJSON)) 301 m.Handle("/submit-block", jsonHandler(a.submitBlock)) 302 m.Handle("/submit-work", jsonHandler(a.submitWork)) 303 m.Handle("/submit-work-json", jsonHandler(a.submitWorkJSON)) 304 305 m.Handle("/verify-message", jsonHandler(a.verifyMessage)) 306 m.Handle("/compile", jsonHandler(a.compileEquity)) 307 308 m.Handle("/gas-rate", jsonHandler(a.gasRate)) 309 m.Handle("/net-info", jsonHandler(a.getNetInfo)) 310 311 m.Handle("/list-peers", jsonHandler(a.listPeers)) 312 m.Handle("/disconnect-peer", jsonHandler(a.disconnectPeer)) 313 m.Handle("/connect-peer", jsonHandler(a.connectPeer)) 314 315 m.Handle("/get-merkle-proof", jsonHandler(a.getMerkleProof)) 316 317 m.HandleFunc("/websocket-subscribe", a.websocketHandler) 318 319 handler := walletHandler(m, walletEnable) 320 handler = webAssetsHandler(handler) 321 handler = gzip.Handler{Handler: handler} 322 a.handler = handler 323 } 324 325 // json Handler 326 func jsonHandler(f interface{}) http.Handler { 327 h, err := httpjson.Handler(f, errorFormatter.Write) 328 if err != nil { 329 panic(err) 330 } 331 return h 332 } 333 334 // error Handler 335 func alwaysError(err error) http.Handler { 336 return jsonHandler(func() error { return err }) 337 } 338 339 func webAssetsHandler(next http.Handler) http.Handler { 340 mux := http.NewServeMux() 341 mux.Handle("/dashboard/", http.StripPrefix("/dashboard/", static.Handler{ 342 Assets: dashboard.Files, 343 Default: "index.html", 344 })) 345 mux.Handle("/equity/", http.StripPrefix("/equity/", static.Handler{ 346 Assets: equity.Files, 347 Default: "index.html", 348 })) 349 mux.Handle("/", next) 350 351 return mux 352 } 353 354 // AuthHandler access token auth Handler 355 func AuthHandler(handler http.Handler, accessTokens *accesstoken.CredentialStore, authDisable bool) http.Handler { 356 authenticator := authn.NewAPI(accessTokens, authDisable) 357 358 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 359 // TODO(tessr): check that this path exists; return early if this path isn't legit 360 req, err := authenticator.Authenticate(req) 361 if err != nil { 362 log.WithFields(log.Fields{"module": logModule, "error": errors.Wrap(err, "Serve")}).Error("Authenticate fail") 363 err = errors.WithDetail(errNotAuthenticated, err.Error()) 364 errorFormatter.Write(req.Context(), rw, err) 365 return 366 } 367 handler.ServeHTTP(rw, req) 368 }) 369 } 370 371 // RedirectHandler redirect to dashboard handler 372 func RedirectHandler(next http.Handler) http.Handler { 373 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 374 if req.URL.Path == "/" { 375 http.Redirect(w, req, "/dashboard/", http.StatusFound) 376 return 377 } 378 next.ServeHTTP(w, req) 379 }) 380 } 381 382 func walletHandler(m *http.ServeMux, walletEnable bool) http.Handler { 383 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 384 // when the wallet is not been opened and the url path is not been found, modify url path to error, 385 // and redirect handler to error 386 if _, pattern := m.Handler(req); pattern != req.URL.Path && !walletEnable { 387 req.URL.Path = "/error" 388 walletRedirectHandler(w, req) 389 return 390 } 391 392 m.ServeHTTP(w, req) 393 }) 394 } 395 396 // walletRedirectHandler redirect to error when the wallet is closed 397 func walletRedirectHandler(w http.ResponseWriter, req *http.Request) { 398 h := http.RedirectHandler(req.URL.String(), http.StatusMovedPermanently) 399 h.ServeHTTP(w, req) 400 }