Skip to content

Commit d242a0a

Browse files
authored
feat: add block id, transaction id, certificate hash, and encode refactor (#60)
1 parent 4a55842 commit d242a0a

5 files changed

Lines changed: 81 additions & 56 deletions

File tree

pactus/block/block.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,7 @@ def id(self) -> bytes:
5353
return hashlib.blake2b(buf, digest_size=32).digest()
5454

5555
def _header_bytes(self) -> bytes:
56-
h = self.header
57-
buf = encoding.append_uint8(b"", h.version)
58-
buf = encoding.append_uint32(buf, h.unix_time)
59-
buf = h.prev_block_hash.encode(buf)
60-
buf = h.state_root.encode(buf)
61-
buf = encoding.append_fixed_bytes(buf, h.sortition_seed)
62-
return h.proposer_address.encode(buf)
56+
return self.header.encode(b"")
6357

6458
def _txs_root(self) -> bytes:
6559
return Block._merkle_root([tx.id() for tx in self.transactions])

pactus/block/certificate.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,17 @@ def decode(cls, buf: bytes) -> tuple:
4747
cert = cls(height, round_, committers, absentees, signature)
4848
return cert, buf
4949

50-
def hash(self) -> bytes:
51-
"""Return the certificate hash (blake2b-256 of encoded bytes)."""
52-
buf = self.height.encode(b"")
50+
def encode(self, buf: bytes) -> bytes:
51+
buf = self.height.encode(buf)
5352
buf = self.round.encode(buf)
5453
buf = encoding.append_var_int(buf, len(self.committers))
5554
for n in self.committers:
5655
buf = encoding.append_var_int(buf, n)
5756
buf = encoding.append_var_int(buf, len(self.absentees))
5857
for n in self.absentees:
5958
buf = encoding.append_var_int(buf, n)
60-
buf = self.signature.encode(buf)
61-
return hashlib.blake2b(buf, digest_size=32).digest()
59+
return self.signature.encode(buf)
60+
61+
def hash(self) -> bytes:
62+
"""Return the certificate hash (blake2b-256 of encoded bytes)."""
63+
return hashlib.blake2b(self.encode(b""), digest_size=32).digest()

pactus/block/header.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,11 @@ def decode(cls, buf: bytes) -> tuple:
4242
proposer_address,
4343
)
4444
return header, buf
45+
46+
def encode(self, buf: bytes) -> bytes:
47+
buf = encoding.append_uint8(buf, self.version)
48+
buf = encoding.append_uint32(buf, self.unix_time)
49+
buf = self.prev_block_hash.encode(buf)
50+
buf = self.state_root.encode(buf)
51+
buf = encoding.append_fixed_bytes(buf, self.sortition_seed)
52+
return self.proposer_address.encode(buf)

pactus/crypto/hash.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def encode(self, buf: bytes) -> bytes:
3232
return encoding.append_fixed_bytes(buf, self.data)
3333

3434
@classmethod
35-
def decode(cls, buf: bytes) -> tuple[Hash, bytes]:
35+
def decode(cls, buf: bytes) -> tuple:
3636
"""
3737
Decode a Hash from bytes.
3838
Returns (Hash, remaining_buf).

tests/test_block.py

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,31 @@
66

77

88
class TestBlockDecode(unittest.TestCase):
9-
def test_decode_block_from_raw(self):
10-
# Block number of 888
11-
# https://pactusscan.com/block/888
9+
def test_decode_block_88888(self):
1210
raw_hex = (
13-
"019094b165"
14-
"84fdb8aac442735c6d4d4457e2236cd6ca86cb0b898ab37a03be1723b8faac55"
15-
"d24bf4f116090735a366d1818ec48f5d91b4ff1deab091ecef5fc8bbae94cdd5"
16-
"a50eac535372c874d7150a60cb8a988dde1a60b7ebda1729cc14f6d4d2575ed4"
17-
"b72089c36ffe9f7ecc60d8be09912feb"
18-
"010c4cbdcae2ba23a3335f994eaaf104a4f53981a2"
19-
"770300000000040001020300"
20-
"8da0b181eaa433b5c9855a89de0f0c7530ec4d2c3aecff290af7ae02b33dd968"
21-
"43d6f72983bf25ce856946df1360710f"
22-
"0102017803000000000100"
23-
"020c8ce957b484397ab248c25ce135e2d0aff0c8e48094ebdc03"
11+
"01d802bf65f805b91ce08e5ee84b4707248fd602db5364da6ba9b273a200228ce9c62be4ac"
12+
"a846b739df34099e5345088e66ded3f63762cc766c5380c58f788fb134c9467c"
13+
"b366e61d7dc9f9a63d0b4ce69aa1cfc91cc11436eb43220b4db00ac39b8056e47f6ee34f1846"
14+
"83626ba43a0f85d7d82b01468a4e63084ff773a7750a52872c665b19b1174c375b"
15+
"010000003324ee0365e004a605ab079403c305910780089406379203b0077622d6049201bd07e"
16+
"903fb07bf072cc806c8053fc8014a0f81018901a701870835b301cb05850126b101b00120e2"
17+
"051d5dae05c506d405a10534b502df0400"
18+
"8a95d277dd80203ca7b964711f819bdd19b9e028f46d3416c43b1f3f28f746936b9c309960b9"
19+
"6e01fceb4917e6bd95a3"
20+
"050201385b01000000010002f9682245d323f7c4b72131cd6d0fac6f1cfc6783a0d689dd0301"
21+
"01375b010000000301a2aa63337fad6bac1936dc563ada569c7a12e2aa93bf4a3be75443b0c7"
22+
"8e2e342984bb89c55542cb0d1f09c4ec9ebae998f9c42f4d7385261793325fe1cbdd3141c07d"
23+
"288252f8c291497f04e572d56bcc7997d19f634880d4c9f238e0791f062e5bc64af160b4c976"
24+
"6c2c2f3cae588314909fe40101375b01000000030127179f1ae3a28030972f30b0a848053a2c"
25+
"752bedb4bab53872a3d52e40bc04de319b0a92a70f49983379f113502c2e0e34f7321c9b31b4"
26+
"eb8ce63c623e468e4ba401e6daadd19f9d254e0894b82f50fef40d1b4ad969b26894e43237c6"
27+
"8b527571418cee783b5821e5f96234a8dcc53fea17266b0101375b01000000030180e9161eaa"
28+
"d5285558caa382f1eec2d2642c13eeae3698cdedf32d066e1946b2d92881c0378aaa8155ad3b"
29+
"52de4eeee2d106b4d0ef0bb69f0d6b2e6e8ce573652553bf0eadd458f64081f3c0bd3cec9566"
30+
"1188a004a5f2ce81d40007238639a2babe6ba4347616b848098094e1c0c4f396196478010137"
31+
"5b0100a0c21e000202b9abf7f9a0406e4a9f7262b3d3d23d449d161cad0165d2d2c74fb4cc35"
32+
"09cdd646f4eeefacb1a9cf720080e497d0128c7d2655bfd876417fb7b18efd83374bd9cacebb"
33+
"990453ba153defd4c41fb97bcc074552307f6c7194aeeb6e10588358"
2434
)
2535

2636
raw = bytes.fromhex(raw_hex)
@@ -29,70 +39,81 @@ def test_decode_block_from_raw(self):
2939
# --- Header ---
3040
h = block.header
3141
self.assertEqual(h.version, 1)
32-
self.assertEqual(h.unix_time, 1706136720)
42+
self.assertEqual(h.unix_time, 1707016920)
3343
self.assertEqual(
3444
str(h.prev_block_hash),
35-
"84fdb8aac442735c6d4d4457e2236cd6ca86cb0b898ab37a03be1723b8faac55",
45+
"f805b91ce08e5ee84b4707248fd602db5364da6ba9b273a200228ce9c62be4ac",
3646
)
3747
self.assertEqual(
3848
str(h.state_root),
39-
"d24bf4f116090735a366d1818ec48f5d91b4ff1deab091ecef5fc8bbae94cdd5",
40-
)
41-
self.assertEqual(
42-
h.sortition_seed.hex(),
43-
"a50eac535372c874d7150a60cb8a988dde1a60b7ebda1729cc14f6d4d2575ed4"
44-
"b72089c36ffe9f7ecc60d8be09912feb",
49+
"a846b739df34099e5345088e66ded3f63762cc766c5380c58f788fb134c9467c",
4550
)
4651
self.assertEqual(h.proposer_address.address_type(), AddressType.VALIDATOR)
47-
self.assertEqual(
48-
h.proposer_address.raw_bytes().hex(),
49-
"010c4cbdcae2ba23a3335f994eaaf104a4f53981a2",
50-
)
5152
self.assertEqual(
5253
h.proposer_address.string(),
53-
"pc1pp3xtmjhzhg36xv6ln9824ugy5n6nnqdzugzu3j",
54+
"pc1pg69yuccgflmh8fm4pffgwtrxtvvmz96vzjuvev",
5455
)
5556

5657
# --- Certificate ---
5758
cert = block.prev_cert
5859
self.assertIsNotNone(cert)
59-
self.assertEqual(cert.height.value, 887)
60+
self.assertEqual(cert.height.value, 88887)
6061
self.assertEqual(cert.round.value, 0)
61-
self.assertEqual(cert.committers, [0, 1, 2, 3])
62+
self.assertEqual(len(cert.committers), 51)
6263
self.assertEqual(cert.absentees, [])
6364
self.assertEqual(
64-
cert.signature.string(),
65-
"8da0b181eaa433b5c9855a89de0f0c7530ec4d2c3aecff290af7ae02b33dd968"
66-
"43d6f72983bf25ce856946df1360710f",
65+
cert.hash().hex(),
66+
"14aa7049254af312d2f8c5515989271a709a758e552801fe34ac68efa11581ef",
6767
)
6868

6969
# --- Block ID ---
7070
self.assertEqual(
7171
block.id.hex(),
72-
"3fad130499227f7a83d4e65873b0f880db275a6c4d1b9dd65005c1f1667ec98f",
72+
"5a12881b1d5fd9bea3a61f24d7555d8900e85d02e9606b5a3176edf3c355a32d",
7373
)
7474

7575
# --- Transactions ---
76-
self.assertEqual(len(block.transactions), 1)
76+
self.assertEqual(len(block.transactions), 5)
7777

78+
# Tx 0: Subsidy (transfer from treasury)
7879
tx = block.transactions[0]
7980
self.assertEqual(tx.version, 1)
80-
self.assertEqual(tx.flags, 0x02) # FLAG_NOT_SIGNED
81-
self.assertEqual(tx.lock_time.value, 888)
81+
self.assertEqual(tx.flags, 0x02)
82+
self.assertEqual(tx.lock_time.value, 88888)
8283
self.assertEqual(tx.fee.value, 0)
83-
self.assertEqual(tx.memo, "")
8484
self.assertEqual(tx.payload.get_type(), PayloadType.TRANSFER)
85-
86-
# Subsidy transaction: signer is treasury address
8785
self.assertTrue(tx.payload.signer().is_treasury_address())
88-
self.assertEqual(tx.signature, None)
89-
self.assertEqual(tx.public_key, None)
86+
self.assertIsNone(tx.signature)
87+
self.assertIsNone(tx.public_key)
88+
self.assertEqual(tx.payload.amount.value, 1_000_500_000)
89+
self.assertEqual(
90+
tx.payload.receiver.string(),
91+
"pc1zl95zy3wny0mufdepx8xk6ravduw0ceurudtqqq",
92+
)
93+
94+
# Tx 1-3: Sortition
95+
for i in range(1, 4):
96+
tx = block.transactions[i]
97+
self.assertEqual(tx.flags, 0x01)
98+
self.assertEqual(tx.lock_time.value, 88887)
99+
self.assertEqual(tx.fee.value, 0)
100+
self.assertEqual(tx.payload.get_type(), PayloadType.SORTITION)
101+
self.assertIsNotNone(tx.signature)
102+
self.assertIsNone(tx.public_key) # stripped
90103

91-
self.assertEqual(tx.payload.amount.value, 1_000_000_000)
104+
# Tx 4: Bond
105+
tx = block.transactions[4]
106+
self.assertEqual(tx.flags, 0x01)
107+
self.assertEqual(tx.lock_time.value, 88887)
108+
self.assertEqual(tx.fee.value, 500_000)
109+
self.assertEqual(tx.payload.get_type(), PayloadType.BOND)
110+
self.assertEqual(tx.payload.stake.value, 5_000_000_000)
92111
self.assertEqual(
93112
tx.payload.receiver.string(),
94-
"pc1zpjxwj4a5ssuh4vjgcfwwzd0z6zhlpj8ylnhdl8",
113+
"pc1pvhfd9360knxr2zwd6er0fmh04jc6nnmjulhkcp",
95114
)
115+
self.assertIsNotNone(tx.signature)
116+
self.assertIsNone(tx.public_key) # stripped
96117

97118

98119
if __name__ == "__main__":

0 commit comments

Comments
 (0)