Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions pactus/block/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,7 @@ def id(self) -> bytes:
return hashlib.blake2b(buf, digest_size=32).digest()

def _header_bytes(self) -> bytes:
h = self.header
buf = encoding.append_uint8(b"", h.version)
buf = encoding.append_uint32(buf, h.unix_time)
buf = h.prev_block_hash.encode(buf)
buf = h.state_root.encode(buf)
buf = encoding.append_fixed_bytes(buf, h.sortition_seed)
return h.proposer_address.encode(buf)
return self.header.encode(b"")

def _txs_root(self) -> bytes:
return Block._merkle_root([tx.id() for tx in self.transactions])
Expand Down
12 changes: 7 additions & 5 deletions pactus/block/certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ def decode(cls, buf: bytes) -> tuple:
cert = cls(height, round_, committers, absentees, signature)
return cert, buf

def hash(self) -> bytes:
"""Return the certificate hash (blake2b-256 of encoded bytes)."""
buf = self.height.encode(b"")
def encode(self, buf: bytes) -> bytes:
buf = self.height.encode(buf)
buf = self.round.encode(buf)
buf = encoding.append_var_int(buf, len(self.committers))
for n in self.committers:
buf = encoding.append_var_int(buf, n)
buf = encoding.append_var_int(buf, len(self.absentees))
for n in self.absentees:
buf = encoding.append_var_int(buf, n)
buf = self.signature.encode(buf)
return hashlib.blake2b(buf, digest_size=32).digest()
return self.signature.encode(buf)

def hash(self) -> bytes:
"""Return the certificate hash (blake2b-256 of encoded bytes)."""
return hashlib.blake2b(self.encode(b""), digest_size=32).digest()
8 changes: 8 additions & 0 deletions pactus/block/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ def decode(cls, buf: bytes) -> tuple:
proposer_address,
)
return header, buf

def encode(self, buf: bytes) -> bytes:
buf = encoding.append_uint8(buf, self.version)
buf = encoding.append_uint32(buf, self.unix_time)
buf = self.prev_block_hash.encode(buf)
buf = self.state_root.encode(buf)
buf = encoding.append_fixed_bytes(buf, self.sortition_seed)
return self.proposer_address.encode(buf)
2 changes: 1 addition & 1 deletion pactus/crypto/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def encode(self, buf: bytes) -> bytes:
return encoding.append_fixed_bytes(buf, self.data)

@classmethod
def decode(cls, buf: bytes) -> tuple[Hash, bytes]:
def decode(cls, buf: bytes) -> tuple:
"""
Decode a Hash from bytes.
Returns (Hash, remaining_buf).
Expand Down
107 changes: 64 additions & 43 deletions tests/test_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,31 @@


class TestBlockDecode(unittest.TestCase):
def test_decode_block_from_raw(self):
# Block number of 888
# https://pactusscan.com/block/888
def test_decode_block_88888(self):
raw_hex = (
"019094b165"
"84fdb8aac442735c6d4d4457e2236cd6ca86cb0b898ab37a03be1723b8faac55"
"d24bf4f116090735a366d1818ec48f5d91b4ff1deab091ecef5fc8bbae94cdd5"
"a50eac535372c874d7150a60cb8a988dde1a60b7ebda1729cc14f6d4d2575ed4"
"b72089c36ffe9f7ecc60d8be09912feb"
"010c4cbdcae2ba23a3335f994eaaf104a4f53981a2"
"770300000000040001020300"
"8da0b181eaa433b5c9855a89de0f0c7530ec4d2c3aecff290af7ae02b33dd968"
"43d6f72983bf25ce856946df1360710f"
"0102017803000000000100"
"020c8ce957b484397ab248c25ce135e2d0aff0c8e48094ebdc03"
"01d802bf65f805b91ce08e5ee84b4707248fd602db5364da6ba9b273a200228ce9c62be4ac"
"a846b739df34099e5345088e66ded3f63762cc766c5380c58f788fb134c9467c"
"b366e61d7dc9f9a63d0b4ce69aa1cfc91cc11436eb43220b4db00ac39b8056e47f6ee34f1846"
"83626ba43a0f85d7d82b01468a4e63084ff773a7750a52872c665b19b1174c375b"
"010000003324ee0365e004a605ab079403c305910780089406379203b0077622d6049201bd07e"
"903fb07bf072cc806c8053fc8014a0f81018901a701870835b301cb05850126b101b00120e2"
"051d5dae05c506d405a10534b502df0400"
"8a95d277dd80203ca7b964711f819bdd19b9e028f46d3416c43b1f3f28f746936b9c309960b9"
"6e01fceb4917e6bd95a3"
"050201385b01000000010002f9682245d323f7c4b72131cd6d0fac6f1cfc6783a0d689dd0301"
"01375b010000000301a2aa63337fad6bac1936dc563ada569c7a12e2aa93bf4a3be75443b0c7"
"8e2e342984bb89c55542cb0d1f09c4ec9ebae998f9c42f4d7385261793325fe1cbdd3141c07d"
"288252f8c291497f04e572d56bcc7997d19f634880d4c9f238e0791f062e5bc64af160b4c976"
"6c2c2f3cae588314909fe40101375b01000000030127179f1ae3a28030972f30b0a848053a2c"
"752bedb4bab53872a3d52e40bc04de319b0a92a70f49983379f113502c2e0e34f7321c9b31b4"
"eb8ce63c623e468e4ba401e6daadd19f9d254e0894b82f50fef40d1b4ad969b26894e43237c6"
"8b527571418cee783b5821e5f96234a8dcc53fea17266b0101375b01000000030180e9161eaa"
"d5285558caa382f1eec2d2642c13eeae3698cdedf32d066e1946b2d92881c0378aaa8155ad3b"
"52de4eeee2d106b4d0ef0bb69f0d6b2e6e8ce573652553bf0eadd458f64081f3c0bd3cec9566"
"1188a004a5f2ce81d40007238639a2babe6ba4347616b848098094e1c0c4f396196478010137"
"5b0100a0c21e000202b9abf7f9a0406e4a9f7262b3d3d23d449d161cad0165d2d2c74fb4cc35"
"09cdd646f4eeefacb1a9cf720080e497d0128c7d2655bfd876417fb7b18efd83374bd9cacebb"
"990453ba153defd4c41fb97bcc074552307f6c7194aeeb6e10588358"
)

raw = bytes.fromhex(raw_hex)
Expand All @@ -29,70 +39,81 @@ def test_decode_block_from_raw(self):
# --- Header ---
h = block.header
self.assertEqual(h.version, 1)
self.assertEqual(h.unix_time, 1706136720)
self.assertEqual(h.unix_time, 1707016920)
self.assertEqual(
str(h.prev_block_hash),
"84fdb8aac442735c6d4d4457e2236cd6ca86cb0b898ab37a03be1723b8faac55",
"f805b91ce08e5ee84b4707248fd602db5364da6ba9b273a200228ce9c62be4ac",
)
self.assertEqual(
str(h.state_root),
"d24bf4f116090735a366d1818ec48f5d91b4ff1deab091ecef5fc8bbae94cdd5",
)
self.assertEqual(
h.sortition_seed.hex(),
"a50eac535372c874d7150a60cb8a988dde1a60b7ebda1729cc14f6d4d2575ed4"
"b72089c36ffe9f7ecc60d8be09912feb",
"a846b739df34099e5345088e66ded3f63762cc766c5380c58f788fb134c9467c",
)
self.assertEqual(h.proposer_address.address_type(), AddressType.VALIDATOR)
self.assertEqual(
h.proposer_address.raw_bytes().hex(),
"010c4cbdcae2ba23a3335f994eaaf104a4f53981a2",
)
self.assertEqual(
h.proposer_address.string(),
"pc1pp3xtmjhzhg36xv6ln9824ugy5n6nnqdzugzu3j",
"pc1pg69yuccgflmh8fm4pffgwtrxtvvmz96vzjuvev",
)

# --- Certificate ---
cert = block.prev_cert
self.assertIsNotNone(cert)
self.assertEqual(cert.height.value, 887)
self.assertEqual(cert.height.value, 88887)
self.assertEqual(cert.round.value, 0)
self.assertEqual(cert.committers, [0, 1, 2, 3])
self.assertEqual(len(cert.committers), 51)
self.assertEqual(cert.absentees, [])
self.assertEqual(
cert.signature.string(),
"8da0b181eaa433b5c9855a89de0f0c7530ec4d2c3aecff290af7ae02b33dd968"
"43d6f72983bf25ce856946df1360710f",
cert.hash().hex(),
"14aa7049254af312d2f8c5515989271a709a758e552801fe34ac68efa11581ef",
)

# --- Block ID ---
self.assertEqual(
block.id.hex(),
"3fad130499227f7a83d4e65873b0f880db275a6c4d1b9dd65005c1f1667ec98f",
"5a12881b1d5fd9bea3a61f24d7555d8900e85d02e9606b5a3176edf3c355a32d",
)

# --- Transactions ---
self.assertEqual(len(block.transactions), 1)
self.assertEqual(len(block.transactions), 5)

# Tx 0: Subsidy (transfer from treasury)
tx = block.transactions[0]
self.assertEqual(tx.version, 1)
self.assertEqual(tx.flags, 0x02) # FLAG_NOT_SIGNED
self.assertEqual(tx.lock_time.value, 888)
self.assertEqual(tx.flags, 0x02)
self.assertEqual(tx.lock_time.value, 88888)
self.assertEqual(tx.fee.value, 0)
self.assertEqual(tx.memo, "")
self.assertEqual(tx.payload.get_type(), PayloadType.TRANSFER)

# Subsidy transaction: signer is treasury address
self.assertTrue(tx.payload.signer().is_treasury_address())
self.assertEqual(tx.signature, None)
self.assertEqual(tx.public_key, None)
self.assertIsNone(tx.signature)
self.assertIsNone(tx.public_key)
self.assertEqual(tx.payload.amount.value, 1_000_500_000)
self.assertEqual(
tx.payload.receiver.string(),
"pc1zl95zy3wny0mufdepx8xk6ravduw0ceurudtqqq",
)

# Tx 1-3: Sortition
for i in range(1, 4):
tx = block.transactions[i]
self.assertEqual(tx.flags, 0x01)
self.assertEqual(tx.lock_time.value, 88887)
self.assertEqual(tx.fee.value, 0)
self.assertEqual(tx.payload.get_type(), PayloadType.SORTITION)
self.assertIsNotNone(tx.signature)
self.assertIsNone(tx.public_key) # stripped

self.assertEqual(tx.payload.amount.value, 1_000_000_000)
# Tx 4: Bond
tx = block.transactions[4]
self.assertEqual(tx.flags, 0x01)
self.assertEqual(tx.lock_time.value, 88887)
self.assertEqual(tx.fee.value, 500_000)
self.assertEqual(tx.payload.get_type(), PayloadType.BOND)
self.assertEqual(tx.payload.stake.value, 5_000_000_000)
self.assertEqual(
tx.payload.receiver.string(),
"pc1zpjxwj4a5ssuh4vjgcfwwzd0z6zhlpj8ylnhdl8",
"pc1pvhfd9360knxr2zwd6er0fmh04jc6nnmjulhkcp",
)
self.assertIsNotNone(tx.signature)
self.assertIsNone(tx.public_key) # stripped


if __name__ == "__main__":
Expand Down
Loading