github.com/onflow/flow-go@v0.33.17/admin/commands/common/read_protocol_state_blocks.go (about)

     1  package common
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math"
     9  	"strings"
    10  
    11  	"github.com/onflow/flow-go/admin"
    12  	"github.com/onflow/flow-go/admin/commands"
    13  	storageCommands "github.com/onflow/flow-go/admin/commands/storage"
    14  	"github.com/onflow/flow-go/model/flow"
    15  	"github.com/onflow/flow-go/state/protocol"
    16  	"github.com/onflow/flow-go/storage"
    17  )
    18  
    19  var _ commands.AdminCommand = (*ReadProtocolStateBlocksCommand)(nil)
    20  
    21  type requestType int
    22  
    23  const (
    24  	ID requestType = iota
    25  	Height
    26  	Final
    27  	Sealed
    28  )
    29  
    30  type requestData struct {
    31  	requestType      requestType
    32  	blockID          flow.Identifier
    33  	blockHeight      uint64
    34  	numBlocksToQuery uint
    35  }
    36  
    37  type ReadProtocolStateBlocksCommand struct {
    38  	state  protocol.State
    39  	blocks storage.Blocks
    40  }
    41  
    42  func (r *ReadProtocolStateBlocksCommand) getBlockByHeight(height uint64) (*flow.Block, error) {
    43  	header, err := r.state.AtHeight(height).Head()
    44  	if err != nil {
    45  		return nil, fmt.Errorf("could not get header by height: %v, %w", height, err)
    46  	}
    47  
    48  	block, err := r.getBlockByHeader(header)
    49  	if err != nil {
    50  		return nil, fmt.Errorf("could not get block by header: %w", err)
    51  	}
    52  	return block, nil
    53  }
    54  
    55  func (r *ReadProtocolStateBlocksCommand) getFinal() (*flow.Block, error) {
    56  	header, err := r.state.Final().Head()
    57  	if err != nil {
    58  		return nil, fmt.Errorf("could not get finalized, %w", err)
    59  	}
    60  
    61  	block, err := r.getBlockByHeader(header)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("could not get block by header: %w", err)
    64  	}
    65  	return block, nil
    66  }
    67  
    68  func (r *ReadProtocolStateBlocksCommand) getSealed() (*flow.Block, error) {
    69  	header, err := r.state.Sealed().Head()
    70  	if err != nil {
    71  		return nil, fmt.Errorf("could not get sealed block, %w", err)
    72  	}
    73  
    74  	block, err := r.getBlockByHeader(header)
    75  	if err != nil {
    76  		return nil, fmt.Errorf("could not get block by header: %w", err)
    77  	}
    78  	return block, nil
    79  }
    80  
    81  func (r *ReadProtocolStateBlocksCommand) getBlockByID(blockID flow.Identifier) (*flow.Block, error) {
    82  	header, err := r.state.AtBlockID(blockID).Head()
    83  	if err != nil {
    84  		return nil, fmt.Errorf("could not get header by blockID: %v, %w", blockID, err)
    85  	}
    86  
    87  	block, err := r.getBlockByHeader(header)
    88  	if err != nil {
    89  		return nil, fmt.Errorf("could not get block by header: %w", err)
    90  	}
    91  	return block, nil
    92  }
    93  
    94  func (r *ReadProtocolStateBlocksCommand) getBlockByHeader(header *flow.Header) (*flow.Block, error) {
    95  	blockID := header.ID()
    96  	block, err := r.blocks.ByID(blockID)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("could not get block by ID %v: %w", blockID, err)
    99  	}
   100  	return block, nil
   101  }
   102  
   103  func (r *ReadProtocolStateBlocksCommand) Handler(_ context.Context, req *admin.CommandRequest) (interface{}, error) {
   104  	data := req.ValidatorData.(*requestData)
   105  	var result []*flow.Block
   106  	var block *flow.Block
   107  	var err error
   108  
   109  	switch data.requestType {
   110  	case ID:
   111  		block, err = r.getBlockByID(data.blockID)
   112  	case Height:
   113  		block, err = r.getBlockByHeight(data.blockHeight)
   114  	case Final:
   115  		block, err = r.getFinal()
   116  	case Sealed:
   117  		block, err = r.getSealed()
   118  	}
   119  
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	result = append(result, block)
   125  	firstHeight := int64(block.Header.Height)
   126  
   127  	for height := firstHeight - 1; height >= 0 && height > firstHeight-int64(data.numBlocksToQuery); height-- {
   128  		block, err = r.getBlockByHeight(uint64(height))
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		result = append(result, block)
   133  	}
   134  
   135  	var resultList []interface{}
   136  	bytes, err := json.Marshal(result)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	err = json.Unmarshal(bytes, &resultList)
   141  
   142  	return resultList, err
   143  }
   144  
   145  // Validator validates the request.
   146  // Returns admin.InvalidAdminReqError for invalid/malformed requests.
   147  func (r *ReadProtocolStateBlocksCommand) Validator(req *admin.CommandRequest) error {
   148  	input, ok := req.Data.(map[string]interface{})
   149  	if !ok {
   150  		return admin.NewInvalidAdminReqFormatError("expected map[string]any")
   151  	}
   152  
   153  	inBlock, ok := input["block"]
   154  	if !ok {
   155  		return admin.NewInvalidAdminReqErrorf("missing 'block' field")
   156  	}
   157  
   158  	data := &requestData{}
   159  
   160  	switch block := inBlock.(type) {
   161  	case string:
   162  		block = strings.ToLower(strings.TrimSpace(block))
   163  		if block == storageCommands.FINAL {
   164  			data.requestType = Final
   165  		} else if block == storageCommands.SEALED {
   166  			data.requestType = Sealed
   167  		} else if len(block) == 2*flow.IdentifierLen {
   168  			b, err := hex.DecodeString(block)
   169  			if err != nil {
   170  				return admin.NewInvalidAdminReqParameterError("block", "block ID must be 64-char hex string", inBlock)
   171  			}
   172  			data.requestType = ID
   173  			data.blockID = flow.HashToID(b)
   174  		} else {
   175  			return admin.NewInvalidAdminReqParameterError("block", "must be 'final', 'sealed', or block ID hex", inBlock)
   176  		}
   177  	case float64:
   178  		if block < 0 || math.Trunc(block) != block {
   179  			return admin.NewInvalidAdminReqParameterError("block", "block height must be >=0 and integral", inBlock)
   180  		}
   181  		data.requestType = Height
   182  		data.blockHeight = uint64(block)
   183  	default:
   184  		return admin.NewInvalidAdminReqParameterError("block", "must be string or number", inBlock)
   185  	}
   186  
   187  	if inN, ok := input["n"]; ok {
   188  		n, ok := inN.(float64)
   189  		if !ok {
   190  			return admin.NewInvalidAdminReqParameterError("n", "must be number", inN)
   191  		}
   192  		if math.Trunc(n) != n {
   193  			return admin.NewInvalidAdminReqParameterError("n", "must be integral", inN)
   194  		}
   195  		if n < 1 {
   196  			return admin.NewInvalidAdminReqParameterError("n", "must be >=1", inN)
   197  		}
   198  		data.numBlocksToQuery = uint(n)
   199  	} else {
   200  		data.numBlocksToQuery = 1
   201  	}
   202  
   203  	req.ValidatorData = data
   204  
   205  	return nil
   206  
   207  }
   208  
   209  func NewReadProtocolStateBlocksCommand(state protocol.State, storage storage.Blocks) commands.AdminCommand {
   210  	return &ReadProtocolStateBlocksCommand{
   211  		state,
   212  		storage,
   213  	}
   214  }