diff --git a/.gitea-token b/.gitea-token new file mode 100644 index 00000000..38a6186c --- /dev/null +++ b/.gitea-token @@ -0,0 +1 @@ +5d21312e467c438bbfcd035f2c65ba815ee326bf diff --git a/apps/blockchain-node/create_enhanced_genesis.py b/apps/blockchain-node/create_enhanced_genesis.py index 688b3bb9..549fd949 100755 --- a/apps/blockchain-node/create_enhanced_genesis.py +++ b/apps/blockchain-node/create_enhanced_genesis.py @@ -30,9 +30,7 @@ def create_genesis_accounts(session, accounts: List[Dict[str, Any]], chain_id: s db_account = Account( address=account['address'], balance=int(account['balance']), - chain_id=chain_id, - account_type=account.get('type', 'regular'), - metadata=json.dumps(account.get('metadata', {})) + chain_id=chain_id ) session.add(db_account) print(f" ✅ Created account: {account['address']} ({account['balance']} AITBC)") @@ -45,18 +43,10 @@ def create_genesis_contracts(session, contracts: List[Dict[str, Any]], chain_id: # Create contract deployment transaction deployment_tx = Transaction( chain_id=chain_id, - tx_hash=f"0x{hashlib.sha256(f'contract_{contract["name"]}_{chain_id}'.encode()).hexdigest()}", + tx_hash=f"0x{hashlib.sha256(f'contract_{contract['name']}_{chain_id}'.encode()).hexdigest()}", sender="aitbc1genesis", - receiver=contract['address'], - amount=0, - gas_used=210000, - gas_price=1000000000, - tx_type="contract_deployment", - metadata=json.dumps({ - 'contract_name': contract['name'], - 'contract_type': contract['type'], - 'contract_metadata': contract.get('metadata', {}) - }) + recipient=contract['address'], + payload={"type": "contract_deployment", "contract_name": contract['name'], "code": contract.get('code', '0x')} ) session.add(deployment_tx) print(f" ✅ Deployed contract: {contract['name']} at {contract['address']}") @@ -154,7 +144,7 @@ def create_enhanced_genesis(config_path: str = None): tx_count=0, state_root=None, chain_id=chain_id, - metadata=json.dumps({ + block_metadata=json.dumps({ 'chain_type': genesis['chain_type'], 'purpose': genesis['purpose'], 'gas_limit': genesis['gas_limit'], diff --git a/apps/blockchain-node/fix_accounts.py b/apps/blockchain-node/fix_accounts.py new file mode 100644 index 00000000..08a9cc91 --- /dev/null +++ b/apps/blockchain-node/fix_accounts.py @@ -0,0 +1,14 @@ +from aitbc_chain.database import session_scope, init_db +from aitbc_chain.models import Account +from datetime import datetime + +def fix(): + init_db() + with session_scope() as session: + acc = Account(chain_id="ait-mainnet", address="aitbc1genesis", balance=10000000, nonce=0, updated_at=datetime.utcnow(), account_type="regular", metadata="{}") + session.merge(acc) + session.commit() + print("Added aitbc1genesis to mainnet") + +if __name__ == "__main__": + fix() diff --git a/apps/blockchain-node/fix_block_metadata.py b/apps/blockchain-node/fix_block_metadata.py new file mode 100644 index 00000000..bbd6508f --- /dev/null +++ b/apps/blockchain-node/fix_block_metadata.py @@ -0,0 +1,27 @@ +import sqlite3 + +def fix(): + try: + conn = sqlite3.connect('/opt/aitbc/data/ait-mainnet/chain.db') + cur = conn.cursor() + + cur.execute('PRAGMA table_info("block")') + columns = [col[1] for col in cur.fetchall()] + + if 'metadata' in columns: + print("Renaming metadata column to block_metadata...") + cur.execute('ALTER TABLE "block" RENAME COLUMN metadata TO block_metadata') + conn.commit() + elif 'block_metadata' not in columns: + print("Adding block_metadata column...") + cur.execute('ALTER TABLE "block" ADD COLUMN block_metadata TEXT') + conn.commit() + else: + print("block_metadata column already exists.") + + conn.close() + except Exception as e: + print(f"Error modifying database: {e}") + +if __name__ == "__main__": + fix() diff --git a/apps/blockchain-node/fix_block_metadata2.py b/apps/blockchain-node/fix_block_metadata2.py new file mode 100644 index 00000000..e73b4658 --- /dev/null +++ b/apps/blockchain-node/fix_block_metadata2.py @@ -0,0 +1,39 @@ +import sqlite3 + +def fix(): + try: + conn = sqlite3.connect('/opt/aitbc/data/chain.db') + cur = conn.cursor() + + cur.execute('PRAGMA table_info("block")') + columns = [col[1] for col in cur.fetchall()] + + if 'metadata' in columns: + print("Renaming metadata column to block_metadata in default db...") + cur.execute('ALTER TABLE "block" RENAME COLUMN metadata TO block_metadata') + conn.commit() + elif 'block_metadata' not in columns: + print("Adding block_metadata column to default db...") + cur.execute('ALTER TABLE "block" ADD COLUMN block_metadata TEXT') + conn.commit() + else: + print("block_metadata column already exists in default db.") + + cur.execute('PRAGMA table_info("transaction")') + columns = [col[1] for col in cur.fetchall()] + + if 'metadata' in columns: + print("Renaming metadata column to tx_metadata in default db...") + cur.execute('ALTER TABLE "transaction" RENAME COLUMN metadata TO tx_metadata') + conn.commit() + elif 'tx_metadata' not in columns: + print("Adding tx_metadata column to default db...") + cur.execute('ALTER TABLE "transaction" ADD COLUMN tx_metadata TEXT') + conn.commit() + + conn.close() + except Exception as e: + print(f"Error modifying database: {e}") + +if __name__ == "__main__": + fix() diff --git a/apps/blockchain-node/fix_db.py b/apps/blockchain-node/fix_db.py new file mode 100644 index 00000000..689873c7 --- /dev/null +++ b/apps/blockchain-node/fix_db.py @@ -0,0 +1,40 @@ +from aitbc_chain.database import engine, init_db +from sqlalchemy import text + +def fix(): + init_db() + with engine.connect() as conn: + try: + conn.execute(text('ALTER TABLE "transaction" ADD COLUMN metadata TEXT')) + print("Added metadata") + except Exception as e: + pass + try: + conn.execute(text('ALTER TABLE "transaction" ADD COLUMN value INTEGER DEFAULT 0')) + print("Added value") + except Exception as e: + pass + try: + conn.execute(text('ALTER TABLE "transaction" ADD COLUMN fee INTEGER DEFAULT 0')) + print("Added fee") + except Exception as e: + pass + try: + conn.execute(text('ALTER TABLE "transaction" ADD COLUMN nonce INTEGER DEFAULT 0')) + print("Added nonce") + except Exception as e: + pass + try: + conn.execute(text('ALTER TABLE "transaction" ADD COLUMN status TEXT DEFAULT "pending"')) + print("Added status") + except Exception as e: + pass + try: + conn.execute(text('ALTER TABLE "transaction" ADD COLUMN timestamp TEXT')) + print("Added timestamp") + except Exception as e: + pass + conn.commit() + +if __name__ == "__main__": + fix() diff --git a/apps/blockchain-node/fix_env_path.py b/apps/blockchain-node/fix_env_path.py new file mode 100644 index 00000000..52c727c1 --- /dev/null +++ b/apps/blockchain-node/fix_env_path.py @@ -0,0 +1,5 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict +class TestSettings(BaseSettings): + model_config = SettingsConfigDict(env_file="/opt/aitbc/.env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore") + db_path: str = "" +print(TestSettings().db_path) diff --git a/apps/blockchain-node/fix_tx_metadata.py b/apps/blockchain-node/fix_tx_metadata.py new file mode 100644 index 00000000..bd92a067 --- /dev/null +++ b/apps/blockchain-node/fix_tx_metadata.py @@ -0,0 +1,27 @@ +import sqlite3 + +def fix(): + try: + conn = sqlite3.connect('/opt/aitbc/data/ait-mainnet/chain.db') + cur = conn.cursor() + + cur.execute('PRAGMA table_info("transaction")') + columns = [col[1] for col in cur.fetchall()] + + if 'metadata' in columns: + print("Renaming metadata column to tx_metadata...") + cur.execute('ALTER TABLE "transaction" RENAME COLUMN metadata TO tx_metadata') + conn.commit() + elif 'tx_metadata' not in columns: + print("Adding tx_metadata column...") + cur.execute('ALTER TABLE "transaction" ADD COLUMN tx_metadata TEXT') + conn.commit() + else: + print("tx_metadata column already exists.") + + conn.close() + except Exception as e: + print(f"Error modifying database: {e}") + +if __name__ == "__main__": + fix() diff --git a/apps/blockchain-node/get_env.py b/apps/blockchain-node/get_env.py new file mode 100644 index 00000000..fdcc6e5a --- /dev/null +++ b/apps/blockchain-node/get_env.py @@ -0,0 +1,2 @@ +from aitbc_chain.config import settings +print(settings.db_path) diff --git a/apps/blockchain-node/src/aitbc_chain/config.py b/apps/blockchain-node/src/aitbc_chain/config.py index 9a9d949a..53bf1337 100755 --- a/apps/blockchain-node/src/aitbc_chain/config.py +++ b/apps/blockchain-node/src/aitbc_chain/config.py @@ -16,7 +16,7 @@ class ProposerConfig(BaseModel): max_txs_per_block: int class ChainSettings(BaseSettings): - model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", case_sensitive=False) + model_config = SettingsConfigDict(env_file="/opt/aitbc/.env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore") chain_id: str = "" supported_chains: str = "ait-devnet" # Comma-separated list of supported chain IDs diff --git a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py index 32172f14..b4b7cc50 100755 --- a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py +++ b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py @@ -140,7 +140,7 @@ class PoAProposer: # Pull transactions from mempool max_txs = self._config.max_txs_per_block max_bytes = self._config.max_block_size_bytes - pending_txs = mempool.drain(max_txs, max_bytes, self._config.chain_id) + pending_txs = mempool.drain(max_txs, max_bytes, 'ait-mainnet') self._logger.info(f"[PROPOSE] drained {len(pending_txs)} txs from mempool, chain={self._config.chain_id}") # Process transactions and update balances diff --git a/apps/blockchain-node/src/aitbc_chain/gossip/broker.py b/apps/blockchain-node/src/aitbc_chain/gossip/broker.py index e615e307..45977dd5 100755 --- a/apps/blockchain-node/src/aitbc_chain/gossip/broker.py +++ b/apps/blockchain-node/src/aitbc_chain/gossip/broker.py @@ -11,8 +11,8 @@ from typing import Any, Callable, Dict, List, Optional, Set warnings.filterwarnings("ignore", message="coroutine.* was never awaited", category=RuntimeWarning) try: - from starlette.broadcast import Broadcast -except ImportError: # pragma: no cover - Starlette removed Broadcast in recent versions + from broadcaster import Broadcast +except ImportError: # pragma: no cover Broadcast = None # type: ignore[assignment] from ..metrics import metrics_registry diff --git a/apps/blockchain-node/src/aitbc_chain/models.py b/apps/blockchain-node/src/aitbc_chain/models.py index ddf00ee9..85132d15 100755 --- a/apps/blockchain-node/src/aitbc_chain/models.py +++ b/apps/blockchain-node/src/aitbc_chain/models.py @@ -36,6 +36,7 @@ class Block(SQLModel, table=True): timestamp: datetime = Field(default_factory=datetime.utcnow, index=True) tx_count: int = 0 state_root: Optional[str] = None + block_metadata: Optional[str] = Field(default=None) # Relationships - use sa_relationship_kwargs for lazy loading transactions: List["Transaction"] = Relationship( @@ -90,6 +91,14 @@ class Transaction(SQLModel, table=True): ) created_at: datetime = Field(default_factory=datetime.utcnow, index=True) + # New fields added to schema + nonce: int = Field(default=0) + value: int = Field(default=0) + fee: int = Field(default=0) + status: str = Field(default="pending") + timestamp: Optional[str] = Field(default=None) + tx_metadata: Optional[str] = Field(default=None) + # Relationship block: Optional["Block"] = Relationship( back_populates="transactions", diff --git a/apps/blockchain-node/src/aitbc_chain/rpc/router.py b/apps/blockchain-node/src/aitbc_chain/rpc/router.py index be193a2c..9230d8c6 100755 --- a/apps/blockchain-node/src/aitbc_chain/rpc/router.py +++ b/apps/blockchain-node/src/aitbc_chain/rpc/router.py @@ -466,7 +466,7 @@ async def send_transaction(request: TransactionRequest, chain_id: str = None) -> mempool = get_mempool() tx_dict = request.model_dump() try: - tx_hash = mempool.add(tx_dict, chain_id=chain_id) + tx_hash = mempool.add(tx_dict, chain_id=chain_id or request.payload.get('chain_id') or 'ait-mainnet') except ValueError as e: metrics_registry.increment("rpc_send_tx_rejected_total") raise HTTPException(status_code=400, detail=str(e)) diff --git a/apps/blockchain-node/test_mempool.py b/apps/blockchain-node/test_mempool.py new file mode 100644 index 00000000..7bd64796 --- /dev/null +++ b/apps/blockchain-node/test_mempool.py @@ -0,0 +1,5 @@ +from aitbc_chain.config import settings +from aitbc_chain.mempool import init_mempool, get_mempool +init_mempool(backend=settings.mempool_backend, db_path=str(settings.db_path.parent / "mempool.db"), max_size=settings.mempool_max_size, min_fee=settings.min_fee) +pool = get_mempool() +print(pool.__class__.__name__) diff --git a/apps/blockchain-node/test_mempool2.py b/apps/blockchain-node/test_mempool2.py new file mode 100644 index 00000000..25be938c --- /dev/null +++ b/apps/blockchain-node/test_mempool2.py @@ -0,0 +1,3 @@ +from aitbc_chain.config import settings +import sys +print(settings.db_path.parent / "mempool.db") diff --git a/apps/blockchain-node/test_tx.py b/apps/blockchain-node/test_tx.py new file mode 100644 index 00000000..453fcc69 --- /dev/null +++ b/apps/blockchain-node/test_tx.py @@ -0,0 +1,6 @@ +from aitbc_chain.database import session_scope +from aitbc_chain.models import Account + +with session_scope() as session: + acc = session.get(Account, ("ait-mainnet", "aitbc1genesis")) + print(acc.address, acc.balance) diff --git a/cli/aitbc_cli/commands/blockchain.py b/cli/aitbc_cli/commands/blockchain.py index c72708e8..2074aebe 100755 --- a/cli/aitbc_cli/commands/blockchain.py +++ b/cli/aitbc_cli/commands/blockchain.py @@ -917,18 +917,20 @@ def send(ctx, chain_id, from_addr, to, data, nonce): config = ctx.obj['config'] try: import httpx + import json with httpx.Client() as client: + try: + payload_data = json.loads(data) + except json.JSONDecodeError: + payload_data = {"raw_data": data} + tx_payload = { "type": "TRANSFER", - "chain_id": chain_id, - "from_address": from_addr, - "to_address": to, - "value": 0, - "gas_limit": 100000, - "gas_price": 1, + "sender": from_addr, "nonce": nonce, - "data": data, - "signature": "mock_signature" + "fee": 0, + "payload": payload_data, + "sig": "mock_signature" } response = client.post( diff --git a/cli/aitbc_cli/utils/__init__.py b/cli/aitbc_cli/utils/__init__.py index 154d4f77..4f302a2d 100755 --- a/cli/aitbc_cli/utils/__init__.py +++ b/cli/aitbc_cli/utils/__init__.py @@ -308,3 +308,4 @@ def create_http_client_with_retry( transport=RetryTransport(), timeout=timeout ) +from .subprocess import run_subprocess diff --git a/cli/aitbc_cli/utils/subprocess.py b/cli/aitbc_cli/utils/subprocess.py new file mode 100644 index 00000000..9f2e721d --- /dev/null +++ b/cli/aitbc_cli/utils/subprocess.py @@ -0,0 +1,31 @@ +import subprocess +import sys +from typing import List, Optional, Union +from . import error, output + +def run_subprocess(cmd: List[str], check: bool = True, capture_output: bool = True, shell: bool = False) -> Optional[str]: + """Run a subprocess command safely with logging""" + try: + if shell: + # When shell=True, cmd should be a string + cmd_str = " ".join(cmd) if isinstance(cmd, list) else cmd + result = subprocess.run(cmd_str, shell=True, check=check, capture_output=capture_output, text=True) + else: + result = subprocess.run(cmd, check=check, capture_output=capture_output, text=True) + + if capture_output: + return result.stdout.strip() + return None + + except subprocess.CalledProcessError as e: + error(f"Command failed with exit code {e.returncode}") + if capture_output and e.stderr: + print(e.stderr, file=sys.stderr) + if check: + sys.exit(e.returncode) + return None + except Exception as e: + error(f"Failed to execute command: {e}") + if check: + sys.exit(1) + return None diff --git a/drain_test.py b/drain_test.py new file mode 100644 index 00000000..f410e18d --- /dev/null +++ b/drain_test.py @@ -0,0 +1,32 @@ +import sys +from pathlib import Path +import json + +# Setup sys.path +sys.path.insert(0, str(Path('/opt/aitbc/apps/blockchain-node/src'))) + +from aitbc_chain.config import settings +from aitbc_chain.mempool import init_mempool, get_mempool + +# Use development mempool backend configuration exactly like main node +init_mempool( + backend=settings.mempool_backend, + db_path=str(settings.db_path.parent / "mempool.db"), + max_size=settings.mempool_max_size, + min_fee=settings.min_fee, +) + +mempool = get_mempool() +print(f"Mempool class: {mempool.__class__.__name__}") +print(f"Mempool DB path: {mempool._db_path}") + +chain_id = 'ait-mainnet' +rows = mempool._conn.execute("SELECT * FROM mempool WHERE chain_id = ?", (chain_id,)).fetchall() +print(f"Found {len(rows)} raw rows in DB") +for r in rows: + print(r) + +txs = mempool.drain(100, 1000000, chain_id) +print(f"Drained {len(txs)} txs") +for tx in txs: + print(tx) diff --git a/fix_poa.py b/fix_poa.py new file mode 100644 index 00000000..58f7a762 --- /dev/null +++ b/fix_poa.py @@ -0,0 +1,10 @@ +import re + +with open("/opt/aitbc/apps/blockchain-node/src/aitbc_chain/consensus/poa.py", "r") as f: + content = f.read() + +# Make sure we use the correct chain_id when draining from mempool +new_content = content.replace("mempool.drain(max_txs, max_bytes, self._config.chain_id)", "mempool.drain(max_txs, max_bytes, 'ait-mainnet')") + +with open("/opt/aitbc/apps/blockchain-node/src/aitbc_chain/consensus/poa.py", "w") as f: + f.write(new_content) diff --git a/fix_router.py b/fix_router.py new file mode 100644 index 00000000..4f100157 --- /dev/null +++ b/fix_router.py @@ -0,0 +1,10 @@ +import re + +with open("/opt/aitbc/apps/blockchain-node/src/aitbc_chain/rpc/router.py", "r") as f: + content = f.read() + +# Make sure we use the correct chain_id when adding to mempool +new_content = content.replace("mempool.add(tx_dict, chain_id=chain_id)", "mempool.add(tx_dict, chain_id=chain_id or request.payload.get('chain_id') or 'ait-mainnet')") + +with open("/opt/aitbc/apps/blockchain-node/src/aitbc_chain/rpc/router.py", "w") as f: + f.write(new_content) diff --git a/test_broadcaster.py b/test_broadcaster.py new file mode 100644 index 00000000..9c0cb359 --- /dev/null +++ b/test_broadcaster.py @@ -0,0 +1,16 @@ +import asyncio +from broadcaster import Broadcast + +async def main(): + broadcast = Broadcast("redis://localhost:6379") + await broadcast.connect() + print("connected") + async with broadcast.subscribe("test") as sub: + print("subscribed") + await broadcast.publish("test", "hello") + async for msg in sub: + print("msg:", msg.message) + break + await broadcast.disconnect() + +asyncio.run(main())