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 }