useful_blockchain 2.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. useful_blockchain/__init__.py +6 -0
  2. useful_blockchain/blockchain.py +220 -0
  3. useful_blockchain/chain_validator.py +116 -0
  4. useful_blockchain/cli.py +86 -0
  5. useful_blockchain/consensus/__init__.py +11 -0
  6. useful_blockchain/consensus/base.py +58 -0
  7. useful_blockchain/consensus/factory.py +36 -0
  8. useful_blockchain/consensus/pos.py +239 -0
  9. useful_blockchain/consensus/pow.py +124 -0
  10. useful_blockchain/hash_utils.py +45 -0
  11. useful_blockchain/network/__init__.py +4 -0
  12. useful_blockchain/network/discovery.py +79 -0
  13. useful_blockchain/network/messages.py +35 -0
  14. useful_blockchain/network/node.py +484 -0
  15. useful_blockchain/network/peer.py +156 -0
  16. useful_blockchain/network/peer_auth.py +69 -0
  17. useful_blockchain/network/rate_limit.py +31 -0
  18. useful_blockchain/network/reconnect.py +55 -0
  19. useful_blockchain/network/server.py +201 -0
  20. useful_blockchain/network/tls.py +64 -0
  21. useful_blockchain/observability/__init__.py +7 -0
  22. useful_blockchain/observability/health_server.py +139 -0
  23. useful_blockchain/observability/logging_config.py +56 -0
  24. useful_blockchain/observability/metrics.py +143 -0
  25. useful_blockchain/persistence/__init__.py +5 -0
  26. useful_blockchain/persistence/constants.py +5 -0
  27. useful_blockchain/persistence/store.py +169 -0
  28. useful_blockchain/py.typed +0 -0
  29. useful_blockchain/settings.py +244 -0
  30. useful_blockchain/signature.py +304 -0
  31. useful_blockchain/types.py +171 -0
  32. useful_blockchain-2.1.1.data/data/config/default.yaml +74 -0
  33. useful_blockchain-2.1.1.data/data/config/docker.yaml +47 -0
  34. useful_blockchain-2.1.1.data/data/config/production.yaml +74 -0
  35. useful_blockchain-2.1.1.dist-info/METADATA +296 -0
  36. useful_blockchain-2.1.1.dist-info/RECORD +40 -0
  37. useful_blockchain-2.1.1.dist-info/WHEEL +5 -0
  38. useful_blockchain-2.1.1.dist-info/entry_points.txt +2 -0
  39. useful_blockchain-2.1.1.dist-info/licenses/LICENSE +21 -0
  40. useful_blockchain-2.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,6 @@
1
+ from useful_blockchain.blockchain import BlockChain
2
+ from useful_blockchain.network.node import Node
3
+ from useful_blockchain.signature import SignatureManager
4
+
5
+ __version__ = "2.1.1"
6
+ __all__ = ["BlockChain", "SignatureManager", "Node", "__version__"]
@@ -0,0 +1,220 @@
1
+ """
2
+ シンプルなブロックチェーンの実装
3
+
4
+ このモジュールは基本的なブロックチェーンの機能を提供します。
5
+ 各ブロックは前のブロックのハッシュ値を含んでおり、チェーン状に連結されています。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import datetime as dt
11
+ import hashlib
12
+ import json
13
+ from typing import Any
14
+
15
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
16
+
17
+ from useful_blockchain.chain_validator import verify_chain_integrity
18
+ from useful_blockchain.consensus.base import ConsensusProtocol
19
+ from useful_blockchain.hash_utils import calc_body_hash, calc_legacy_tran_hash
20
+ from useful_blockchain.signature import SignatureManager
21
+ from useful_blockchain.types import (
22
+ Block,
23
+ ChainVerificationResult,
24
+ DEFAULT_GENESIS_PREV_HASH,
25
+ TransactionBody,
26
+ )
27
+
28
+
29
+ class BlockChain:
30
+ """
31
+ ブロックチェーンクラス
32
+
33
+ ブロックを連結してチェーン状に管理するクラスです。
34
+ 各ブロックには取引データ、タイムスタンプ、前のブロックへの参照が含まれます。
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ enable_signature: bool = False,
40
+ consensus: ConsensusProtocol | None = None,
41
+ genesis_prev_hash: str = DEFAULT_GENESIS_PREV_HASH,
42
+ ) -> None:
43
+ self.chain: list[Block] = []
44
+ self.enable_signature = enable_signature
45
+ self.consensus = consensus
46
+ self.genesis_prev_hash = genesis_prev_hash
47
+ self.signature_manager = SignatureManager() if enable_signature else None
48
+
49
+ def add_new_block(self, input_data: Any, output_data: Any) -> Block:
50
+ new_transaction = self.__create_new_transaction(input_data, output_data)
51
+
52
+ if len(self.chain) > 0:
53
+ prev_hash = self.chain[-1]["block_header"]["tran_hash"]
54
+ else:
55
+ prev_hash = self.genesis_prev_hash
56
+
57
+ body_hash = calc_body_hash(new_transaction)
58
+ header: dict[str, Any] = {"prev_hash": prev_hash}
59
+
60
+ if self.consensus is not None:
61
+ header["consensus_type"] = self.consensus.consensus_type
62
+ header["tran_hash"] = ""
63
+ else:
64
+ header["tran_hash"] = calc_legacy_tran_hash(prev_hash, body_hash)
65
+
66
+ new_block: Block = {
67
+ "block_index": len(self.chain) + 1,
68
+ "block_item": dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
69
+ "block_header": header, # type: ignore[typeddict-item]
70
+ "tran_counter": len(input_data) + len(output_data),
71
+ "tran_body": new_transaction,
72
+ }
73
+
74
+ if self.consensus is not None:
75
+ new_block = self.consensus.prepare_block(new_block, self.chain)
76
+ elif self.enable_signature and self.signature_manager:
77
+ if self.signature_manager.private_key is None:
78
+ raise ValueError(
79
+ "秘密鍵が設定されていません。generate_key_pair()を先に実行してください。"
80
+ )
81
+ new_block = self.signature_manager.sign_block(new_block) # type: ignore[assignment]
82
+
83
+ self.chain.append(new_block)
84
+ if self.consensus is not None:
85
+ self.consensus.on_block_added(new_block)
86
+ return new_block
87
+
88
+ def add_block(self, block: Block, validate: bool = True) -> bool:
89
+ """外部から受信したブロックを追加する。"""
90
+ if validate:
91
+ previous = self.chain[-1] if self.chain else None
92
+ if self.consensus is not None:
93
+ link = self.consensus.validate_chain_link(
94
+ block, previous, self.genesis_prev_hash
95
+ )
96
+ if not link.valid:
97
+ return False
98
+ elif previous is not None:
99
+ if block["block_header"]["prev_hash"] != previous["block_header"]["tran_hash"]:
100
+ return False
101
+ if self.consensus is not None:
102
+ result = self.consensus.validate_block(block, self.chain)
103
+ if not result.valid:
104
+ return False
105
+ verification = verify_chain_integrity(
106
+ self.chain + [block], self.consensus, self.genesis_prev_hash
107
+ )
108
+ if not verification.valid:
109
+ return False
110
+
111
+ self.chain.append(block)
112
+ if self.consensus is not None:
113
+ self.consensus.on_block_added(block)
114
+ return True
115
+
116
+ def replace_chain(
117
+ self,
118
+ new_chain: list[Block],
119
+ *,
120
+ genesis_stakes: dict[str, int] | None = None,
121
+ ) -> bool:
122
+ verification = verify_chain_integrity(
123
+ new_chain,
124
+ self.consensus,
125
+ self.genesis_prev_hash,
126
+ genesis_stakes=genesis_stakes,
127
+ )
128
+ if not verification.valid:
129
+ return False
130
+ self.chain = list(new_chain)
131
+ if self.consensus is not None and genesis_stakes is not None:
132
+ from useful_blockchain.consensus.pos import ProofOfStake
133
+
134
+ if isinstance(self.consensus, ProofOfStake):
135
+ self.consensus.sync_validators_from_chain(self.chain, genesis_stakes)
136
+ return True
137
+
138
+ def verify_chain(
139
+ self, *, genesis_stakes: dict[str, int] | None = None
140
+ ) -> ChainVerificationResult:
141
+ return verify_chain_integrity(
142
+ self.chain,
143
+ self.consensus,
144
+ self.genesis_prev_hash,
145
+ genesis_stakes=genesis_stakes,
146
+ )
147
+
148
+ def get_blocks_from(self, from_height: int, limit: int | None = None) -> list[Block]:
149
+ if from_height < 1:
150
+ blocks = list(self.chain)
151
+ else:
152
+ blocks = self.chain[from_height - 1 :]
153
+ if limit is not None:
154
+ return blocks[:limit]
155
+ return blocks
156
+
157
+ def __create_new_transaction(self, input_data: Any, output_data: Any) -> TransactionBody:
158
+ return {
159
+ "input_data": input_data,
160
+ "output_data": output_data,
161
+ }
162
+
163
+ def __calc_tran_hash(self, new_transaction: dict[str, Any]) -> str:
164
+ tran_string = json.dumps(new_transaction, sort_keys=True).encode()
165
+ return self.__hash(tran_string)
166
+
167
+ def __hash(self, str_seed: Any) -> str:
168
+ return hashlib.sha256(str(str_seed).encode()).hexdigest()
169
+
170
+ def dump(self, block_index: int = 0) -> None:
171
+ if block_index == 0:
172
+ print(json.dumps(self.chain, indent=2))
173
+ elif block_index < 1 or block_index > len(self.chain):
174
+ print("無効なブロックインデックスです。")
175
+ else:
176
+ print(json.dumps(self.chain[block_index - 1], indent=2))
177
+
178
+ def generate_key_pair(self) -> tuple[RSAPrivateKey, RSAPublicKey] | None:
179
+ if not self.enable_signature or not self.signature_manager:
180
+ print("署名機能が有効ではありません。")
181
+ return None
182
+ return self.signature_manager.generate_key_pair()
183
+
184
+ def verify_block_signature(self, block_index: int) -> bool:
185
+ if not self.enable_signature or not self.signature_manager:
186
+ print("署名機能が有効ではありません。")
187
+ return False
188
+ if block_index < 1 or block_index > len(self.chain):
189
+ print("無効なブロックインデックスです。")
190
+ return False
191
+ block = self.chain[block_index - 1]
192
+ if "signature" not in block:
193
+ print("このブロックには署名がありません。")
194
+ return False
195
+ return self.signature_manager.verify_block_signature(block)
196
+
197
+ def verify_all_signatures(self) -> dict[str, bool | None]:
198
+ if not self.enable_signature or not self.signature_manager:
199
+ print("署名機能が有効ではありません。")
200
+ return {}
201
+ results: dict[str, bool | None] = {}
202
+ for i, block in enumerate(self.chain, 1):
203
+ if "signature" in block:
204
+ results[f"block_{i}"] = self.signature_manager.verify_block_signature(block)
205
+ else:
206
+ results[f"block_{i}"] = None
207
+ return results
208
+
209
+ def export_public_key(self) -> bytes | None:
210
+ if not self.enable_signature or not self.signature_manager:
211
+ print("署名機能が有効ではありません。")
212
+ return None
213
+ return self.signature_manager.export_public_key()
214
+
215
+
216
+ if __name__ == "__main__":
217
+ bc = BlockChain()
218
+ bc.add_new_block("test", "test1")
219
+ bc.add_new_block("test3", "test4")
220
+ print(bc.chain)
@@ -0,0 +1,116 @@
1
+ """チェーン整合性検証。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from useful_blockchain.consensus.base import ConsensusProtocol
6
+ from useful_blockchain.hash_utils import calc_body_hash, calc_legacy_tran_hash
7
+ from useful_blockchain.types import Block, ChainVerificationResult, DEFAULT_GENESIS_PREV_HASH
8
+
9
+
10
+ def verify_chain_integrity(
11
+ chain: list[Block],
12
+ consensus: ConsensusProtocol | None = None,
13
+ genesis_prev_hash: str = DEFAULT_GENESIS_PREV_HASH,
14
+ *,
15
+ genesis_stakes: dict[str, int] | None = None,
16
+ ) -> ChainVerificationResult:
17
+ from useful_blockchain.consensus.pos import ProofOfStake
18
+
19
+ if isinstance(consensus, ProofOfStake) and genesis_stakes is not None:
20
+ for index, block in enumerate(chain):
21
+ previous = chain[index - 1] if index > 0 else None
22
+ if previous is not None:
23
+ link = consensus.validate_chain_link(block, previous, genesis_prev_hash)
24
+ if not link.valid:
25
+ return ChainVerificationResult(
26
+ valid=False,
27
+ failed_at_index=index + 1,
28
+ reason=link.reason,
29
+ )
30
+ else:
31
+ if block["block_header"]["prev_hash"] != genesis_prev_hash:
32
+ return ChainVerificationResult(
33
+ valid=False,
34
+ failed_at_index=index + 1,
35
+ reason="genesis prev_hash mismatch",
36
+ )
37
+ link = consensus.validate_chain_link(block, None, genesis_prev_hash)
38
+ if not link.valid:
39
+ return ChainVerificationResult(
40
+ valid=False,
41
+ failed_at_index=index + 1,
42
+ reason=link.reason,
43
+ )
44
+
45
+ prefix_validators = ProofOfStake.compute_validators_from_chain(
46
+ chain[:index], genesis_stakes, consensus.settings
47
+ )
48
+ result = consensus.validate_block(
49
+ block,
50
+ chain[:index],
51
+ validators_at_state=prefix_validators,
52
+ )
53
+ if not result.valid:
54
+ return ChainVerificationResult(
55
+ valid=False,
56
+ failed_at_index=index + 1,
57
+ reason=result.reason,
58
+ )
59
+ return ChainVerificationResult(valid=True)
60
+
61
+ for index, block in enumerate(chain):
62
+ previous = chain[index - 1] if index > 0 else None
63
+ if previous is not None:
64
+ if consensus is not None:
65
+ link = consensus.validate_chain_link(block, previous, genesis_prev_hash)
66
+ else:
67
+ prev_tran = previous["block_header"]["tran_hash"]
68
+ if block["block_header"]["prev_hash"] != prev_tran:
69
+ return ChainVerificationResult(
70
+ valid=False,
71
+ failed_at_index=index + 1,
72
+ reason="prev_hash mismatch",
73
+ )
74
+ if block["block_index"] != previous["block_index"] + 1:
75
+ return ChainVerificationResult(
76
+ valid=False,
77
+ failed_at_index=index + 1,
78
+ reason="block_index mismatch",
79
+ )
80
+ else:
81
+ if block["block_header"]["prev_hash"] != genesis_prev_hash:
82
+ return ChainVerificationResult(
83
+ valid=False,
84
+ failed_at_index=index + 1,
85
+ reason="genesis prev_hash mismatch",
86
+ )
87
+ if consensus is not None:
88
+ link = consensus.validate_chain_link(block, None, genesis_prev_hash)
89
+ if not link.valid:
90
+ return ChainVerificationResult(
91
+ valid=False,
92
+ failed_at_index=index + 1,
93
+ reason=link.reason,
94
+ )
95
+
96
+ header = block.get("block_header", {})
97
+ consensus_type = header.get("consensus_type")
98
+ if consensus is not None and consensus_type:
99
+ result = consensus.validate_block(block, chain[:index])
100
+ if not result.valid:
101
+ return ChainVerificationResult(
102
+ valid=False,
103
+ failed_at_index=index + 1,
104
+ reason=result.reason,
105
+ )
106
+ elif consensus is None and not consensus_type:
107
+ body_hash = calc_body_hash(block["tran_body"])
108
+ expected = calc_legacy_tran_hash(header["prev_hash"], body_hash)
109
+ if header.get("tran_hash") != expected:
110
+ return ChainVerificationResult(
111
+ valid=False,
112
+ failed_at_index=index + 1,
113
+ reason="legacy tran_hash mismatch",
114
+ )
115
+
116
+ return ChainVerificationResult(valid=True)
@@ -0,0 +1,86 @@
1
+ """easyblockchain ノード CLI エントリポイント。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import asyncio
7
+ import logging
8
+ import sys
9
+
10
+ from useful_blockchain.network.node import Node
11
+ from useful_blockchain.observability.logging_config import configure_logging
12
+ from useful_blockchain.settings import resolve_log_level
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def _build_parser() -> argparse.ArgumentParser:
18
+ parser = argparse.ArgumentParser(description="Run an easyblockchain P2P node")
19
+ parser.add_argument("--config", help="Path to config YAML", default=None)
20
+ parser.add_argument("--consensus", choices=["pow", "pos"], help="Consensus type override")
21
+ parser.add_argument("--port", type=int, help="Network port override")
22
+ parser.add_argument("--bootstrap", nargs="*", default=[], help="Bootstrap peer URLs")
23
+ parser.add_argument(
24
+ "--log-level",
25
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
26
+ help="Log level override",
27
+ )
28
+ parser.add_argument(
29
+ "--add-block",
30
+ nargs=2,
31
+ metavar=("INPUT", "OUTPUT"),
32
+ help="Add one block on start",
33
+ )
34
+ return parser
35
+
36
+
37
+ def _build_overrides(args: argparse.Namespace) -> dict:
38
+ overrides: dict = {}
39
+ if args.consensus:
40
+ overrides.setdefault("consensus", {})["type"] = args.consensus
41
+ if args.port:
42
+ overrides.setdefault("network", {})["port"] = args.port
43
+ if args.bootstrap:
44
+ overrides.setdefault("network", {})["bootstrap_peers"] = args.bootstrap
45
+ if args.log_level:
46
+ overrides.setdefault("node", {})["log_level"] = args.log_level
47
+ return overrides
48
+
49
+
50
+ async def run(args: argparse.Namespace | None = None) -> int:
51
+ parsed = args or _build_parser().parse_args()
52
+ overrides = _build_overrides(parsed)
53
+
54
+ node = Node(config_path=parsed.config, overrides=overrides or None)
55
+ configure_logging(
56
+ resolve_log_level(node.settings.node.log_level),
57
+ log_format=node.settings.observability.log_format,
58
+ node_id=node.node_id,
59
+ )
60
+ await node.start()
61
+ logger.info(
62
+ "Node %s listening on %s (%s)",
63
+ node.node_id,
64
+ node.p2p.local_url,
65
+ node.settings.consensus.type,
66
+ )
67
+
68
+ if parsed.add_block:
69
+ block = await node.add_block(parsed.add_block[0], parsed.add_block[1])
70
+ logger.info("Added block #%s", block["block_index"])
71
+
72
+ try:
73
+ while True:
74
+ await asyncio.sleep(3600)
75
+ except KeyboardInterrupt:
76
+ logger.info("Shutting down...")
77
+ await node.stop()
78
+ return 0
79
+
80
+
81
+ def main() -> None:
82
+ sys.exit(asyncio.run(run()))
83
+
84
+
85
+ if __name__ == "__main__":
86
+ main()
@@ -0,0 +1,11 @@
1
+ from useful_blockchain.consensus.base import ConsensusProtocol
2
+ from useful_blockchain.consensus.factory import create_consensus
3
+ from useful_blockchain.consensus.pos import ProofOfStake
4
+ from useful_blockchain.consensus.pow import ProofOfWork
5
+
6
+ __all__ = [
7
+ "ConsensusProtocol",
8
+ "ProofOfWork",
9
+ "ProofOfStake",
10
+ "create_consensus",
11
+ ]
@@ -0,0 +1,58 @@
1
+ """合意プロトコルの抽象基底クラス。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from useful_blockchain.types import Block, DEFAULT_GENESIS_PREV_HASH, ValidationResult
8
+
9
+
10
+ class ConsensusProtocol(ABC):
11
+ genesis_prev_hash: str = DEFAULT_GENESIS_PREV_HASH
12
+
13
+ @property
14
+ @abstractmethod
15
+ def consensus_type(self) -> str:
16
+ ...
17
+
18
+ @abstractmethod
19
+ def prepare_block(self, block: Block, chain: list[Block]) -> Block:
20
+ """ブロックに合意要件(マイニング・提案者署名等)を適用する。"""
21
+
22
+ @abstractmethod
23
+ def validate_block(self, block: Block, chain: list[Block]) -> ValidationResult:
24
+ """単一ブロックが合意ルールを満たすか検証する。"""
25
+
26
+ @abstractmethod
27
+ def select_canonical_chain(
28
+ self,
29
+ chains: list[list[Block]],
30
+ *,
31
+ genesis_stakes: dict[str, int] | None = None,
32
+ ) -> list[Block]:
33
+ """複数チェーンから正規チェーンを選択する。"""
34
+
35
+ def on_block_added(self, block: Block) -> None:
36
+ """ブロック追加後のフック(PoS 報酬等)。"""
37
+
38
+ def validate_chain_link(
39
+ self,
40
+ block: Block,
41
+ previous_block: Block | None,
42
+ genesis_prev_hash: str | None = None,
43
+ ) -> ValidationResult:
44
+ expected_genesis = genesis_prev_hash or self.genesis_prev_hash
45
+ if previous_block is None:
46
+ if block["block_header"]["prev_hash"] != expected_genesis:
47
+ return ValidationResult(valid=False, reason="genesis prev_hash mismatch")
48
+ return ValidationResult(valid=True)
49
+ prev_tran = previous_block["block_header"]["tran_hash"]
50
+ if block["block_header"]["prev_hash"] != prev_tran:
51
+ return ValidationResult(valid=False, reason="prev_hash mismatch")
52
+ if block["block_index"] != previous_block["block_index"] + 1:
53
+ return ValidationResult(valid=False, reason="block_index mismatch")
54
+ return ValidationResult(valid=True)
55
+
56
+ def score_chain(self, chain: list[Block]) -> tuple[int, int]:
57
+ """フォーク選択用スコア: (長さ, 重み)。"""
58
+ return len(chain), 0
@@ -0,0 +1,36 @@
1
+ """合意プロトコルファクトリ。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from useful_blockchain.consensus.base import ConsensusProtocol
6
+ from useful_blockchain.consensus.pos import ProofOfStake
7
+ from useful_blockchain.consensus.pow import ProofOfWork
8
+ from useful_blockchain.signature import SignatureManager
9
+ from useful_blockchain.types import AppSettings, ConsensusType
10
+
11
+
12
+ def create_consensus(
13
+ settings: AppSettings,
14
+ node_validator_id: str | None = None,
15
+ signature_manager: SignatureManager | None = None,
16
+ genesis_stakes: dict[str, int] | None = None,
17
+ ) -> ConsensusProtocol:
18
+ consensus_type: ConsensusType = settings.consensus.type
19
+ if consensus_type == "pow":
20
+ consensus: ConsensusProtocol = ProofOfWork(settings.consensus.pow)
21
+ elif consensus_type == "pos":
22
+ pos = ProofOfStake(
23
+ settings.consensus.pos,
24
+ node_validator_id=node_validator_id,
25
+ signature_manager=signature_manager,
26
+ )
27
+ stakes = genesis_stakes or {}
28
+ if node_validator_id and node_validator_id not in stakes:
29
+ stakes[node_validator_id] = settings.consensus.pos.min_stake
30
+ for validator_id, stake in stakes.items():
31
+ pos.register_validator(validator_id, stake)
32
+ consensus = pos
33
+ else:
34
+ raise ValueError(f"Unsupported consensus type: {consensus_type}")
35
+ consensus.genesis_prev_hash = settings.genesis.prev_hash
36
+ return consensus