consensus: improve block proposal timing and return status, fix transaction field mapping in sync
- Add initial sleep before first proposal to prevent immediate block creation - Return bool from _propose_block to indicate if block was actually proposed - Wait for next slot only after successful proposal, use regular interval after skipped proposals - Add 1s sleep after proposal errors to prevent tight error loops - Add parent_hash, proposer, and state_root fields to /rpc/blocks-range response - Fix transaction field mapping
This commit is contained in:
@@ -96,14 +96,24 @@ class PoAProposer:
|
||||
self._task = None
|
||||
|
||||
async def _run_loop(self) -> None:
|
||||
# Initial sleep so we don't start proposing immediately
|
||||
await asyncio.sleep(self._config.interval_seconds)
|
||||
while not self._stop_event.is_set():
|
||||
await self._wait_until_next_slot()
|
||||
if self._stop_event.is_set():
|
||||
break
|
||||
try:
|
||||
await self._propose_block()
|
||||
proposed = await self._propose_block()
|
||||
if proposed:
|
||||
await self._wait_until_next_slot()
|
||||
else:
|
||||
# If we skipped proposing, wait a regular interval
|
||||
try:
|
||||
await asyncio.wait_for(self._stop_event.wait(), timeout=self._config.interval_seconds)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
except Exception as exc: # pragma: no cover - defensive logging
|
||||
self._logger.exception("Failed to propose block", extra={"error": str(exc)})
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
async def _wait_until_next_slot(self) -> None:
|
||||
head = self._fetch_chain_head()
|
||||
@@ -119,7 +129,7 @@ class PoAProposer:
|
||||
except asyncio.TimeoutError:
|
||||
return
|
||||
|
||||
async def _propose_block(self) -> None:
|
||||
async def _propose_block(self) -> bool:
|
||||
# Check internal mempool and include transactions
|
||||
from ..mempool import get_mempool
|
||||
from ..models import Transaction, Account
|
||||
@@ -131,7 +141,7 @@ class PoAProposer:
|
||||
mempool_size = mempool.size(self._config.chain_id)
|
||||
if mempool_size == 0:
|
||||
self._logger.info(f"[PROPOSE] Skipping block proposal: mempool is empty (chain={self._config.chain_id})")
|
||||
return
|
||||
return False
|
||||
|
||||
with self._session_factory() as session:
|
||||
head = session.exec(select(Block).where(Block.chain_id == self._config.chain_id).order_by(Block.height.desc()).limit(1)).first()
|
||||
@@ -283,6 +293,8 @@ class PoAProposer:
|
||||
},
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def _ensure_genesis_block(self) -> None:
|
||||
with self._session_factory() as session:
|
||||
# Check if genesis block already exists
|
||||
|
||||
@@ -254,10 +254,13 @@ async def get_blocks_range(start: int = 0, end: int = 10, include_tx: bool = Tru
|
||||
block_data = {
|
||||
"height": b.height,
|
||||
"hash": b.hash,
|
||||
"parent_hash": b.parent_hash,
|
||||
"proposer": b.proposer,
|
||||
"timestamp": b.timestamp.isoformat(),
|
||||
"tx_count": b.tx_count,
|
||||
"state_root": b.state_root,
|
||||
}
|
||||
|
||||
|
||||
if include_tx:
|
||||
# Fetch transactions for this block
|
||||
txs = session.exec(
|
||||
|
||||
@@ -288,10 +288,9 @@ class ChainSync:
|
||||
# Import transactions if provided and apply state changes
|
||||
if transactions:
|
||||
for tx_data in transactions:
|
||||
sender_addr = tx_data.get("sender", "")
|
||||
payload = tx_data.get("payload", {}) or {}
|
||||
recipient_addr = payload.get("to") or tx_data.get("recipient", "")
|
||||
value = int(payload.get("value", 0) or 0)
|
||||
sender_addr = tx_data.get("from", "")
|
||||
recipient_addr = tx_data.get("to", "")
|
||||
value = int(tx_data.get("amount", 0) or 0)
|
||||
fee = int(tx_data.get("fee", 0) or 0)
|
||||
tx_hash = tx_data.get("tx_hash", "")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user