github.com/Tri-stone/burrow@v0.25.0/rpc/service.go (about) 1 // Copyright 2017 Monax Industries Limited 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package rpc 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "math/big" 21 "time" 22 23 "github.com/hyperledger/burrow/acm" 24 "github.com/hyperledger/burrow/acm/acmstate" 25 "github.com/hyperledger/burrow/acm/validator" 26 "github.com/hyperledger/burrow/bcm" 27 "github.com/hyperledger/burrow/binary" 28 "github.com/hyperledger/burrow/consensus/tendermint" 29 "github.com/hyperledger/burrow/crypto" 30 "github.com/hyperledger/burrow/execution/names" 31 "github.com/hyperledger/burrow/logging" 32 "github.com/hyperledger/burrow/logging/structure" 33 "github.com/hyperledger/burrow/permission" 34 "github.com/hyperledger/burrow/project" 35 "github.com/hyperledger/burrow/txs" 36 "github.com/tendermint/tendermint/consensus" 37 "github.com/tendermint/tendermint/p2p" 38 core_types "github.com/tendermint/tendermint/rpc/core/types" 39 tmTypes "github.com/tendermint/tendermint/types" 40 ) 41 42 // Magic! Should probably be configurable, but not shouldn't be so huge we 43 // end up DoSing ourselves. 44 const MaxBlockLookback = 1000 45 46 // Base service that provides implementation for all underlying RPC methods 47 type Service struct { 48 state acmstate.IterableStatsReader 49 nameReg names.IterableReader 50 blockchain bcm.BlockchainInfo 51 validators validator.History 52 nodeView *tendermint.NodeView 53 logger *logging.Logger 54 } 55 56 // Service provides an internal query and information service with serialisable return types on which can accomodate 57 // a number of transport front ends 58 func NewService(state acmstate.IterableStatsReader, nameReg names.IterableReader, blockchain bcm.BlockchainInfo, 59 validators validator.History, nodeView *tendermint.NodeView, logger *logging.Logger) *Service { 60 61 return &Service{ 62 state: state, 63 nameReg: nameReg, 64 blockchain: blockchain, 65 validators: validators, 66 nodeView: nodeView, 67 logger: logger.With(structure.ComponentKey, "Service"), 68 } 69 } 70 71 func (s *Service) Stats() acmstate.AccountStatsGetter { 72 return s.state 73 } 74 75 func (s *Service) BlockchainInfo() bcm.BlockchainInfo { 76 return s.blockchain 77 } 78 79 func (s *Service) ChainID() string { 80 return s.blockchain.ChainID() 81 } 82 83 func (s *Service) UnconfirmedTxs(maxTxs int64) (*ResultUnconfirmedTxs, error) { 84 if s.nodeView == nil { 85 return nil, fmt.Errorf("cannot list unconfirmed transactions because NodeView not mounted") 86 } 87 // Get all transactions for now 88 transactions, err := s.nodeView.MempoolTransactions(int(maxTxs)) 89 if err != nil { 90 return nil, err 91 } 92 wrappedTxs := make([]*txs.Envelope, len(transactions)) 93 for i, tx := range transactions { 94 wrappedTxs[i] = tx 95 } 96 return &ResultUnconfirmedTxs{ 97 NumTxs: len(transactions), 98 Txs: wrappedTxs, 99 }, nil 100 } 101 102 func (s *Service) Status() (*ResultStatus, error) { 103 return Status(s.BlockchainInfo(), s.validators, s.nodeView, "", "") 104 } 105 106 func (s *Service) StatusWithin(blockTimeWithin, blockSeenTimeWithin string) (*ResultStatus, error) { 107 return Status(s.BlockchainInfo(), s.validators, s.nodeView, blockTimeWithin, blockSeenTimeWithin) 108 } 109 110 func (s *Service) ChainIdentifiers() (*ResultChainId, error) { 111 return &ResultChainId{ 112 ChainName: s.blockchain.GenesisDoc().ChainName, 113 ChainId: s.blockchain.ChainID(), 114 GenesisHash: s.blockchain.GenesisHash(), 115 }, nil 116 } 117 118 func (s *Service) Peers() []core_types.Peer { 119 if s.nodeView == nil { 120 return nil 121 } 122 p2pPeers := s.nodeView.Peers().List() 123 peers := make([]core_types.Peer, len(p2pPeers)) 124 for i, peer := range p2pPeers { 125 ni, _ := peer.NodeInfo().(p2p.DefaultNodeInfo) 126 peers[i] = core_types.Peer{ 127 NodeInfo: ni, 128 IsOutbound: peer.IsOutbound(), 129 ConnectionStatus: peer.Status(), 130 } 131 } 132 return peers 133 } 134 135 func (s *Service) Network() (*ResultNetwork, error) { 136 if s.nodeView == nil { 137 return nil, fmt.Errorf("cannot return network info because NodeView not mounted") 138 } 139 var listeners []string 140 peers := s.Peers() 141 return &ResultNetwork{ 142 ThisNode: s.nodeView.NodeInfo(), 143 ResultNetInfo: &core_types.ResultNetInfo{ 144 Listening: true, 145 Listeners: listeners, 146 NPeers: len(peers), 147 Peers: peers, 148 }, 149 }, nil 150 } 151 152 func (s *Service) Genesis() (*ResultGenesis, error) { 153 return &ResultGenesis{ 154 Genesis: s.blockchain.GenesisDoc(), 155 }, nil 156 } 157 158 // Accounts 159 func (s *Service) Account(address crypto.Address) (*ResultAccount, error) { 160 acc, err := s.state.GetAccount(address) 161 if err != nil { 162 return nil, err 163 } 164 return &ResultAccount{Account: acc}, nil 165 } 166 167 func (s *Service) Accounts(predicate func(*acm.Account) bool) (*ResultAccounts, error) { 168 accounts := make([]*acm.Account, 0) 169 s.state.IterateAccounts(func(account *acm.Account) error { 170 if predicate(account) { 171 accounts = append(accounts, account) 172 } 173 return nil 174 }) 175 176 return &ResultAccounts{ 177 BlockHeight: s.blockchain.LastBlockHeight(), 178 Accounts: accounts, 179 }, nil 180 } 181 182 func (s *Service) Storage(address crypto.Address, key []byte) (*ResultStorage, error) { 183 account, err := s.state.GetAccount(address) 184 if err != nil { 185 return nil, err 186 } 187 if account == nil { 188 return nil, fmt.Errorf("UnknownAddress: %s", address) 189 } 190 191 value, err := s.state.GetStorage(address, binary.LeftPadWord256(key)) 192 if err != nil { 193 return nil, err 194 } 195 if value == binary.Zero256 { 196 return &ResultStorage{Key: key, Value: nil}, nil 197 } 198 return &ResultStorage{Key: key, Value: value.UnpadLeft()}, nil 199 } 200 201 func (s *Service) DumpStorage(address crypto.Address) (*ResultDumpStorage, error) { 202 account, err := s.state.GetAccount(address) 203 if err != nil { 204 return nil, err 205 } 206 if account == nil { 207 return nil, fmt.Errorf("UnknownAddress: %X", address) 208 } 209 var storageItems []StorageItem 210 err = s.state.IterateStorage(address, func(key, value binary.Word256) error { 211 storageItems = append(storageItems, StorageItem{Key: key.UnpadLeft(), Value: value.UnpadLeft()}) 212 return nil 213 }) 214 if err != nil { 215 return nil, err 216 } 217 return &ResultDumpStorage{ 218 StorageItems: storageItems, 219 }, nil 220 } 221 222 func (s *Service) AccountHumanReadable(address crypto.Address) (*ResultAccountHumanReadable, error) { 223 acc, err := s.state.GetAccount(address) 224 if err != nil { 225 return nil, err 226 } 227 if acc == nil { 228 return &ResultAccountHumanReadable{}, nil 229 } 230 tokens, err := acc.Code.Tokens() 231 if err != nil { 232 return nil, err 233 } 234 perms := permission.BasePermissionsToStringList(acc.Permissions.Base) 235 236 return &ResultAccountHumanReadable{ 237 Account: &AccountHumanReadable{ 238 Address: acc.GetAddress(), 239 PublicKey: acc.PublicKey, 240 Sequence: acc.Sequence, 241 Balance: acc.Balance, 242 Code: tokens, 243 Permissions: perms, 244 Roles: acc.Permissions.Roles, 245 }, 246 }, nil 247 } 248 249 func (s *Service) AccountStats() (*ResultAccountStats, error) { 250 stats := s.state.GetAccountStats() 251 return &ResultAccountStats{ 252 AccountsWithCode: stats.AccountsWithCode, 253 AccountsWithoutCode: stats.AccountsWithoutCode, 254 }, nil 255 } 256 257 // Name registry 258 func (s *Service) Name(name string) (*ResultName, error) { 259 entry, err := s.nameReg.GetName(name) 260 if err != nil { 261 return nil, err 262 } 263 if entry == nil { 264 return nil, fmt.Errorf("name %s not found", name) 265 } 266 return &ResultName{Entry: entry}, nil 267 } 268 269 func (s *Service) Names(predicate func(*names.Entry) bool) (*ResultNames, error) { 270 var nms []*names.Entry 271 s.nameReg.IterateNames(func(entry *names.Entry) error { 272 if predicate(entry) { 273 nms = append(nms, entry) 274 } 275 return nil 276 }) 277 return &ResultNames{ 278 BlockHeight: s.blockchain.LastBlockHeight(), 279 Names: nms, 280 }, nil 281 } 282 283 func (s *Service) Block(height uint64) (*ResultBlock, error) { 284 if s.nodeView == nil { 285 return nil, fmt.Errorf("NodeView is not mounted so cannot pull Tendermint blocks") 286 } 287 return &ResultBlock{ 288 Block: &Block{s.nodeView.BlockStore().LoadBlock(int64(height))}, 289 BlockMeta: &BlockMeta{s.nodeView.BlockStore().LoadBlockMeta(int64(height))}, 290 }, nil 291 } 292 293 // Returns the current blockchain height and metadata for a range of blocks 294 // between minHeight and maxHeight. Only returns maxBlockLookback block metadata 295 // from the top of the range of blocks. 296 // Passing 0 for maxHeight sets the upper height of the range to the current 297 // blockchain height. 298 func (s *Service) Blocks(minHeight, maxHeight int64) (*ResultBlocks, error) { 299 if s.nodeView == nil { 300 return nil, fmt.Errorf("NodeView is not mounted so cannot pull Tendermint blocks") 301 } 302 latestHeight := int64(s.blockchain.LastBlockHeight()) 303 304 if minHeight < 1 { 305 minHeight = latestHeight 306 } 307 if maxHeight == 0 || latestHeight < maxHeight { 308 maxHeight = latestHeight 309 } 310 if maxHeight > minHeight && maxHeight-minHeight > MaxBlockLookback { 311 minHeight = maxHeight - MaxBlockLookback 312 } 313 314 var blockMetas []*tmTypes.BlockMeta 315 for height := minHeight; height <= maxHeight; height++ { 316 blockMeta := s.nodeView.BlockStore().LoadBlockMeta(height) 317 blockMetas = append(blockMetas, blockMeta) 318 } 319 320 return &ResultBlocks{ 321 LastHeight: uint64(latestHeight), 322 BlockMetas: blockMetas, 323 }, nil 324 } 325 326 func (s *Service) Validators() (*ResultValidators, error) { 327 var validators []*validator.Validator 328 err := s.validators.Validators(0).IterateValidators(func(id crypto.Addressable, power *big.Int) error { 329 address := id.GetAddress() 330 validators = append(validators, &validator.Validator{ 331 Address: &address, 332 PublicKey: id.GetPublicKey(), 333 Power: power.Uint64(), 334 }) 335 return nil 336 }) 337 if err != nil { 338 return nil, err 339 } 340 return &ResultValidators{ 341 BlockHeight: s.blockchain.LastBlockHeight(), 342 BondedValidators: validators, 343 UnbondingValidators: nil, 344 }, nil 345 } 346 347 func (s *Service) ConsensusState() (*ResultConsensusState, error) { 348 if s.nodeView == nil { 349 return nil, fmt.Errorf("cannot pull ConsensusState because NodeView not mounted") 350 } 351 peers := s.nodeView.Peers().List() 352 peerStates := make([]core_types.PeerStateInfo, len(peers)) 353 for i, peer := range peers { 354 peerState := peer.Get(tmTypes.PeerStateKey).(*consensus.PeerState) 355 peerStateJSON, err := peerState.ToJSON() 356 if err != nil { 357 return nil, err 358 } 359 peerStates[i] = core_types.PeerStateInfo{ 360 // Peer basic info. 361 NodeAddress: p2p.IDAddressString(peer.ID(), peer.NodeInfo().NetAddress().String()), 362 // Peer consensus state. 363 PeerState: peerStateJSON, 364 } 365 } 366 367 roundStateJSON, err := s.nodeView.RoundStateJSON() 368 if err != nil { 369 return nil, err 370 } 371 return &ResultConsensusState{ 372 ResultDumpConsensusState: &core_types.ResultDumpConsensusState{ 373 RoundState: roundStateJSON, 374 Peers: peerStates, 375 }, 376 }, nil 377 } 378 379 func (s *Service) GeneratePrivateAccount() (*ResultGeneratePrivateAccount, error) { 380 privateAccount, err := acm.GeneratePrivateAccount() 381 if err != nil { 382 return nil, err 383 } 384 return &ResultGeneratePrivateAccount{ 385 PrivateAccount: privateAccount.ConcretePrivateAccount(), 386 }, nil 387 } 388 389 func Status(blockchain bcm.BlockchainInfo, validators validator.History, nodeView *tendermint.NodeView, blockTimeWithin, 390 blockSeenTimeWithin string) (*ResultStatus, error) { 391 res := &ResultStatus{ 392 ChainID: blockchain.ChainID(), 393 RunID: nodeView.RunID().String(), 394 BurrowVersion: project.FullVersion(), 395 GenesisHash: blockchain.GenesisHash(), 396 NodeInfo: nodeView.NodeInfo(), 397 SyncInfo: bcm.GetSyncInfo(blockchain), 398 CatchingUp: nodeView.IsFastSyncing(), 399 } 400 if nodeView != nil { 401 address := nodeView.ValidatorAddress() 402 power, err := validators.Validators(0).Power(address) 403 if err != nil { 404 return nil, err 405 } 406 res.ValidatorInfo = &validator.Validator{ 407 Address: &address, 408 PublicKey: nodeView.ValidatorPublicKey(), 409 Power: power.Uint64(), 410 } 411 } 412 413 now := time.Now() 414 415 if blockTimeWithin != "" { 416 err := timeWithin(now, res.SyncInfo.LatestBlockTime, blockTimeWithin) 417 if err != nil { 418 return nil, fmt.Errorf("have not committed block with sufficiently recent timestamp: %v, current status: %s", 419 err, statusJSON(res)) 420 } 421 } 422 423 if blockSeenTimeWithin != "" { 424 err := timeWithin(now, res.SyncInfo.LatestBlockSeenTime, blockSeenTimeWithin) 425 if err != nil { 426 return nil, fmt.Errorf("have not committed a block sufficiently recently: %v, current status: %s", 427 err, statusJSON(res)) 428 } 429 } 430 431 return res, nil 432 } 433 434 func statusJSON(res *ResultStatus) string { 435 bs, err := json.Marshal(res) 436 if err != nil { 437 bs = []byte("<error: could not marshal status>") 438 } 439 return string(bs) 440 } 441 442 func timeWithin(now time.Time, testTime time.Time, within string) error { 443 duration, err := time.ParseDuration(within) 444 if err != nil { 445 return fmt.Errorf("could not parse duration '%s' to determine whether to throw error: %v", within, err) 446 } 447 // Take neg abs in case caller is counting backwards (note we later add the time since we normalise the duration to negative) 448 if duration > 0 { 449 duration = -duration 450 } 451 threshold := now.Add(duration) 452 if testTime.After(threshold) { 453 return nil 454 } 455 return fmt.Errorf("time %s does not fall within last %s (cutoff: %s)", testTime.Format(time.RFC3339), within, 456 threshold.Format(time.RFC3339)) 457 }