Skip to content

Commit 40570c1

Browse files
mpaya5kstroobants
andauthored
feat(hardhat): add rollup consensus service (#713)
* feat: add hardhat node and web3 package - Add Hardhat node service with Docker configuration - Configure hardhat.config.js with local network settings - Add 20 test accounts with 10,000 ETH each - Add web3 Python package to backend requirements - Update .gitignore for Hardhat artifacts and cache * feat: add contract and try script * feat: setup contract compilation and artifact handling - Add contract compilation setup with hardhat - Configure project structure for smart contracts - Fix file path for GhostContract.json artifact - Add necessary dependencies for contract compilation - Setup directory structure for contracts and artifacts * feat: creating hardhat transactions in consensus mechanism - Moved web3 python package into backend requirements file - Added access to compiled hardhat contract in jsonrpc service - Added database migration file so that a transaction has the ghost contract address of hardhat network - When a genlayer contract is deployed then a hardhat contract is deployed, both are linked in the CurrentState table - When a genlayer write method is executed then the new transaction gets the hardhat contract from the CurrentState table - When a genlayer transaction changes from status then a rollup transaction is created on the hardhat network - todo: remove rollup transaction table, put hardhat port in env, link genlayer account to hardhat account, check for out of gas, remove prints * feat: put hardhat port in env * feat: remove rollup transactions database table * feat: free transactions on hardhat, one hardhat account * test: add hardhat test with code from the transaction_processor * fix: resolve pre-commit error * test: moved test to integration tests, upgrade web3 version in requirements as it gave an import error * feat(hardhat): add genlayer-consensus contracts and setup compilation - Add genlayer-consensus smart contracts to hardhat/contracts directory - Update hardhat.config.js to enable new code generator (viaIR: true) - Add @openzeppelin/contracts and @openzeppelin/contracts-upgradeable dependencies - Configure hardhat Docker container for contract compilation - Verify hardhat node functionality in genlayer-studio This commit sets up the smart contract development environment with the necessary dependencies and configurations to compile and deploy genlayer-consensus contracts. * feat: deleted amm_adaptive.py - Deleted amm_adaptive.py because is not using the new syntax I will upload the new amm_adaptive.py whenevir will be ready * fix: contract interaction - Fixed contract deployment state updates for frontend synchronization - Improved contract method interactions and state management - Ensured proper state updates after contract value changes * fix: pre-commit errors solved * fix: solved black pre-commit issue * feat(hardhat): add deployment persistence to hardhat node - Add volume mappings for deployments, artifacts and cache - Implement manual deployment file saving - Update hardhat configuration for proper deployment paths This change ensures that deployed contracts persist between container restarts by saving deployment data to mounted volumes. Note: Block persistence still requires a different solution (Ganache/Geth/Besu) as Hardhat Network doesn't support state persistence. Related issue: #669 * refactor(hardhat): integrate start script into Dockerfile - Removed start.sh script - Incorporated start script directly into Dockerfile for streamlined execution * fix(db): remove duplicate ghost_contract_address assignment Removes redundant assignment of ghost_contract_address in transaction_from_dict function to improve code clarity and prevent potential inconsistencies * fix(security): address SonarCloud warnings and improve test coverage - Ensure non-root user is used in Dockerfile to enhance security - Adjust permissions to avoid overly permissive access (777) on /app directories - Add tests to cover deployment scripts, ensuring proper functionality and coverage * commit: Add contract initialization and consensus service integration - Updated deploy_contracts.js to handle contract initialization after deployment - Added initial ConsensusService integration into server.py - Enhanced Dockerfile.hardhat with proper permissions and chainId handling - Started implementation of consensus_service.py with basic contract interactions Note: consensus_service.py still needs additional work for complete contract initialization and interaction methods. * feat(deploy): integrate contract deployment and backend service Integrate complete contract deployment persistence in Hardhat and create ConsensusService for contract interaction. - Add deployFixture to Hardhat deployment script to handle contract initialization - Create ConsensusService class to interact with deployed contracts from backend - Add deploy_fixture method to ConsensusService as fallback initialization option - Ensure consistent contract initialization between Hardhat and backend - Load contract artifacts and addresses in ConsensusService from deployment files The deployFixture now handles all contract initialization during deployment, while ConsensusService provides a Python interface to interact with the deployed contracts. The deploy_fixture method in ConsensusService mirrors the Hardhat initialization logic but is kept as a fallback option. * feat(hardhat): implement contract deployment flow and deployment backup system - Add consensus contract deployment flow with proper initialization sequence - Implement contract backup system for deployment persistence - Add deployment verification to prevent unnecessary redeployments - Create copy-deployments script for managing contract backups - Configure hardhat to handle contract compilation and deployment * feat(hardhat): implement contract deployment flow and deployment backup system - Add consensus contract deployment flow with proper initialization sequence - Implement contract backup system for deployment persistence - Add deployment verification to prevent unnecessary redeployments - Create copy-deployments script for managing contract backups - Configure hardhat to handle contract compilation and deployment * refactor: remove ghost_contract_address redundant references - Remove ghost_contract_address from transactions_processor to avoid duplication - Remove unused ghost_contract_address from types.py * refactor: replace hardhat-deploy with hardhat-ignition - Migrate deployment process from hardhat-deploy to hardhat-ignition - Create DeployFixture module following TransactionFlow deployment order - Update deployment tests to ensure initialization and contract connecton as TransactionFlow * refactor: remove deployFixture from consensus_service.py - Remove unused deployFixture function from consensus_service.py - Deployment now handled entirely through hardhat-ignition * refactor(consensus): clean up unused imports and methods in ConsensusService - Remove unused eth_account Account import - Remove unused _send_transaction method * refactor(consensus): clean up unused imports and methods in ConsensusService - Remove unused eth_account Account import - Remove unused _send_transaction method --------- Co-authored-by: kstroobants <stroobants.kristof@hotmail.com>
1 parent 9a36796 commit 40570c1

File tree

11 files changed

+431
-28
lines changed

11 files changed

+431
-28
lines changed

.gitignore

+7-2
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,16 @@ package-lock.json
153153
nginx/ssl/*.key
154154
nginx/ssl/*.crt
155155

156+
156157
# Hardhat files
157-
hardhat/cache
158-
hardhat/artifacts
158+
hardhat/cache/*
159+
hardhat/artifacts/*
159160
hardhat/.openzeppelin
160161
hardhat/coverage
161162
hardhat/coverage.json
162163
hardhat/typechain
163164
hardhat/typechain-types
165+
hardhat/deployments/*
166+
167+
# Geth keystore
168+
hardhat/geth_keystore/*

backend/protocol_rpc/server.py

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from backend.database_handler.accounts_manager import AccountsManager
2424
from backend.consensus.base import ConsensusAlgorithm
2525
from backend.database_handler.models import Base
26+
from backend.rollup.consensus_service import ConsensusService
2627

2728

2829
def get_db_name(database: str) -> str:
@@ -59,6 +60,7 @@ def create_app():
5960
accounts_manager = AccountsManager(sqlalchemy_db.session)
6061
validators_registry = ValidatorsRegistry(sqlalchemy_db.session)
6162
llm_provider_registry = LLMProviderRegistry(sqlalchemy_db.session)
63+
consensus_service = ConsensusService()
6264

6365
# Initialize validators from environment configuration in a thread
6466
initialize_validators_db_session = Session(engine, expire_on_commit=False)
@@ -84,6 +86,7 @@ def create_app():
8486
consensus,
8587
llm_provider_registry,
8688
sqlalchemy_db,
89+
consensus_service,
8790
)
8891

8992

@@ -100,6 +103,7 @@ def create_app():
100103
consensus,
101104
llm_provider_registry,
102105
sqlalchemy_db,
106+
consensus_service,
103107
) = create_app()
104108
register_all_rpc_endpoints(
105109
jsonrpc,

backend/rollup/__init__.py

Whitespace-only changes.

backend/rollup/consensus_service.py

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import json
2+
import os
3+
from web3 import Web3
4+
from eth_account import Account
5+
from typing import Optional
6+
from pathlib import Path
7+
8+
9+
class ConsensusService:
10+
def __init__(self):
11+
"""
12+
Initialize the ConsensusService class
13+
"""
14+
# Connect to Hardhat Network
15+
port = os.environ.get("HARDHAT_PORT")
16+
url = os.environ.get("HARDHAT_URL")
17+
hardhat_url = f"{url}:{port}"
18+
self.web3 = Web3(Web3.HTTPProvider(hardhat_url))
19+
20+
if not self.web3.is_connected():
21+
raise ConnectionError(f"Failed to connect to Hardhat node at {hardhat_url}")
22+
23+
# Set up the default account (similar to ethers.getSigners()[0])
24+
self.owner = self.web3.eth.accounts[0]
25+
self.web3.eth.default_account = self.owner
26+
self.private_key = os.environ.get("HARDHAT_PRIVATE_KEY")
27+
28+
# Load all required contracts
29+
self.ghost_contract = self._load_contract("GhostContract")
30+
self.ghost_factory_contract = self._load_contract("GhostFactory")
31+
self.ghost_blueprint_contract = self._load_contract("GhostBlueprint")
32+
self.consensus_manager_contract = self._load_contract("ConsensusManager")
33+
self.mock_gen_staking_contract = self._load_contract("MockGenStaking")
34+
self.queues_contract = self._load_contract("Queues")
35+
self.transactions_contract = self._load_contract("Transactions")
36+
self.consensus_main_contract = self._load_contract("ConsensusMain")
37+
38+
def _load_contract(self, contract_name: str) -> Optional[dict]:
39+
"""
40+
Load contract deployment data from Hardhat deployments
41+
42+
Args:
43+
contract_name (str): The name of the contract to load
44+
45+
Returns:
46+
Optional[dict]: The contract deployment data or None if loading fails
47+
"""
48+
try:
49+
# Path to deployment file (using Docker volume path)
50+
deployment_path = (
51+
Path("/app/hardhat/deployments/localhost") / f"{contract_name}.json"
52+
)
53+
54+
if not deployment_path.exists():
55+
print(
56+
f"CONSENSUS_SERVICE: Deployment file not found at {deployment_path}"
57+
)
58+
return None
59+
60+
with open(deployment_path, "r") as f:
61+
deployment_data = json.load(f)
62+
63+
# Create contract instance
64+
contract = self.web3.eth.contract(
65+
address=deployment_data["address"], abi=deployment_data["abi"]
66+
)
67+
print(
68+
f"CONSENSUS_SERVICE: Loaded {contract_name} contract with address {contract.address}"
69+
)
70+
71+
return contract
72+
73+
except FileNotFoundError:
74+
print(
75+
f"CONSENSUS_SERVICE: Warning: {contract_name} deployment file not found"
76+
)
77+
return None
78+
except json.JSONDecodeError as e:
79+
print(
80+
f"CONSENSUS_SERVICE: Error decoding {contract_name} deployment file: {str(e)}"
81+
)
82+
return None
83+
except Exception as e:
84+
print(
85+
f"CONSENSUS_SERVICE: Error loading {contract_name} contract: {str(e)}"
86+
)
87+
return None

docker-compose.yml

+15-6
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ services:
7878
volumes:
7979
- ./.env:/app/.env
8080
- ./backend:/app/backend
81-
- ./hardhat/artifacts:/app/hardhat/artifacts
81+
- hardhat_artifacts:/app/hardhat/artifacts
82+
- hardhat_deployments:/app/hardhat/deployments
8283
depends_on:
8384
hardhat:
8485
condition: service_healthy
@@ -197,13 +198,19 @@ services:
197198
ports:
198199
- "${HARDHAT_PORT:-8545}:8545"
199200
volumes:
200-
- ./hardhat/contracts:/app/contracts
201-
- ./hardhat/scripts:/app/scripts
202-
- ./hardhat/test:/app/test
203-
- ./hardhat/hardhat.config.js:/app/hardhat.config.js
201+
- ./hardhat/contracts:/app/contracts:ro
202+
- ./hardhat/scripts:/app/scripts:ro
203+
- ./hardhat/test:/app/test:ro
204+
- ./hardhat/hardhat.config.js:/app/hardhat.config.js:ro
205+
- ./hardhat/deploy:/app/deploy:ro
206+
- ./hardhat/scripts:/app/scripts:ro
204207
- hardhat_artifacts:/app/artifacts
208+
- hardhat_cache:/app/cache
209+
- hardhat_deployments:/app/deployments
210+
- ignition_deployments:/app/ignition/deployments
205211
environment:
206212
- HARDHAT_NETWORK=hardhat
213+
restart: always
207214
healthcheck:
208215
test: ["CMD", "curl", "-X", "POST", "-H", "Content-Type: application/json", "--fail", "http://localhost:8545", "-d", '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}']
209216
interval: 10s
@@ -213,4 +220,6 @@ services:
213220

214221
volumes:
215222
hardhat_artifacts:
216-
223+
hardhat_cache:
224+
hardhat_deployments:
225+
ignition_deployments:

docker/Dockerfile.hardhat

+24-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ WORKDIR /app
66

77
# Install necessary packages and set up the environment
88
RUN apk add --no-cache curl g++ make netcat-openbsd python3 && \
9-
# Copy and install npm packages
109
mkdir -p /app && \
1110
chown -R hardhat-user:hardhat-group /app
1211

@@ -15,20 +14,36 @@ RUN npm install --ignore-scripts
1514

1615
COPY ./hardhat .
1716

18-
# Set up directories and permissions
19-
RUN mkdir -p /app/artifacts/build-info && \
17+
# Create directories and set permissions
18+
RUN mkdir -p /app/deployments/localhost && \
19+
mkdir -p /app/deployments/hardhat && \
20+
mkdir -p /app/artifacts/build-info && \
2021
mkdir -p /app/artifacts/contracts && \
22+
mkdir -p /app/cache && \
23+
mkdir -p /app/ignition/deployments && \
2124
chown -R hardhat-user:hardhat-group /app && \
22-
chmod -R 755 /app/artifacts && \
23-
# Create start script
24-
echo -e '#!/bin/sh\necho "Compiling contracts..."\nnpx hardhat compile --force\necho "Starting Hardhat node..."\nexec ./node_modules/.bin/hardhat node --network hardhat' > /app/start.sh && \
25-
chmod +x /app/start.sh
25+
chmod -R 755 /app && \
26+
chmod -R 775 /app/deployments && \
27+
chmod -R 775 /app/artifacts && \
28+
chmod -R 775 /app/cache && \
29+
chmod -R 775 /app/ignition/deployments
2630

2731
ENV PATH="/app/node_modules/.bin:${PATH}"
2832

33+
# Add the start script
34+
RUN echo '#!/bin/sh' > /app/start.sh && \
35+
echo 'echo "Checking and compiling contracts if needed..."' >> /app/start.sh && \
36+
echo 'npx hardhat compile' >> /app/start.sh && \
37+
echo 'echo "Starting Hardhat node..."' >> /app/start.sh && \
38+
echo 'npx hardhat node --hostname 0.0.0.0 & sleep 5' >> /app/start.sh && \
39+
echo 'echo "Deploying contracts with Ignition..."' >> /app/start.sh && \
40+
echo 'npx hardhat run scripts/deploy.js --network localhost' >> /app/start.sh && \
41+
echo 'npx hardhat test' >> /app/start.sh && \
42+
echo 'wait' >> /app/start.sh && \
43+
chmod +x /app/start.sh
44+
2945
EXPOSE 8545
3046

31-
# Switch to non-root user
3247
USER hardhat-user
3348

34-
CMD ["/app/start.sh"]
49+
CMD ["/app/start.sh"]

hardhat/hardhat.config.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require("@nomicfoundation/hardhat-toolbox");
2+
require("@nomicfoundation/hardhat-ignition-ethers");
23

3-
/** @type import('hardhat/config').HardhatUserConfig */
44
module.exports = {
55
solidity: {
66
version: "0.8.24",
@@ -21,15 +21,16 @@ module.exports = {
2121
chainId: 31337,
2222
gasPrice: 0,
2323
initialBaseFeePerGas: 0,
24-
accounts: {
25-
count: 1
26-
}
27-
}
28-
},
29-
paths: {
30-
sources: "./contracts",
31-
tests: "./test",
32-
cache: "./cache",
33-
artifacts: "./artifacts"
24+
blockGasLimit: 30000000,
25+
ignition: {
26+
blockPollingInterval: 1_000,
27+
timeBeforeBumpingFees: 3 * 60 * 1_000,
28+
maxFeeBumps: 4,
29+
requiredConfirmations: 5,
30+
disableFeeBumping: false,
31+
deploymentDir: "deployments/localhost",
32+
},
33+
},
34+
3435
}
3536
};
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
2+
3+
module.exports = buildModule("DeployFixture", (m) => {
4+
// 1. Deploy base contracts (in order of TransactionFlow)
5+
const ConsensusManager = m.contract("ConsensusManager");
6+
const GhostFactory = m.contract("GhostFactory");
7+
const GhostBlueprint = m.contract("GhostBlueprint");
8+
const GhostContract = m.contract("GhostContract");
9+
const MockGenStaking = m.contract("MockGenStaking", [m.getAccount(1)]);
10+
const Queues = m.contract("Queues");
11+
const Transactions = m.contract("Transactions");
12+
const ConsensusMain = m.contract("ConsensusMain");
13+
14+
// 2. Initialize GhostFactory and configure GhostBlueprint
15+
const initGhostFactory = m.call(GhostFactory, "initialize");
16+
const setBlueprint = m.call(GhostFactory, "setGhostBlueprint", [GhostBlueprint], {
17+
after: [initGhostFactory]
18+
});
19+
20+
// 3. Initialize ConsensusMain and dependents
21+
const initConsensusMain = m.call(ConsensusMain, "initialize", [ConsensusManager]);
22+
const initTransactions = m.call(Transactions, "initialize", [ConsensusMain], {
23+
after: [initConsensusMain]
24+
});
25+
const initQueues = m.call(Queues, "initialize", [ConsensusMain], {
26+
after: [initConsensusMain]
27+
});
28+
29+
// 4. Setup contract connections
30+
m.call(ConsensusMain, "setGhostFactory", [GhostFactory], { after: [initConsensusMain] });
31+
m.call(ConsensusMain, "setGenStaking", [MockGenStaking], { after: [initConsensusMain] });
32+
m.call(ConsensusMain, "setGenQueue", [Queues], { after: [initConsensusMain, initQueues] });
33+
m.call(ConsensusMain, "setGenTransactions", [Transactions], { after: [initConsensusMain, initTransactions] });
34+
35+
m.call(GhostFactory, "setGenConsensus", [ConsensusMain], { after: [initGhostFactory, initConsensusMain] });
36+
m.call(GhostFactory, "setGhostManager", [ConsensusMain], { after: [initGhostFactory, initConsensusMain] });
37+
m.call(Transactions, "setGenConsensus", [ConsensusMain], { after: [initTransactions] });
38+
m.call(ConsensusMain, "setAcceptanceTimeout", [0], { after: [initConsensusMain] });
39+
40+
// 5. Setup validators
41+
const validator1 = m.getAccount(1);
42+
const validator2 = m.getAccount(2);
43+
const validator3 = m.getAccount(3);
44+
m.call(MockGenStaking, "addValidators", [[validator1, validator2, validator3]]);
45+
46+
// 6. Deploy beacon proxy
47+
m.call(GhostFactory, "deployNewBeaconProxy", [], {
48+
after: [
49+
setBlueprint,
50+
initGhostFactory,
51+
initConsensusMain
52+
]
53+
});
54+
55+
return {
56+
GhostBlueprint,
57+
GhostContract,
58+
ConsensusManager,
59+
GhostFactory,
60+
MockGenStaking,
61+
Queues,
62+
Transactions,
63+
ConsensusMain
64+
};
65+
});

hardhat/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,9 @@
2020
},
2121
"engines": {
2222
"node": ">=18.0.0"
23+
},
24+
"devDependencies": {
25+
"@nomicfoundation/hardhat-ignition": "^0.15.8",
26+
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.8"
2327
}
2428
}

hardhat/scripts/deploy.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const hre = require("hardhat");
2+
const fs = require("fs-extra");
3+
const path = require("path");
4+
5+
/**
6+
* Save the deployment of a contract to a file.
7+
* @param {string} name - The name of the contract.
8+
* @param {Contract} contract - The contract instance.
9+
* @param {string} folder - The folder to save the deployment file.
10+
*/
11+
async function saveDeployment(name, contract, folder = "deployments/localhost") {
12+
const deploymentData = {
13+
address: await contract.getAddress(),
14+
abi: JSON.parse(contract.interface.formatJson())
15+
};
16+
17+
await fs.ensureDir(folder);
18+
const savePath = path.join(folder, `${name}.json`);
19+
await fs.writeJson(savePath, deploymentData, { spaces: 2 });
20+
21+
const backupFolder = path.join("copy_deployments/localhost");
22+
await fs.ensureDir(backupFolder);
23+
await fs.writeJson(path.join(backupFolder, `${name}.json`), deploymentData, { spaces: 2 });
24+
}
25+
26+
/**
27+
* Main function to deploy contracts and save their deployments.
28+
*/
29+
async function main() {
30+
console.log("\nDeploying contracts with Ignition...");
31+
const DeployFixture = require("../ignition/modules/DeployFixture");
32+
const result = await hre.ignition.deploy(DeployFixture);
33+
34+
try {
35+
for (const [name, contract] of Object.entries(result)) {
36+
console.log(`Saving ${name}...`);
37+
await saveDeployment(name, contract);
38+
}
39+
} catch (error) {
40+
console.error("Error saving deployment:", error);
41+
}
42+
}
43+
44+
main()
45+
.then(() => process.exit(0))
46+
.catch((error) => {
47+
console.error(error);
48+
process.exit(1);
49+
});

0 commit comments

Comments
 (0)