github.com/klaytn/klaytn@v1.12.1/consensus/istanbul/backend/api.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2017 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from quorum/consensus/istanbul/backend/api.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package backend
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"math/big"
    27  	"reflect"
    28  
    29  	klaytnApi "github.com/klaytn/klaytn/api"
    30  	"github.com/klaytn/klaytn/blockchain"
    31  	"github.com/klaytn/klaytn/blockchain/types"
    32  	"github.com/klaytn/klaytn/common"
    33  	"github.com/klaytn/klaytn/consensus"
    34  	"github.com/klaytn/klaytn/consensus/istanbul"
    35  	istanbulCore "github.com/klaytn/klaytn/consensus/istanbul/core"
    36  	"github.com/klaytn/klaytn/networks/rpc"
    37  )
    38  
    39  // API is a user facing RPC API to dump Istanbul state
    40  type API struct {
    41  	chain    consensus.ChainReader
    42  	istanbul *backend
    43  }
    44  
    45  // GetSnapshot retrieves the state snapshot at a given block.
    46  func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
    47  	// Retrieve the requested block number (or current if none requested)
    48  	header, err := headerByRpcNumber(api.chain, number)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	return api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil, false)
    54  }
    55  
    56  // GetSnapshotAtHash retrieves the state snapshot at a given block.
    57  func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
    58  	header := api.chain.GetHeaderByHash(hash)
    59  	if header == nil {
    60  		return nil, errUnknownBlock
    61  	}
    62  	return api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil, false)
    63  }
    64  
    65  // GetValidators retrieves the list of authorized validators with the given block number.
    66  func (api *API) GetValidators(number *rpc.BlockNumber) ([]common.Address, error) {
    67  	header, err := headerByRpcNumber(api.chain, number)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	blockNumber := header.Number.Uint64()
    73  	if blockNumber == 0 {
    74  		// The committee of genesis block can not be calculated because it requires a previous block.
    75  		istanbulExtra, err := types.ExtractIstanbulExtra(header)
    76  		if err != nil {
    77  			return nil, errExtractIstanbulExtra
    78  		}
    79  		return istanbulExtra.Validators, nil
    80  	}
    81  
    82  	snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64()-1, header.ParentHash, nil, false)
    83  	if err != nil {
    84  		logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
    85  		return nil, err
    86  	}
    87  	return snap.validators(), nil
    88  }
    89  
    90  // GetValidatorsAtHash retrieves the list of authorized validators with the given block hash.
    91  func (api *API) GetValidatorsAtHash(hash common.Hash) ([]common.Address, error) {
    92  	header := api.chain.GetHeaderByHash(hash)
    93  	if header == nil {
    94  		return nil, errUnknownBlock
    95  	}
    96  
    97  	blockNumber := header.Number.Uint64()
    98  	if blockNumber == 0 {
    99  		// The committee of genesis block can not be calculated because it requires a previous block.
   100  		istanbulExtra, err := types.ExtractIstanbulExtra(header)
   101  		if err != nil {
   102  			return nil, errExtractIstanbulExtra
   103  		}
   104  		return istanbulExtra.Validators, nil
   105  	}
   106  
   107  	snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64()-1, header.ParentHash, nil, false)
   108  	if err != nil {
   109  		logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
   110  		return nil, errInternalError
   111  	}
   112  	return snap.validators(), nil
   113  }
   114  
   115  // GetDemotedValidators retrieves the list of authorized, but demoted validators with the given block number.
   116  func (api *API) GetDemoteValidators(number *rpc.BlockNumber) ([]common.Address, error) {
   117  	header, err := headerByRpcNumber(api.chain, number)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	blockNumber := header.Number.Uint64()
   123  	if blockNumber == 0 {
   124  		snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil, false)
   125  		if err != nil {
   126  			logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
   127  			return nil, err
   128  		}
   129  		return snap.demotedValidators(), nil
   130  	} else {
   131  		snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64()-1, header.ParentHash, nil, false)
   132  		if err != nil {
   133  			logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
   134  			return nil, err
   135  		}
   136  		return snap.demotedValidators(), nil
   137  	}
   138  }
   139  
   140  // GetDemotedValidatorsAtHash retrieves the list of authorized, but demoted validators with the given block hash.
   141  func (api *API) GetDemotedValidatorsAtHash(hash common.Hash) ([]common.Address, error) {
   142  	header := api.chain.GetHeaderByHash(hash)
   143  	if header == nil {
   144  		return nil, errUnknownBlock
   145  	}
   146  
   147  	blockNumber := header.Number.Uint64()
   148  	if blockNumber == 0 {
   149  		snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil, false)
   150  		if err != nil {
   151  			logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
   152  			return nil, err
   153  		}
   154  		return snap.demotedValidators(), nil
   155  	} else {
   156  		snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64()-1, header.ParentHash, nil, false)
   157  		if err != nil {
   158  			logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
   159  			return nil, err
   160  		}
   161  		return snap.demotedValidators(), nil
   162  	}
   163  }
   164  
   165  // Candidates returns the current candidates the node tries to uphold and vote on.
   166  func (api *API) Candidates() map[common.Address]bool {
   167  	api.istanbul.candidatesLock.RLock()
   168  	defer api.istanbul.candidatesLock.RUnlock()
   169  
   170  	proposals := make(map[common.Address]bool)
   171  	for address, auth := range api.istanbul.candidates {
   172  		proposals[address] = auth
   173  	}
   174  	return proposals
   175  }
   176  
   177  // Propose injects a new authorization candidate that the validator will attempt to
   178  // push through.
   179  func (api *API) Propose(address common.Address, auth bool) {
   180  	api.istanbul.candidatesLock.Lock()
   181  	defer api.istanbul.candidatesLock.Unlock()
   182  
   183  	api.istanbul.candidates[address] = auth
   184  }
   185  
   186  // Discard drops a currently running candidate, stopping the validator from casting
   187  // further votes (either for or against).
   188  func (api *API) Discard(address common.Address) {
   189  	api.istanbul.candidatesLock.Lock()
   190  	defer api.istanbul.candidatesLock.Unlock()
   191  
   192  	delete(api.istanbul.candidates, address)
   193  }
   194  
   195  // API extended by Klaytn developers
   196  type APIExtension struct {
   197  	chain    consensus.ChainReader
   198  	istanbul *backend
   199  }
   200  
   201  var (
   202  	errPendingNotAllowed       = errors.New("pending is not allowed")
   203  	errInternalError           = errors.New("internal error")
   204  	errStartNotPositive        = errors.New("start block number should be positive")
   205  	errEndLargetThanLatest     = errors.New("end block number should be smaller than the latest block number")
   206  	errStartLargerThanEnd      = errors.New("start should be smaller than end")
   207  	errRequestedBlocksTooLarge = errors.New("number of requested blocks should be smaller than 50")
   208  	errRangeNil                = errors.New("range values should not be nil")
   209  	errExtractIstanbulExtra    = errors.New("extract Istanbul Extra from block header of the given block number")
   210  	errNoBlockExist            = errors.New("block with the given block number is not existed")
   211  	errNoBlockNumber           = errors.New("block number is not assigned")
   212  )
   213  
   214  // GetCouncil retrieves the list of authorized validators at the specified block.
   215  func (api *APIExtension) GetCouncil(number *rpc.BlockNumber) ([]common.Address, error) {
   216  	header, err := headerByRpcNumber(api.chain, number)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	blockNumber := header.Number.Uint64()
   222  	if blockNumber == 0 {
   223  		// The committee of genesis block can not be calculated because it requires a previous block.
   224  		istanbulExtra, err := types.ExtractIstanbulExtra(header)
   225  		if err != nil {
   226  			return nil, errExtractIstanbulExtra
   227  		}
   228  		return istanbulExtra.Validators, nil
   229  	}
   230  
   231  	snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64()-1, header.ParentHash, nil, false)
   232  	if err != nil {
   233  		logger.Error("Failed to get snapshot.", "blockNum", blockNumber, "err", err)
   234  		return nil, err
   235  	}
   236  
   237  	return append(snap.validators(), snap.demotedValidators()...), nil
   238  }
   239  
   240  func (api *APIExtension) GetCouncilSize(number *rpc.BlockNumber) (int, error) {
   241  	council, err := api.GetCouncil(number)
   242  	if err == nil {
   243  		return len(council), nil
   244  	} else {
   245  		return -1, err
   246  	}
   247  }
   248  
   249  func (api *APIExtension) GetCommittee(number *rpc.BlockNumber) ([]common.Address, error) {
   250  	header, err := headerByRpcNumber(api.chain, number)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	blockNumber := header.Number.Uint64()
   256  	if blockNumber == 0 {
   257  		// The committee of genesis block can not be calculated because it requires a previous block.
   258  		istanbulExtra, err := types.ExtractIstanbulExtra(header)
   259  		if err != nil {
   260  			return nil, errExtractIstanbulExtra
   261  		}
   262  		return istanbulExtra.Validators, nil
   263  	}
   264  
   265  	snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64()-1, header.ParentHash, nil, false)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	round := header.Round()
   270  	view := &istanbul.View{
   271  		Sequence: new(big.Int).SetUint64(blockNumber),
   272  		Round:    new(big.Int).SetUint64(uint64(round)),
   273  	}
   274  
   275  	// get the proposer of this block.
   276  	proposer, err := ecrecover(header)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  
   281  	parentHash := header.ParentHash
   282  
   283  	// get the committee list of this block at the view (blockNumber, round)
   284  	committee := snap.ValSet.SubListWithProposer(parentHash, proposer, view)
   285  	addresses := make([]common.Address, len(committee))
   286  	for i, v := range committee {
   287  		addresses[i] = v.Address()
   288  	}
   289  
   290  	return addresses, nil
   291  }
   292  
   293  func (api *APIExtension) GetCommitteeSize(number *rpc.BlockNumber) (int, error) {
   294  	committee, err := api.GetCommittee(number)
   295  	if err == nil {
   296  		return len(committee), nil
   297  	} else {
   298  		return -1, err
   299  	}
   300  }
   301  
   302  func (api *APIExtension) makeRPCBlockOutput(b *types.Block,
   303  	cInfo consensus.ConsensusInfo, transactions types.Transactions, receipts types.Receipts,
   304  ) map[string]interface{} {
   305  	head := b.Header() // copies the header once
   306  	hash := head.Hash()
   307  
   308  	td := big.NewInt(0)
   309  	if bc, ok := api.chain.(*blockchain.BlockChain); ok {
   310  		td = bc.GetTd(hash, b.NumberU64())
   311  	}
   312  	r, err := klaytnApi.RpcOutputBlock(b, td, false, false, api.chain.Config().Rules(b.Header().Number))
   313  	if err != nil {
   314  		logger.Error("failed to RpcOutputBlock", "err", err)
   315  		return nil
   316  	}
   317  
   318  	// make transactions
   319  	numTxs := len(transactions)
   320  	rpcTransactions := make([]map[string]interface{}, numTxs)
   321  	for i, tx := range transactions {
   322  		if len(receipts) == len(transactions) {
   323  			rpcTransactions[i] = klaytnApi.RpcOutputReceipt(head, tx, hash, head.Number.Uint64(), uint64(i), receipts[i])
   324  		} else {
   325  			// fill the transaction output if receipt is not found
   326  			rpcTransactions[i] = klaytnApi.NewRPCTransaction(b, tx, hash, head.Number.Uint64(), uint64(i))
   327  		}
   328  	}
   329  
   330  	r["committee"] = cInfo.Committee
   331  	r["committers"] = cInfo.Committers
   332  	r["sigHash"] = cInfo.SigHash
   333  	r["proposer"] = cInfo.Proposer
   334  	r["round"] = cInfo.Round
   335  	r["originProposer"] = cInfo.OriginProposer
   336  	r["transactions"] = rpcTransactions
   337  	return r
   338  }
   339  
   340  func RecoverCommittedSeals(extra *types.IstanbulExtra, headerHash common.Hash) ([]common.Address, error) {
   341  	committers := make([]common.Address, len(extra.CommittedSeal))
   342  	for idx, cs := range extra.CommittedSeal {
   343  		committer, err := istanbul.GetSignatureAddress(istanbulCore.PrepareCommittedSeal(headerHash), cs)
   344  		if err != nil {
   345  			return nil, err
   346  		}
   347  		committers[idx] = committer
   348  	}
   349  	return committers, nil
   350  }
   351  
   352  // TODO-Klaytn: This API functions should be managed with API functions with namespace "klay"
   353  func (api *APIExtension) GetBlockWithConsensusInfoByNumber(number *rpc.BlockNumber) (map[string]interface{}, error) {
   354  	b, ok := api.chain.(*blockchain.BlockChain)
   355  	if !ok {
   356  		logger.Error("chain is not a type of blockchain.BlockChain", "type", reflect.TypeOf(api.chain))
   357  		return nil, errInternalError
   358  	}
   359  	var block *types.Block
   360  	var blockNumber uint64
   361  
   362  	if number == nil {
   363  		logger.Trace("block number is not assigned")
   364  		return nil, errNoBlockNumber
   365  	}
   366  
   367  	if *number == rpc.PendingBlockNumber {
   368  		logger.Trace("Cannot get consensus information of the PendingBlock.")
   369  		return nil, errPendingNotAllowed
   370  	}
   371  
   372  	if *number == rpc.LatestBlockNumber {
   373  		block = b.CurrentBlock()
   374  		blockNumber = block.NumberU64()
   375  	} else {
   376  		// rpc.EarliestBlockNumber == 0, no need to treat it as a special case.
   377  		blockNumber = uint64(number.Int64())
   378  		block = b.GetBlockByNumber(blockNumber)
   379  	}
   380  
   381  	if block == nil {
   382  		logger.Trace("Finding a block by number failed.", "blockNum", blockNumber)
   383  		return nil, fmt.Errorf("the block does not exist (block number: %d)", blockNumber)
   384  	}
   385  	blockHash := block.Hash()
   386  
   387  	cInfo, err := api.istanbul.GetConsensusInfo(block)
   388  	if err != nil {
   389  		logger.Error("Getting the proposer and validators failed.", "blockHash", blockHash, "err", err)
   390  		return nil, errInternalError
   391  	}
   392  
   393  	receipts := b.GetBlockReceiptsInCache(blockHash)
   394  	if receipts == nil {
   395  		receipts = b.GetReceiptsByBlockHash(blockHash)
   396  	}
   397  
   398  	return api.makeRPCBlockOutput(block, cInfo, block.Transactions(), receipts), nil
   399  }
   400  
   401  func (api *APIExtension) GetBlockWithConsensusInfoByNumberRange(start *rpc.BlockNumber, end *rpc.BlockNumber) (map[string]interface{}, error) {
   402  	blocks := make(map[string]interface{})
   403  
   404  	if start == nil || end == nil {
   405  		logger.Trace("the range values should not be nil.", "start", start, "end", end)
   406  		return nil, errRangeNil
   407  	}
   408  
   409  	// check error status.
   410  	s := start.Int64()
   411  	e := end.Int64()
   412  	if s < 0 {
   413  		logger.Trace("start should be positive", "start", s)
   414  		return nil, errStartNotPositive
   415  	}
   416  
   417  	eChain := api.chain.CurrentHeader().Number.Int64()
   418  	if e > eChain {
   419  		logger.Trace("end should be smaller than the lastest block number", "end", end, "eChain", eChain)
   420  		return nil, errEndLargetThanLatest
   421  	}
   422  
   423  	if s > e {
   424  		logger.Trace("start should be smaller than end", "start", s, "end", e)
   425  		return nil, errStartLargerThanEnd
   426  	}
   427  
   428  	if (e - s) > 50 {
   429  		logger.Trace("number of requested blocks should be smaller than 50", "start", s, "end", e)
   430  		return nil, errRequestedBlocksTooLarge
   431  	}
   432  
   433  	// gather s~e blocks
   434  	for i := s; i <= e; i++ {
   435  		strIdx := fmt.Sprintf("0x%x", i)
   436  
   437  		blockNum := rpc.BlockNumber(i)
   438  		b, err := api.GetBlockWithConsensusInfoByNumber(&blockNum)
   439  		if err != nil {
   440  			logger.Error("error on GetBlockWithConsensusInfoByNumber", "err", err)
   441  			blocks[strIdx] = nil
   442  		} else {
   443  			blocks[strIdx] = b
   444  		}
   445  	}
   446  
   447  	return blocks, nil
   448  }
   449  
   450  func (api *APIExtension) GetBlockWithConsensusInfoByHash(blockHash common.Hash) (map[string]interface{}, error) {
   451  	b, ok := api.chain.(*blockchain.BlockChain)
   452  	if !ok {
   453  		logger.Error("chain is not a type of blockchain.Blockchain, returning...", "type", reflect.TypeOf(api.chain))
   454  		return nil, errInternalError
   455  	}
   456  
   457  	block := b.GetBlockByHash(blockHash)
   458  	if block == nil {
   459  		logger.Trace("Finding a block failed.", "blockHash", blockHash)
   460  		return nil, fmt.Errorf("the block does not exist (block hash: %s)", blockHash.String())
   461  	}
   462  
   463  	cInfo, err := api.istanbul.GetConsensusInfo(block)
   464  	if err != nil {
   465  		logger.Error("Getting the proposer and validators failed.", "blockHash", blockHash, "err", err)
   466  		return nil, errInternalError
   467  	}
   468  
   469  	receipts := b.GetBlockReceiptsInCache(blockHash)
   470  	if receipts == nil {
   471  		receipts = b.GetReceiptsByBlockHash(blockHash)
   472  	}
   473  
   474  	return api.makeRPCBlockOutput(block, cInfo, block.Transactions(), receipts), nil
   475  }
   476  
   477  func (api *API) GetTimeout() uint64 {
   478  	return istanbul.DefaultConfig.Timeout
   479  }
   480  
   481  // Retrieve the header at requested block number
   482  func headerByRpcNumber(chain consensus.ChainReader, number *rpc.BlockNumber) (*types.Header, error) {
   483  	var header *types.Header
   484  	if number == nil || *number == rpc.LatestBlockNumber {
   485  		header = chain.CurrentHeader()
   486  	} else if *number == rpc.PendingBlockNumber {
   487  		logger.Trace("Cannot get snapshot of the pending block.", "number", number)
   488  		return nil, errPendingNotAllowed
   489  	} else {
   490  		header = chain.GetHeaderByNumber(uint64(number.Int64()))
   491  	}
   492  	// Ensure we have an actually valid block and return its snapshot
   493  	if header == nil {
   494  		return nil, errUnknownBlock
   495  	}
   496  	return header, nil
   497  }