Coverage for ledger/block_utils.py: 78%

41 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-07-10 13:43 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2021 RSK Labs Ltd 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy of 

6# this software and associated documentation files (the "Software"), to deal in 

7# the Software without restriction, including without limitation the rights to 

8# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 

9# of the Software, and to permit persons to whom the Software is furnished to do 

10# so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in all 

13# copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

21# SOFTWARE. 

22 

23import rlp 

24from comm.utils import keccak_256 

25 

26 

27# Compute the given block's top-level RLP encoding list payload length in bytes, 

28# but excluding all merge mining fields. 

29# 

30# This is used by the ledger to compute the merge-mining 

31# hash of a block to be validated without having to transmit 

32# the block information twice (or having to save the block information 

33# elsewhere -- which wouldn't be possible given the lack of memory). 

34def rlp_mm_payload_size(raw_block_hex): 

35 return rlp_first_element_list_payload_length( 

36 remove_mm_fields_if_present(raw_block_hex, leave_btcblock=False, hex=False) 

37 ) 

38 

39 

40# Exclude a given block's merge mining fields (either leaving or not the 

41# BTC merge mining header, depending on parameter). 

42# That is, parse the header, exclude the last one (none) or three (two) fields 

43# (depending on which mm fields it originally includes) and re-encode it 

44def remove_mm_fields_if_present(raw_block_hex, leave_btcblock=True, hex=True): 

45 # Decode 

46 try: 

47 block = rlp.decode(bytes.fromhex(raw_block_hex)) 

48 except Exception as e: 

49 raise ValueError(e) 

50 # Sanity validation: list length (w/wo/umm_root and/or mm fields) 

51 num_fields = len(block) 

52 if num_fields not in [17, 18, 19, 20]: 

53 raise ValueError( 

54 "Block header must have 17, 18, 19 or 20 elements, got %d", num_fields 

55 ) 

56 

57 # Exclude merge mining fields and re-encode 

58 if num_fields in [19, 20]: 

59 block_without_mm_fields = block[:-2] if leave_btcblock else block[:-3] 

60 else: 

61 block_without_mm_fields = block if leave_btcblock else block[:-1] 

62 

63 block_without_mm_fields_rlp = rlp.encode(block_without_mm_fields) 

64 

65 if not hex: 

66 return block_without_mm_fields_rlp 

67 

68 return block_without_mm_fields_rlp.hex() 

69 

70 

71# Given a raw block hex, compute its block hash 

72# and return it as a hex string 

73def get_block_hash(raw_block_hex): 

74 return keccak_256(remove_mm_fields_if_present( 

75 raw_block_hex, 

76 leave_btcblock=True, 

77 hex=False)).hex() 

78 

79 

80# Given a raw block hex, 

81# extract the coinbase transaction (last field) 

82def get_coinbase_txn(raw_block_hex): 

83 # Decode 

84 try: 

85 block = rlp.decode(bytes.fromhex(raw_block_hex)) 

86 except Exception as e: 

87 raise ValueError(e) 

88 # Sanity validation: list length (w/wo/umm_root) 

89 num_fields = len(block) 

90 if num_fields not in [19, 20]: 

91 raise ValueError("Block header must have 19 or 20 elements, got %d", num_fields) 

92 return block[-1].hex() 

93 

94 

95# Given a bytes object that represents an RLP-encoded list, 

96# compute the top level list's payload length. 

97def rlp_first_element_list_payload_length(bs): 

98 # Infer length L of payload of the first element of the given RLP-encoded bytes 

99 # First element *MUST* be a list. Validate that. 

100 

101 # Encoding corresponds to a list, so first byte b will be one of: 

102 # 1. anything in the range 0xc0..0xf7: L = b - 0xc0 

103 # 2. anything in the range 0xf8..0xff: 

104 # N = b - 0xf7 

105 # length follows, encoded as a bigendian integer of length N 

106 b = bs[0] 

107 if b >= 0xC0 and b <= 0xF7: 

108 L = b - 0xC0 

109 elif b >= 0xF8 and b <= 0xFF: 

110 N = b - 0xF7 

111 L = 0 

112 for i in range(N): 

113 L = (L << 8) | bs[1 + i] 

114 else: 

115 raise ValueError("Invalid RLP encoded list - got %s as first byte" % hex(b)) 

116 return L