diff --git a/consensus/bor/api.go b/consensus/bor/api.go index 7ef9cdcd473117798ce9a7dfce745d746e3135e5..ca64cd5521e55a87fbdad4af75aa6a2a5529d559 100644 --- a/consensus/bor/api.go +++ b/consensus/bor/api.go @@ -17,9 +17,12 @@ package bor import ( + "math" "math/big" + "strconv" "sync" + lru "github.com/hashicorp/golang-lru" "github.com/maticnetwork/bor/common" "github.com/maticnetwork/bor/consensus" "github.com/maticnetwork/bor/core/types" @@ -29,11 +32,17 @@ import ( "golang.org/x/crypto/sha3" ) +var ( + // MaxCheckpointLength is the maximum number of blocks that can be requested for constructing a checkpoint root hash + MaxCheckpointLength = uint64(math.Pow(2, 15)) +) + // API is a user facing RPC API to allow controlling the signer and voting // mechanisms of the proof-of-authority scheme. type API struct { - chain consensus.ChainReader - bor *Bor + chain consensus.ChainReader + bor *Bor + rootHashCache *lru.ARCCache } // GetSnapshot retrieves the state snapshot at a given block. @@ -112,22 +121,38 @@ func (api *API) GetCurrentValidators() ([]*Validator, error) { return snap.ValidatorSet.Validators, nil } -// GetRootHash gets the current validators -func (api *API) GetRootHash(start uint64, end uint64) ([]byte, error) { +// GetRootHash returns the merkle root of the start to end block headers +func (api *API) GetRootHash(start int64, end int64) ([]byte, error) { + key := getKey(start, end) + if root, err := api.lookupCache(key); err != nil { + return nil, err + } else if root != nil { + return root, nil + } + length := uint64(end - start + 1) + if length > MaxCheckpointLength { + return nil, &MaxCheckpointLengthExceededError{start, end} + } + currentHeaderNumber := api.chain.CurrentHeader().Number.Int64() + if start > end || end > currentHeaderNumber { + return nil, &InvalidStartEndBlockError{start, end, currentHeaderNumber} + } blockHeaders := make([]*types.Header, end-start+1) wg := new(sync.WaitGroup) - // do we want to limit the # of concurrent go routines? + concurrent := make(chan bool, 20) for i := start; i <= end; i++ { wg.Add(1) - go func(number uint64) { - blockHeaders[number-start] = api.chain.GetHeaderByNumber(number) + concurrent <- true + go func(number int64) { + blockHeaders[number-start] = api.chain.GetHeaderByNumber(uint64(number)) + <-concurrent wg.Done() }(i) } wg.Wait() + close(concurrent) - expectedLength := nextPowerOfTwo(end - start + 1) - headers := make([][32]byte, expectedLength) + headers := make([][32]byte, nextPowerOfTwo(length)) for i := 0; i < len(blockHeaders); i++ { blockHeader := blockHeaders[i] header := crypto.Keccak256(appendBytes32( @@ -146,6 +171,24 @@ func (api *API) GetRootHash(start uint64, end uint64) ([]byte, error) { if err := tree.Generate(convert(headers), sha3.NewLegacyKeccak256()); err != nil { return nil, err } + root := tree.Root().Hash + api.rootHashCache.Add(key, root) + return root, nil +} + +func (api *API) lookupCache(key string) ([]byte, error) { + var err error + if api.rootHashCache == nil { + if api.rootHashCache, err = lru.NewARC(10); err != nil { + return nil, err + } + } + if root, known := api.rootHashCache.Get(key); known { + return root.([]byte), nil + } + return nil, nil +} - return tree.Root().Hash, nil +func getKey(start int64, end int64) string { + return strconv.FormatInt(start, 10) + "-" + strconv.FormatInt(end, 10) } diff --git a/consensus/bor/errors.go b/consensus/bor/errors.go index a3b0dc656d08352a5a8ad3f5f1dddfca3aee52b3..ae7da982a7651cc19e6826186e46c6e78e14ea78 100644 --- a/consensus/bor/errors.go +++ b/consensus/bor/errors.go @@ -40,3 +40,31 @@ func (e *TotalVotingPowerExceededError) Error() string { e.Validators, ) } + +type InvalidStartEndBlockError struct { + Start int64 + End int64 + CurrentHeader int64 +} + +func (e *InvalidStartEndBlockError) Error() string { + return fmt.Sprintf( + "Invalid parameters start: %d and end block: %d params", + e.Start, + e.End, + ) +} + +type MaxCheckpointLengthExceededError struct { + Start int64 + End int64 +} + +func (e *MaxCheckpointLengthExceededError) Error() string { + return fmt.Sprintf( + "Start: %d and end block: %d exceed max allowed checkpoint length: %d", + e.Start, + e.End, + MaxCheckpointLength, + ) +} diff --git a/eth/api_backend.go b/eth/api_backend.go index ff03d1a91e0537368b4c8ad9f10c07e916ef819a..7958f86b37c261d9a6d47eb85762d0473bf28d21 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -248,3 +248,17 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *EthAPIBackend) GetRootHash(ctx context.Context, starBlockNr rpc.BlockNumber, endBlockNr rpc.BlockNumber) ([]byte, error) { + var api *bor.API + for _, _api := range b.eth.Engine().APIs(b.eth.BlockChain()) { + if _api.Namespace == "bor" { + api = _api.Service.(*bor.API) + } + } + root, err := api.GetRootHash(starBlockNr.Int64(), endBlockNr.Int64()) + if err != nil { + return nil, err + } + return root, nil +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2a939ed2c463f6d81a3d249a6d45afffd50d95a9..0ad624e7ed3dbf88787e096451291689b7a4ddac 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -716,6 +716,14 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A return res[:], state.Error() } +func (s *PublicBlockChainAPI) GetRootHash(ctx context.Context, starBlockNr rpc.BlockNumber, endBlockNr rpc.BlockNumber) ([]byte, error) { + root, err := s.b.GetRootHash(ctx, starBlockNr, endBlockNr) + if err != nil { + return nil, err + } + return root, nil +} + // CallArgs represents the arguments for a call. type CallArgs struct { From *common.Address `json:"from"` diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index e0deeab81fdb42e2c907b223fe1469d085b52866..21cef13f441506bdc7c7d38e356d9da287b03620 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -62,6 +62,7 @@ type Backend interface { SubscribeStateEvent(ch chan<- core.NewStateChangeEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription + GetRootHash(ctx context.Context, starBlockNr rpc.BlockNumber, endBlockNr rpc.BlockNumber) ([]byte, error) // Transaction pool API SendTx(ctx context.Context, signedTx *types.Transaction) error diff --git a/les/api_backend.go b/les/api_backend.go index 5c8ec82d8812f6c828364e2d4a44334ebb163371..4739abcdebf951e96caefd8e46766dd391bbcbe2 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -223,3 +223,7 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } } + +func (b *LesApiBackend) GetRootHash(ctx context.Context, starBlockNr rpc.BlockNumber, endBlockNr rpc.BlockNumber) ([]byte, error) { + return nil, errors.New("Not implemented") +}