From 5d19f2182b0a4d9464cd24430f634f842bcfee4f Mon Sep 17 00:00:00 2001 From: buddho Date: Tue, 20 Aug 2024 11:51:32 +0800 Subject: [PATCH] internal/ethapi: make GetFinalizedHeader monotonically increasing (#2655) --- eth/api_backend.go | 6 +- internal/ethapi/api.go | 76 +++++++++++++++--------- internal/ethapi/api_test.go | 6 +- internal/ethapi/backend.go | 4 +- internal/ethapi/transaction_args_test.go | 2 +- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index d72711929d..6a137155ed 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -441,14 +441,14 @@ func (b *EthAPIBackend) Engine() consensus.Engine { return b.eth.engine } -func (b *EthAPIBackend) CurrentTurnLength() (turnLength uint8, err error) { +func (b *EthAPIBackend) CurrentValidators() ([]common.Address, error) { if p, ok := b.eth.engine.(*parlia.Parlia); ok { service := p.APIs(b.Chain())[0].Service currentHead := rpc.LatestBlockNumber - return service.(*parlia.API).GetTurnLength(¤tHead) + return service.(*parlia.API).GetValidators(¤tHead) } - return 1, nil + return []common.Address{}, errors.New("not supported") } func (b *EthAPIBackend) CurrentHeader() *types.Header { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 1c81ba544c..6a2ac19319 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -862,54 +862,72 @@ func (s *BlockChainAPI) Health() bool { return true } -// GetFinalizedHeader returns the requested finalized block header. -// - probabilisticFinalized should be in range [2,21], -// then the block header with number `max(fastFinalized, latest-probabilisticFinalized)` is returned -func (s *BlockChainAPI) GetFinalizedHeader(ctx context.Context, probabilisticFinalized int64) (map[string]interface{}, error) { - if probabilisticFinalized < 2 || probabilisticFinalized > 21 { - return nil, fmt.Errorf("%d out of range [2,21]", probabilisticFinalized) +func (s *BlockChainAPI) getFinalizedNumber(ctx context.Context, verifiedValidatorNum int64) (int64, error) { + parliaConfig := s.b.ChainConfig().Parlia + if parliaConfig == nil { + return 0, fmt.Errorf("only parlia engine supported") } - currentTurnLength, err := s.b.CurrentTurnLength() + curValidators, err := s.b.CurrentValidators() if err != nil { // impossible - return nil, err + return 0, err } + valLen := int64(len(curValidators)) + if verifiedValidatorNum < 1 || verifiedValidatorNum > valLen { + return 0, fmt.Errorf("%d out of range [1,%d]", verifiedValidatorNum, valLen) + } + fastFinalizedHeader, err := s.b.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) if err != nil { // impossible - return nil, err + return 0, err } + latestHeader, err := s.b.HeaderByNumber(ctx, rpc.LatestBlockNumber) if err != nil { // impossible - return nil, err + return 0, err + } + lastHeader := latestHeader + confirmedValSet := make(map[common.Address]struct{}, valLen) + confirmedValSet[lastHeader.Coinbase] = struct{}{} + for count := 1; int64(len(confirmedValSet)) < verifiedValidatorNum && count <= int(parliaConfig.Epoch) && lastHeader.Number.Int64() > max(fastFinalizedHeader.Number.Int64(), 1); count++ { + lastHeader, err = s.b.HeaderByHash(ctx, lastHeader.ParentHash) + if err != nil { // impossible + return 0, err + } + confirmedValSet[lastHeader.Coinbase] = struct{}{} } - finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized*int64(currentTurnLength)) - return s.GetHeaderByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber)) -} + finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), lastHeader.Number.Int64()) + log.Debug("getFinalizedNumber", "LatestBlockNumber", latestHeader.Number.Int64(), "fastFinalizedHeight", fastFinalizedHeader.Number.Int64(), + "lastHeader", lastHeader.Number.Int64(), "finalizedBlockNumber", finalizedBlockNumber, "len(confirmedValSet)", len(confirmedValSet)) -// GetFinalizedBlock returns the requested finalized block. -// - probabilisticFinalized should be in range [2,21], -// then the block with number `max(fastFinalized, latest-probabilisticFinalized)` is returned -// - When fullTx is true all transactions in the block are returned, otherwise -// only the transaction hash is returned. -func (s *BlockChainAPI) GetFinalizedBlock(ctx context.Context, probabilisticFinalized int64, fullTx bool) (map[string]interface{}, error) { - if probabilisticFinalized < 2 || probabilisticFinalized > 21 { - return nil, fmt.Errorf("%d out of range [2,21]", probabilisticFinalized) - } + return finalizedBlockNumber, nil +} - currentTurnLength, err := s.b.CurrentTurnLength() - if err != nil { // impossible - return nil, err - } - fastFinalizedHeader, err := s.b.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) +// GetFinalizedHeader returns the finalized block header based on the specified parameters. +// - `verifiedValidatorNum` must be within the range [1, len(currentValidators)]. +// - The function calculates `probabilisticFinalizedHeight` as the highest height of the block verified by `verifiedValidatorNum` validators, +// it then returns the block header with a height equal to `max(fastFinalizedHeight, probabilisticFinalizedHeight)`. +// - The height of the returned block header is guaranteed to be monotonically increasing. +func (s *BlockChainAPI) GetFinalizedHeader(ctx context.Context, verifiedValidatorNum int64) (map[string]interface{}, error) { + finalizedBlockNumber, err := s.getFinalizedNumber(ctx, verifiedValidatorNum) if err != nil { // impossible return nil, err } - latestHeader, err := s.b.HeaderByNumber(ctx, rpc.LatestBlockNumber) + return s.GetHeaderByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber)) +} + +// GetFinalizedBlock returns the finalized block based on the specified parameters. +// - `verifiedValidatorNum` must be within the range [1, len(currentValidators)]. +// - The function calculates `probabilisticFinalizedHeight` as the highest height of the block verified by `verifiedValidatorNum` validators, +// it then returns the block with a height equal to `max(fastFinalizedHeight, probabilisticFinalizedHeight)`. +// - If `fullTx` is true, the block includes all transactions; otherwise, only transaction hashes are included. +// - The height of the returned block is guaranteed to be monotonically increasing. +func (s *BlockChainAPI) GetFinalizedBlock(ctx context.Context, verifiedValidatorNum int64, fullTx bool) (map[string]interface{}, error) { + finalizedBlockNumber, err := s.getFinalizedNumber(ctx, verifiedValidatorNum) if err != nil { // impossible return nil, err } - finalizedBlockNumber := max(fastFinalizedHeader.Number.Int64(), latestHeader.Number.Int64()-probabilisticFinalized*int64(currentTurnLength)) return s.GetBlockByNumber(ctx, rpc.BlockNumber(finalizedBlockNumber), fullTx) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index ac467c352a..8f1b8e3b0f 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -631,9 +631,9 @@ func (b testBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transactio func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { panic("implement me") } -func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } -func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } -func (b testBackend) CurrentTurnLength() (uint8, error) { return 1, nil } +func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } +func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } +func (b testBackend) CurrentValidators() ([]common.Address, error) { return []common.Address{}, nil } func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { panic("implement me") } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 79492cda85..0cb3fb25cd 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -89,8 +89,8 @@ type Backend interface { ChainConfig() *params.ChainConfig Engine() consensus.Engine - // CurrentTurnLength return the turnLength at the latest block - CurrentTurnLength() (uint8, error) + // CurrentValidators return the list of validator at the latest block + CurrentValidators() ([]common.Address, error) // This is copied from filters.Backend // eth/filters needs to be initialized from this backend type, so methods needed by diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 17bbc49c5a..cc00e839d6 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -416,7 +416,7 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) func (b *backendMock) Engine() consensus.Engine { return nil } -func (b *backendMock) CurrentTurnLength() (uint8, error) { return 1, nil } +func (b *backendMock) CurrentValidators() ([]common.Address, error) { return []common.Address{}, nil } func (b *backendMock) MevRunning() bool { return false } func (b *backendMock) HasBuilder(builder common.Address) bool { return false }