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
|
self._task = None
|
||||||
|
|
||||||
async def _run_loop(self) -> 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():
|
while not self._stop_event.is_set():
|
||||||
await self._wait_until_next_slot()
|
|
||||||
if self._stop_event.is_set():
|
if self._stop_event.is_set():
|
||||||
break
|
break
|
||||||
try:
|
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
|
except Exception as exc: # pragma: no cover - defensive logging
|
||||||
self._logger.exception("Failed to propose block", extra={"error": str(exc)})
|
self._logger.exception("Failed to propose block", extra={"error": str(exc)})
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
|
||||||
async def _wait_until_next_slot(self) -> None:
|
async def _wait_until_next_slot(self) -> None:
|
||||||
head = self._fetch_chain_head()
|
head = self._fetch_chain_head()
|
||||||
@@ -119,7 +129,7 @@ class PoAProposer:
|
|||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return
|
return
|
||||||
|
|
||||||
async def _propose_block(self) -> None:
|
async def _propose_block(self) -> bool:
|
||||||
# Check internal mempool and include transactions
|
# Check internal mempool and include transactions
|
||||||
from ..mempool import get_mempool
|
from ..mempool import get_mempool
|
||||||
from ..models import Transaction, Account
|
from ..models import Transaction, Account
|
||||||
@@ -131,7 +141,7 @@ class PoAProposer:
|
|||||||
mempool_size = mempool.size(self._config.chain_id)
|
mempool_size = mempool.size(self._config.chain_id)
|
||||||
if mempool_size == 0:
|
if mempool_size == 0:
|
||||||
self._logger.info(f"[PROPOSE] Skipping block proposal: mempool is empty (chain={self._config.chain_id})")
|
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:
|
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()
|
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:
|
async def _ensure_genesis_block(self) -> None:
|
||||||
with self._session_factory() as session:
|
with self._session_factory() as session:
|
||||||
# Check if genesis block already exists
|
# Check if genesis block already exists
|
||||||
|
|||||||
@@ -254,8 +254,11 @@ async def get_blocks_range(start: int = 0, end: int = 10, include_tx: bool = Tru
|
|||||||
block_data = {
|
block_data = {
|
||||||
"height": b.height,
|
"height": b.height,
|
||||||
"hash": b.hash,
|
"hash": b.hash,
|
||||||
|
"parent_hash": b.parent_hash,
|
||||||
|
"proposer": b.proposer,
|
||||||
"timestamp": b.timestamp.isoformat(),
|
"timestamp": b.timestamp.isoformat(),
|
||||||
"tx_count": b.tx_count,
|
"tx_count": b.tx_count,
|
||||||
|
"state_root": b.state_root,
|
||||||
}
|
}
|
||||||
|
|
||||||
if include_tx:
|
if include_tx:
|
||||||
|
|||||||
@@ -288,10 +288,9 @@ class ChainSync:
|
|||||||
# Import transactions if provided and apply state changes
|
# Import transactions if provided and apply state changes
|
||||||
if transactions:
|
if transactions:
|
||||||
for tx_data in transactions:
|
for tx_data in transactions:
|
||||||
sender_addr = tx_data.get("sender", "")
|
sender_addr = tx_data.get("from", "")
|
||||||
payload = tx_data.get("payload", {}) or {}
|
recipient_addr = tx_data.get("to", "")
|
||||||
recipient_addr = payload.get("to") or tx_data.get("recipient", "")
|
value = int(tx_data.get("amount", 0) or 0)
|
||||||
value = int(payload.get("value", 0) or 0)
|
|
||||||
fee = int(tx_data.get("fee", 0) or 0)
|
fee = int(tx_data.get("fee", 0) or 0)
|
||||||
tx_hash = tx_data.get("tx_hash", "")
|
tx_hash = tx_data.get("tx_hash", "")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user