Skip to content

Commit 949eb0b

Browse files
authored
feat: implement block decoding and refactor encoding layer (#59)
1 parent a681d4a commit 949eb0b

31 files changed

Lines changed: 869 additions & 133 deletions

pactus/block/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .block import Block
2+
from .certificate import Certificate
3+
from .header import Header
4+
5+
__all__ = ["Block", "Certificate", "Header"]

pactus/block/block.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from __future__ import annotations
2+
3+
from pactus.encoding import encoding
4+
from pactus.transaction import Transaction
5+
6+
from .certificate import Certificate
7+
from .header import Header
8+
9+
10+
class Block:
11+
def __init__(
12+
self,
13+
header: Header,
14+
prev_cert: Certificate | None,
15+
transactions: list,
16+
) -> None:
17+
self.header = header
18+
self.prev_cert = prev_cert
19+
self.transactions = transactions
20+
21+
@classmethod
22+
def decode(cls, data: bytes) -> Block:
23+
"""Decode a Block from raw bytes."""
24+
header, data = Header.decode(data)
25+
26+
# Genesis block has no certificate
27+
is_genesis = header.prev_block_hash.is_undef()
28+
29+
prev_cert = None
30+
if not is_genesis:
31+
prev_cert, data = Certificate.decode(data)
32+
33+
num_txs, data = encoding.read_var_int(data)
34+
35+
transactions = []
36+
for _ in range(num_txs):
37+
tx, data = Transaction.decode(data)
38+
transactions.append(tx)
39+
40+
return cls(header, prev_cert, transactions)

pactus/block/certificate.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from pactus.crypto.bls.signature import Signature
2+
from pactus.encoding import encoding
3+
from pactus.types.height import Height
4+
from pactus.types.round import Round
5+
6+
7+
class Certificate:
8+
def __init__(
9+
self,
10+
height: Height,
11+
round_: Round,
12+
committers: list,
13+
absentees: list,
14+
signature: Signature,
15+
) -> None:
16+
self.height = height
17+
self.round = round_
18+
self.committers = committers
19+
self.absentees = absentees
20+
self.signature = signature
21+
22+
@classmethod
23+
def decode(cls, buf: bytes) -> tuple:
24+
"""
25+
Decode a Certificate from bytes.
26+
Returns (Certificate, remaining_buf).
27+
"""
28+
height, buf = Height.decode(buf)
29+
round_, buf = Round.decode(buf)
30+
31+
num_committers, buf = encoding.read_var_int(buf)
32+
committers = []
33+
for _ in range(num_committers):
34+
n, buf = encoding.read_var_int(buf)
35+
committers.append(n)
36+
37+
num_absentees, buf = encoding.read_var_int(buf)
38+
absentees = []
39+
for _ in range(num_absentees):
40+
n, buf = encoding.read_var_int(buf)
41+
absentees.append(n)
42+
43+
signature, buf = Signature.decode(buf)
44+
45+
cert = cls(height, round_, committers, absentees, signature)
46+
return cert, buf

pactus/block/header.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from pactus.crypto.address import Address
2+
from pactus.crypto.hash import Hash
3+
from pactus.encoding import encoding
4+
5+
6+
class Header:
7+
def __init__(
8+
self,
9+
version: int,
10+
unix_time: int,
11+
prev_block_hash: Hash,
12+
state_root: Hash,
13+
sortition_seed: bytes,
14+
proposer_address: Address,
15+
) -> None:
16+
self.version = version
17+
self.unix_time = unix_time
18+
self.prev_block_hash = prev_block_hash
19+
self.state_root = state_root
20+
self.sortition_seed = sortition_seed
21+
self.proposer_address = proposer_address
22+
23+
@classmethod
24+
def decode(cls, buf: bytes) -> tuple:
25+
"""
26+
Decode a Header from bytes.
27+
Returns (Header, remaining_buf).
28+
"""
29+
version, buf = encoding.read_uint8(buf)
30+
unix_time, buf = encoding.read_uint32(buf)
31+
prev_block_hash, buf = Hash.decode(buf)
32+
state_root, buf = Hash.decode(buf)
33+
sortition_seed, buf = encoding.read_fixed_bytes(buf, 48)
34+
proposer_address, buf = Address.decode(buf)
35+
36+
header = cls(
37+
version,
38+
unix_time,
39+
prev_block_hash,
40+
state_root,
41+
sortition_seed,
42+
proposer_address,
43+
)
44+
return header, buf

pactus/crypto/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .address import Address, AddressType
2+
from .hash import Hash
23
from .hrp import HRP
34
from .private_key import PrivateKey
45
from .public_key import PublicKey
56

6-
__all__ = ["HRP", "Address", "AddressType", "PrivateKey", "PublicKey"]
7+
__all__ = ["HRP", "Address", "AddressType", "Hash", "PrivateKey", "PublicKey"]

pactus/crypto/address.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from enum import Enum
44

55
from pactus.crypto.hrp import HRP
6+
from pactus.encoding import encoding
67
from pactus.utils import utils
78

89
# Address format: hrp + `1` + type + data + checksum
@@ -22,7 +23,7 @@ class AddressType(Enum):
2223
class Address:
2324
def __init__(self, address_type: AddressType, data: bytes) -> None:
2425
if len(data) != ADDRESS_SIZE - 1:
25-
msg = "Data must be 21 bytes long"
26+
msg = "Data must be 20 bytes long"
2627
raise ValueError(msg)
2728

2829
self.data = bytearray()
@@ -32,7 +33,7 @@ def __init__(self, address_type: AddressType, data: bytes) -> None:
3233
@classmethod
3334
def from_string(cls, text: str) -> Address:
3435
if text == TREASURY_ADDRESS_STRING:
35-
return bytes([0])
36+
return cls(AddressType.TREASURY, bytes(20))
3637

3738
hrp, typ, data = utils.decode_to_base256_with_type(text)
3839
if hrp != HRP.ADDRESS_HRP:
@@ -59,7 +60,7 @@ def raw_bytes(self) -> bytes:
5960
return bytes(self.data)
6061

6162
def string(self) -> str:
62-
if self.data == bytes([0]):
63+
if self.is_treasury_address():
6364
return TREASURY_ADDRESS_STRING
6465

6566
return utils.encode_from_base256_with_type(
@@ -76,7 +77,30 @@ def is_treasury_address(self) -> bool:
7677

7778
def is_account_address(self) -> bool:
7879
t = self.address_type()
79-
return t in (AddressType.TREASURY, AddressType.BLS_ACCOUNT, AddressType.ED25519_ACCOUNT)
80+
return t in (
81+
AddressType.TREASURY,
82+
AddressType.BLS_ACCOUNT,
83+
AddressType.ED25519_ACCOUNT,
84+
)
8085

8186
def is_validator_address(self) -> bool:
8287
return self.address_type() == AddressType.VALIDATOR
88+
89+
def encode(self) -> bytes:
90+
buf = b""
91+
if self.is_treasury_address():
92+
return encoding.append_uint8(buf, AddressType.TREASURY.value)
93+
return encoding.append_fixed_bytes(buf, self.raw_bytes())
94+
95+
@classmethod
96+
def decode(cls, buf: bytes) -> tuple:
97+
"""
98+
Decode an Address from bytes.
99+
Returns (Address, remaining_buf).
100+
"""
101+
addr_type, buf = encoding.read_uint8(buf)
102+
if addr_type == AddressType.TREASURY.value:
103+
return cls(AddressType.TREASURY, bytes(20)), buf
104+
105+
data, buf = encoding.read_fixed_bytes(buf, 20)
106+
return cls(AddressType(addr_type), data), buf

pactus/crypto/bls/public_key.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from pactus.crypto.address import Address, AddressType
88
from pactus.crypto.hrp import HRP
9+
from pactus.encoding import encoding
910
from pactus.utils import utils
1011

1112
from .bls12_381.bls_sig_g1 import aggregate_pubs, verify
@@ -60,6 +61,14 @@ def string(self) -> str:
6061
self.raw_bytes(),
6162
)
6263

64+
def encode(self) -> bytes:
65+
return encoding.append_fixed_bytes(b"", self.raw_bytes())
66+
67+
@classmethod
68+
def decode(cls, buf: bytes) -> tuple:
69+
data, buf = encoding.read_fixed_bytes(buf, PUBLIC_KEY_SIZE)
70+
return cls.from_bytes(data), buf
71+
6372
def account_address(self) -> Address:
6473
return self._make_address(AddressType.BLS_ACCOUNT)
6574

pactus/crypto/bls/signature.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from pactus.encoding import encoding
4+
35
from .bls12_381.bls_sig_g1 import aggregate_sigs
46
from .bls12_381.serdesZ import deserialize, serialize
57

@@ -36,3 +38,12 @@ def raw_bytes(self) -> bytes:
3638

3739
def string(self) -> str:
3840
return self.raw_bytes().hex()
41+
42+
def encode(self) -> bytes:
43+
return encoding.append_fixed_bytes(b"", self.raw_bytes())
44+
45+
@classmethod
46+
def decode(cls, buf: bytes) -> tuple:
47+
data, buf = encoding.read_fixed_bytes(buf, SIGNATURE_SIZE)
48+
point_g1 = deserialize(data, is_ell2=False)
49+
return cls(point_g1), buf

pactus/crypto/ed25519/public_key.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from pactus.crypto.address import Address, AddressType
1010
from pactus.crypto.hrp import HRP
11+
from pactus.encoding import encoding
1112
from pactus.utils import utils
1213

1314
from .signature import SIGNATURE_TYPE_ED25519, Signature
@@ -49,6 +50,14 @@ def string(self) -> str:
4950
self.raw_bytes(),
5051
)
5152

53+
def encode(self) -> bytes:
54+
return encoding.append_fixed_bytes(b"", self.raw_bytes())
55+
56+
@classmethod
57+
def decode(cls, buf: bytes) -> tuple:
58+
data, buf = encoding.read_fixed_bytes(buf, PUBLIC_KEY_SIZE)
59+
return cls(ed25519.Ed25519PublicKey.from_public_bytes(data)), buf
60+
5261
def account_address(self) -> Address:
5362
blake2b = hashlib.blake2b(digest_size=32)
5463
blake2b.update(self.raw_bytes())

pactus/crypto/ed25519/signature.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from pactus.encoding import encoding
4+
35
SIGNATURE_SIZE = 64
46
SIGNATURE_TYPE_ED25519 = 3
57

@@ -23,3 +25,11 @@ def raw_bytes(self) -> bytes:
2325

2426
def string(self) -> str:
2527
return self.sig.hex()
28+
29+
def encode(self) -> bytes:
30+
return encoding.append_fixed_bytes(b"", self.sig)
31+
32+
@classmethod
33+
def decode(cls, buf: bytes) -> tuple:
34+
data, buf = encoding.read_fixed_bytes(buf, SIGNATURE_SIZE)
35+
return cls(data), buf

0 commit comments

Comments
 (0)