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.
- useful_blockchain/__init__.py +6 -0
- useful_blockchain/blockchain.py +220 -0
- useful_blockchain/chain_validator.py +116 -0
- useful_blockchain/cli.py +86 -0
- useful_blockchain/consensus/__init__.py +11 -0
- useful_blockchain/consensus/base.py +58 -0
- useful_blockchain/consensus/factory.py +36 -0
- useful_blockchain/consensus/pos.py +239 -0
- useful_blockchain/consensus/pow.py +124 -0
- useful_blockchain/hash_utils.py +45 -0
- useful_blockchain/network/__init__.py +4 -0
- useful_blockchain/network/discovery.py +79 -0
- useful_blockchain/network/messages.py +35 -0
- useful_blockchain/network/node.py +484 -0
- useful_blockchain/network/peer.py +156 -0
- useful_blockchain/network/peer_auth.py +69 -0
- useful_blockchain/network/rate_limit.py +31 -0
- useful_blockchain/network/reconnect.py +55 -0
- useful_blockchain/network/server.py +201 -0
- useful_blockchain/network/tls.py +64 -0
- useful_blockchain/observability/__init__.py +7 -0
- useful_blockchain/observability/health_server.py +139 -0
- useful_blockchain/observability/logging_config.py +56 -0
- useful_blockchain/observability/metrics.py +143 -0
- useful_blockchain/persistence/__init__.py +5 -0
- useful_blockchain/persistence/constants.py +5 -0
- useful_blockchain/persistence/store.py +169 -0
- useful_blockchain/py.typed +0 -0
- useful_blockchain/settings.py +244 -0
- useful_blockchain/signature.py +304 -0
- useful_blockchain/types.py +171 -0
- useful_blockchain-2.1.1.data/data/config/default.yaml +74 -0
- useful_blockchain-2.1.1.data/data/config/docker.yaml +47 -0
- useful_blockchain-2.1.1.data/data/config/production.yaml +74 -0
- useful_blockchain-2.1.1.dist-info/METADATA +296 -0
- useful_blockchain-2.1.1.dist-info/RECORD +40 -0
- useful_blockchain-2.1.1.dist-info/WHEEL +5 -0
- useful_blockchain-2.1.1.dist-info/entry_points.txt +2 -0
- useful_blockchain-2.1.1.dist-info/licenses/LICENSE +21 -0
- useful_blockchain-2.1.1.dist-info/top_level.txt +1 -0
|
@@ -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)
|
useful_blockchain/cli.py
ADDED
|
@@ -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
|