github.com/mavryk-network/mvgo@v1.19.9/rpc/block.go (about)

     1  // Copyright (c) 2020-2022 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package rpc
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"time"
    12  
    13  	"github.com/mavryk-network/mvgo/mavryk"
    14  )
    15  
    16  // Block holds information about a Tezos block
    17  type Block struct {
    18  	Protocol   mavryk.ProtocolHash `json:"protocol"`
    19  	ChainId    mavryk.ChainIdHash  `json:"chain_id"`
    20  	Hash       mavryk.BlockHash    `json:"hash"`
    21  	Header     BlockHeader         `json:"header"`
    22  	Metadata   BlockMetadata       `json:"metadata"`
    23  	Operations [][]*Operation      `json:"operations"`
    24  }
    25  
    26  func (b Block) GetLevel() int64 {
    27  	return b.Header.Level
    28  }
    29  
    30  func (b Block) GetTimestamp() time.Time {
    31  	return b.Header.Timestamp
    32  }
    33  
    34  func (b Block) GetVersion() int {
    35  	return b.Header.Proto
    36  }
    37  
    38  func (b Block) GetCycle() int64 {
    39  	if b.Metadata.LevelInfo != nil {
    40  		return b.Metadata.LevelInfo.Cycle
    41  	}
    42  	if b.Metadata.Level != nil {
    43  		return b.Metadata.Level.Cycle
    44  	}
    45  	return 0
    46  }
    47  
    48  func (b Block) GetLevelInfo() LevelInfo {
    49  	if b.Metadata.LevelInfo != nil {
    50  		return *b.Metadata.LevelInfo
    51  	}
    52  	if b.Metadata.Level != nil {
    53  		return *b.Metadata.Level
    54  	}
    55  	return LevelInfo{}
    56  }
    57  
    58  // only works for mainnet when before Edo or for all nets after Edo
    59  // due to fixed constants used
    60  func (b Block) GetVotingInfo() VotingPeriodInfo {
    61  	if b.Metadata.VotingPeriodInfo != nil {
    62  		return *b.Metadata.VotingPeriodInfo
    63  	}
    64  	if b.Metadata.Level != nil {
    65  		return VotingPeriodInfo{
    66  			Position:  b.Metadata.Level.VotingPeriodPosition,
    67  			Remaining: 32768 - b.Metadata.Level.VotingPeriodPosition,
    68  			VotingPeriod: VotingPeriod{
    69  				Index:         b.Metadata.Level.VotingPeriod,
    70  				Kind:          *b.Metadata.VotingPeriodKind,
    71  				StartPosition: b.Metadata.Level.VotingPeriod * 32768,
    72  			},
    73  		}
    74  	}
    75  	return VotingPeriodInfo{}
    76  }
    77  
    78  func (b Block) GetVotingPeriodKind() mavryk.VotingPeriodKind {
    79  	if b.Metadata.VotingPeriodInfo != nil {
    80  		return b.Metadata.VotingPeriodInfo.VotingPeriod.Kind
    81  	}
    82  	if b.Metadata.VotingPeriodKind != nil {
    83  		return *b.Metadata.VotingPeriodKind
    84  	}
    85  	return mavryk.VotingPeriodInvalid
    86  }
    87  
    88  func (b Block) GetVotingPeriod() int64 {
    89  	if b.Metadata.VotingPeriodInfo != nil {
    90  		return b.Metadata.VotingPeriodInfo.VotingPeriod.Index
    91  	}
    92  	if b.Metadata.Level != nil {
    93  		return b.Metadata.Level.VotingPeriod
    94  	}
    95  	return 0
    96  }
    97  
    98  func (b Block) IsProtocolUpgrade() bool {
    99  	return !b.Metadata.Protocol.Equal(b.Metadata.NextProtocol)
   100  }
   101  
   102  // InvalidBlock represents invalid block hash along with the errors that led to it being declared invalid
   103  type InvalidBlock struct {
   104  	Block mavryk.BlockHash `json:"block"`
   105  	Level int64            `json:"level"`
   106  	Error Errors           `json:"error"`
   107  }
   108  
   109  // BlockHeader is a part of the Tezos block data
   110  type BlockHeader struct {
   111  	Level                     int64                 `json:"level"`
   112  	Proto                     int                   `json:"proto"`
   113  	Predecessor               mavryk.BlockHash      `json:"predecessor"`
   114  	Timestamp                 time.Time             `json:"timestamp"`
   115  	ValidationPass            int                   `json:"validation_pass"`
   116  	OperationsHash            mavryk.OpListListHash `json:"operations_hash"`
   117  	Fitness                   []mavryk.HexBytes     `json:"fitness"`
   118  	Context                   mavryk.ContextHash    `json:"context"`
   119  	PayloadHash               mavryk.PayloadHash    `json:"payload_hash"`
   120  	PayloadRound              int                   `json:"payload_round"`
   121  	Priority                  int                   `json:"priority"`
   122  	ProofOfWorkNonce          mavryk.HexBytes       `json:"proof_of_work_nonce"`
   123  	SeedNonceHash             *mavryk.NonceHash     `json:"seed_nonce_hash"`
   124  	Signature                 mavryk.Signature      `json:"signature"`
   125  	Content                   *BlockContent         `json:"content,omitempty"`
   126  	LiquidityBakingEscapeVote bool                  `json:"liquidity_baking_escape_vote"`
   127  	LiquidityBakingToggleVote mavryk.FeatureVote    `json:"liquidity_baking_toggle_vote"`
   128  	AdaptiveIssuanceVote      mavryk.FeatureVote    `json:"adaptive_issuance_vote"`
   129  
   130  	// only present when header is fetched explicitly
   131  	Hash     mavryk.BlockHash    `json:"hash"`
   132  	Protocol mavryk.ProtocolHash `json:"protocol"`
   133  	ChainId  mavryk.ChainIdHash  `json:"chain_id"`
   134  }
   135  
   136  func (h BlockHeader) LbVote() mavryk.FeatureVote {
   137  	if h.LiquidityBakingToggleVote.IsValid() {
   138  		return h.LiquidityBakingToggleVote
   139  	}
   140  	// sic! bool flag has opposite meaning
   141  	if h.LiquidityBakingEscapeVote {
   142  		return mavryk.FeatureVoteOff
   143  	}
   144  	return mavryk.FeatureVoteOn
   145  }
   146  
   147  func (h BlockHeader) AiVote() mavryk.FeatureVote {
   148  	return h.AdaptiveIssuanceVote
   149  }
   150  
   151  // ProtocolData exports protocol-specific extra header fields as binary encoded data.
   152  // Used to produce compliant block monitor data streams.
   153  //
   154  // octez-codec describe 018-Proxford.block_header.protocol_data binary schema
   155  // +---------------------------------------+----------+-------------------------------------+
   156  // | Name                                  | Size     | Contents                            |
   157  // +=======================================+==========+=====================================+
   158  // | payload_hash                          | 32 bytes | bytes                               |
   159  // +---------------------------------------+----------+-------------------------------------+
   160  // | payload_round                         | 4 bytes  | signed 32-bit integer               |
   161  // +---------------------------------------+----------+-------------------------------------+
   162  // | proof_of_work_nonce                   | 8 bytes  | bytes                               |
   163  // +---------------------------------------+----------+-------------------------------------+
   164  // | ? presence of field "seed_nonce_hash" | 1 byte   | boolean (0 for false, 255 for true) |
   165  // +---------------------------------------+----------+-------------------------------------+
   166  // | seed_nonce_hash                       | 32 bytes | bytes                               |
   167  // +---------------------------------------+----------+-------------------------------------+
   168  // | per_block_votes                       | 1 byte   | signed 8-bit integer                |
   169  // +---------------------------------------+----------+-------------------------------------+
   170  // | signature                             | 64 bytes | bytes                               |
   171  // +---------------------------------------+----------+-------------------------------------+
   172  
   173  func (h BlockHeader) ProtocolData() []byte {
   174  	buf := bytes.NewBuffer(nil)
   175  	buf.Write(h.PayloadHash.Bytes())
   176  	binary.Write(buf, binary.BigEndian, uint32(h.PayloadRound))
   177  	buf.Write(h.ProofOfWorkNonce)
   178  	if h.SeedNonceHash != nil {
   179  		buf.WriteByte(0xff)
   180  		buf.Write(h.SeedNonceHash.Bytes())
   181  	} else {
   182  		buf.WriteByte(0x0)
   183  	}
   184  	// broken, how to merge multiple flags is undocumented
   185  	buf.WriteByte(h.LbVote().Tag() | (h.AiVote().Tag() << 2))
   186  	if h.Signature.IsValid() {
   187  		buf.Write(h.Signature.Data) // raw, no tag!
   188  	}
   189  	return buf.Bytes()
   190  }
   191  
   192  // BlockContent is part of block 1 header that seeds the initial context
   193  type BlockContent struct {
   194  	Command    string              `json:"command"`
   195  	Protocol   mavryk.ProtocolHash `json:"hash"`
   196  	Fitness    []mavryk.HexBytes   `json:"fitness"`
   197  	Parameters *GenesisData        `json:"protocol_parameters"`
   198  }
   199  
   200  // OperationListLength is a part of the BlockMetadata
   201  type OperationListLength struct {
   202  	MaxSize int `json:"max_size"`
   203  	MaxOp   int `json:"max_op"`
   204  }
   205  
   206  // BlockLevel is a part of BlockMetadata
   207  type LevelInfo struct {
   208  	Level              int64 `json:"level"`
   209  	LevelPosition      int64 `json:"level_position"`
   210  	Cycle              int64 `json:"cycle"`
   211  	CyclePosition      int64 `json:"cycle_position"`
   212  	ExpectedCommitment bool  `json:"expected_commitment"`
   213  
   214  	// <v008
   215  	VotingPeriod         int64 `json:"voting_period"`
   216  	VotingPeriodPosition int64 `json:"voting_period_position"`
   217  }
   218  
   219  type VotingPeriod struct {
   220  	Index         int64                   `json:"index"`
   221  	Kind          mavryk.VotingPeriodKind `json:"kind"`
   222  	StartPosition int64                   `json:"start_position"`
   223  }
   224  
   225  type VotingPeriodInfo struct {
   226  	Position     int64        `json:"position"`
   227  	Remaining    int64        `json:"remaining"`
   228  	VotingPeriod VotingPeriod `json:"voting_period"`
   229  }
   230  
   231  // BlockMetadata is a part of the Tezos block data
   232  type BlockMetadata struct {
   233  	Protocol               mavryk.ProtocolHash    `json:"protocol"`
   234  	NextProtocol           mavryk.ProtocolHash    `json:"next_protocol"`
   235  	MaxOperationsTTL       int                    `json:"max_operations_ttl"`
   236  	MaxOperationDataLength int                    `json:"max_operation_data_length"`
   237  	MaxBlockHeaderLength   int                    `json:"max_block_header_length"`
   238  	MaxOperationListLength []*OperationListLength `json:"max_operation_list_length"`
   239  	Baker                  mavryk.Address         `json:"baker"`
   240  	Proposer               mavryk.Address         `json:"proposer"`
   241  	NonceHash              mavryk.NonceHash       `json:"nonce_hash"`
   242  	ConsumedGas            int64                  `json:"consumed_gas,string"`
   243  	Deactivated            []mavryk.Address       `json:"deactivated"`
   244  	BalanceUpdates         BalanceUpdates         `json:"balance_updates"`
   245  
   246  	// <v008
   247  	Level            *LevelInfo               `json:"level"`
   248  	VotingPeriodKind *mavryk.VotingPeriodKind `json:"voting_period_kind"`
   249  
   250  	// v008+
   251  	LevelInfo        *LevelInfo        `json:"level_info"`
   252  	VotingPeriodInfo *VotingPeriodInfo `json:"voting_period_info"`
   253  
   254  	// v010+
   255  	ImplicitOperationsResults []ImplicitResult `json:"implicit_operations_results"`
   256  	LiquidityBakingEscapeEma  int64            `json:"liquidity_baking_escape_ema"`
   257  
   258  	// v015+
   259  	ProposerConsensusKey mavryk.Address `json:"proposer_consensus_key"`
   260  	BakerConsensusKey    mavryk.Address `json:"baker_consensus_key"`
   261  
   262  	// v019+
   263  	DalAttestation mavryk.Z `json:"dal_attestation"`
   264  }
   265  
   266  func (m *BlockMetadata) GetLevel() int64 {
   267  	if m.LevelInfo != nil {
   268  		return m.LevelInfo.Level
   269  	}
   270  	if m.Level == nil {
   271  		return 0
   272  	}
   273  	return m.Level.Level
   274  }
   275  
   276  // GetBlock returns information about a Tezos block
   277  // https://protocol.mavryk.org/mainnet/api/rpc.html#get-block-id
   278  func (c *Client) GetBlock(ctx context.Context, id BlockID) (*Block, error) {
   279  	var block Block
   280  	u := fmt.Sprintf("chains/main/blocks/%s", id)
   281  	if c.MetadataMode != "" {
   282  		u += "?metadata=" + string(c.MetadataMode)
   283  	}
   284  	if err := c.Get(ctx, u, &block); err != nil {
   285  		return nil, err
   286  	}
   287  	return &block, nil
   288  }
   289  
   290  // GetBlockHeight returns information about a Tezos block
   291  // https://protocol.mavryk.org/mainnet/api/rpc.html#get-block-id
   292  func (c *Client) GetBlockHeight(ctx context.Context, height int64) (*Block, error) {
   293  	return c.GetBlock(ctx, BlockLevel(height))
   294  }
   295  
   296  // GetTips returns hashes of the current chain tip blocks, first in the array is the
   297  // current main chain.
   298  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   299  func (c *Client) GetTips(ctx context.Context, depth int, head mavryk.BlockHash) ([][]mavryk.BlockHash, error) {
   300  	if depth == 0 {
   301  		depth = 1
   302  	}
   303  	tips := make([][]mavryk.BlockHash, 0, 10)
   304  	var u string
   305  	if head.IsValid() {
   306  		u = fmt.Sprintf("chains/main/blocks?length=%d&head=%s", depth, head)
   307  	} else {
   308  		u = fmt.Sprintf("chains/main/blocks?length=%d", depth)
   309  	}
   310  	if err := c.Get(ctx, u, &tips); err != nil {
   311  		return nil, err
   312  	}
   313  	return tips, nil
   314  }
   315  
   316  // GetHeadBlock returns the chain's head block.
   317  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   318  func (c *Client) GetHeadBlock(ctx context.Context) (*Block, error) {
   319  	return c.GetBlock(ctx, Head)
   320  }
   321  
   322  // GetGenesisBlock returns main chain genesis block.
   323  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   324  func (c *Client) GetGenesisBlock(ctx context.Context) (*Block, error) {
   325  	return c.GetBlock(ctx, Genesis)
   326  }
   327  
   328  // GetTipHeader returns the head block's header.
   329  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   330  func (c *Client) GetTipHeader(ctx context.Context) (*BlockHeader, error) {
   331  	var head BlockHeader
   332  	u := "chains/main/blocks/head/header"
   333  	if err := c.Get(ctx, u, &head); err != nil {
   334  		return nil, err
   335  	}
   336  	return &head, nil
   337  }
   338  
   339  // GetBlockHeader returns a block header.
   340  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   341  func (c *Client) GetBlockHeader(ctx context.Context, id BlockID) (*BlockHeader, error) {
   342  	var head BlockHeader
   343  	u := fmt.Sprintf("chains/main/blocks/%s/header", id)
   344  	if err := c.Get(ctx, u, &head); err != nil {
   345  		return nil, err
   346  	}
   347  	return &head, nil
   348  }
   349  
   350  // GetBlockMetadata returns a block metadata.
   351  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   352  func (c *Client) GetBlockMetadata(ctx context.Context, id BlockID) (*BlockMetadata, error) {
   353  	var meta BlockMetadata
   354  	u := fmt.Sprintf("chains/main/blocks/%s/metadata", id)
   355  	if c.MetadataMode != "" {
   356  		u += "?metadata=" + string(c.MetadataMode)
   357  	}
   358  	if err := c.Get(ctx, u, &meta); err != nil {
   359  		return nil, err
   360  	}
   361  	return &meta, nil
   362  }
   363  
   364  // GetBlockHash returns the main chain's block header.
   365  // https://protocol.mavryk.org/mainnet/api/rpc.html#chains-chain-id-blocks
   366  func (c *Client) GetBlockHash(ctx context.Context, id BlockID) (hash mavryk.BlockHash, err error) {
   367  	u := fmt.Sprintf("chains/main/blocks/%s/hash", id)
   368  	err = c.Get(ctx, u, &hash)
   369  	return
   370  }
   371  
   372  // GetBlockPredHashes returns count parent blocks before block with given hash.
   373  // https://protocol.mavryk.org/mainnet/api/rpc.html#get-chains-chain-id-blocks
   374  func (c *Client) GetBlockPredHashes(ctx context.Context, hash mavryk.BlockHash, count int) ([]mavryk.BlockHash, error) {
   375  	if count <= 0 {
   376  		count = 1
   377  	}
   378  	blockIds := make([][]mavryk.BlockHash, 0, count)
   379  	u := fmt.Sprintf("chains/main/blocks?length=%d&head=%s", count, hash)
   380  	if err := c.Get(ctx, u, &blockIds); err != nil {
   381  		return nil, err
   382  	}
   383  	return blockIds[0], nil
   384  }
   385  
   386  // GetInvalidBlocks lists blocks that have been declared invalid along with the errors that led to them being declared invalid.
   387  // https://protocol.mavryk.org/mainnet/api/rpc.html#get-chains-chain-id-invalid-blocks
   388  func (c *Client) GetInvalidBlocks(ctx context.Context) ([]*InvalidBlock, error) {
   389  	var invalidBlocks []*InvalidBlock
   390  	if err := c.Get(ctx, "chains/main/invalid_blocks", &invalidBlocks); err != nil {
   391  		return nil, err
   392  	}
   393  	return invalidBlocks, nil
   394  }
   395  
   396  // GetInvalidBlock returns a single invalid block with the errors that led to it being declared invalid.
   397  // https://protocol.mavryk.org/mainnet/api/rpc.html#get-chains-chain-id-invalid-blocks-block-hash
   398  func (c *Client) GetInvalidBlock(ctx context.Context, blockID mavryk.BlockHash) (*InvalidBlock, error) {
   399  	var invalidBlock InvalidBlock
   400  	u := fmt.Sprintf("chains/main/invalid_blocks/%s", blockID)
   401  	if err := c.Get(ctx, u, &invalidBlock); err != nil {
   402  		return nil, err
   403  	}
   404  	return &invalidBlock, nil
   405  }