Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hardhat): add snapshot system for state persistence #1031

Merged
merged 17 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b7ee74d
feat(hardhat): add snapshot system for state persistence
mpaya5 Mar 27, 2025
3624511
feat(hardhat): add snapshot system for state persistence
mpaya5 Mar 27, 2025
6d9850f
feat(hardhat): configure custom network with chainId 61999
mpaya5 Mar 28, 2025
f821ab3
feat(hardhat): configure custom network with chainId 61999
mpaya5 Mar 28, 2025
8343844
feat(hardhat): configure custom network with chainId 61999
mpaya5 Mar 28, 2025
8624ba2
Merge branch 'main' into feat/add-snapshots-to-hardhat-and-optional-s…
mpaya5 Mar 31, 2025
821d08d
feat(tests): adapt contract examples to handle consensus events
mpaya5 Apr 1, 2025
bb20436
Merge branch 'feat/add-snapshots-to-hardhat-and-optional-service' of …
mpaya5 Apr 1, 2025
df3f29f
Merge branch 'main' into feat/add-snapshots-to-hardhat-and-optional-s…
mpaya5 Apr 1, 2025
c2308c0
feat(tests): adapt contract examples to handle consensus events
mpaya5 Apr 1, 2025
e1f1118
feat(tests): adapt contract examples to handle consensus events
mpaya5 Apr 1, 2025
dfa1e7c
Merge branch 'feat/add-snapshots-to-hardhat-and-optional-service' of …
mpaya5 Apr 1, 2025
a540f65
docs(readme): add Hardhat node configuration instructions
mpaya5 Apr 1, 2025
07e5016
docs(readme): add Hardhat node configuration instructions
mpaya5 Apr 1, 2025
359f188
chore: increased waiting time for hardhat to be ready
cristiam86 Apr 1, 2025
586d3be
Merge remote-tracking branch 'origin/feat/add-snapshots-to-hardhat-an…
cristiam86 Apr 1, 2025
015c335
feat: avoid sending tx to rollup node if connection is not available
cristiam86 Apr 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,10 @@ HEURISTAIAPIKEY = '<add_your_heuristai_api_key_here>'
VALIDATORS_CONFIG_JSON = ''

# Consensus mechanism
VITE_FINALITY_WINDOW = 1800 # in seconds
VITE_FINALITY_WINDOW = 1800 # in seconds

# Set the compose profile to 'hardhat' to use the hardhat network
COMPOSE_PROFILES = 'hardhat'

# Hardhat chain ID
HARDHAT_CHAIN_ID = 61999
1 change: 1 addition & 0 deletions .github/workflows/backend_integration_tests_pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ jobs:

env:
PYTHONPATH: ${{ github.workspace }}
COMPOSE_PROFILES: hardhat

steps:
- name: Checkout code
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ $ genlayer up
```
After executing those commands a new tab will open in your browser with the GenLayer Studio. Additional installation instructions can be found [here](https://docs.genlayer.com/simulator/installation)

### Enabling Hardhat Node
If you need to interact with a local Hardhat node for transaction processing, make sure to add the following to your `.env` file:

```
HARDHAT_URL=http://hardhat
HARDHAT_PORT=8545
COMPOSE_PROFILES=hardhat
```

This will enable the Hardhat service when running `genlayer up`.

### Disabling Hardhat Node
If you need to disable the Hardhat node, make sure to remove the following from your `.env` file:

```
HARDHAT_URL=
HARDHAT_PORT=
COMPOSE_PROFILES=
```

This will disable the Hardhat service when running `genlayer up`.

## 🚀 Key Features
* 🖥️ **Test Locally:** Developers can test Intelligent Contracts in a local environment, replicating the GenLayer network without the need for deployment. This speeds up the development cycle and reduces the risk of errors in the live environment.

Expand Down
95 changes: 33 additions & 62 deletions backend/consensus/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from abc import ABC, abstractmethod
import threading
import random
from copy import deepcopy

from sqlalchemy.orm import Session
from backend.consensus.vrf import get_validators_for_transaction
Expand Down Expand Up @@ -629,7 +628,7 @@ def execute_transfer(
msg_handler,
)

transactions_processor.create_rollup_transaction(transaction.hash)
# transactions_processor.create_rollup_transaction(transaction.hash)
return

# Update the balance of the sender account
Expand All @@ -655,7 +654,7 @@ def execute_transfer(
msg_handler,
)

transactions_processor.create_rollup_transaction(transaction.hash)
# transactions_processor.create_rollup_transaction(transaction.hash)

def run_appeal_window_loop(
self,
Expand Down Expand Up @@ -992,16 +991,9 @@ async def process_leader_appeal(
transactions_processor.set_transaction_appeal(transaction.hash, False)
transaction.appealed = False

used_leader_addresses = (
ConsensusAlgorithm.get_used_leader_addresses_from_consensus_history(
context.transactions_processor.get_transaction_by_hash(
context.transaction.hash
)["consensus_history"]
)
)
if len(transaction.consensus_data.validators) + len(
used_leader_addresses
) >= len(chain_snapshot.get_all_validators()):
if len(transaction.consensus_data.validators) + 1 == len(
chain_snapshot.get_all_validators()
):
self.msg_handler.send_message(
LogEvent(
"consensus_event",
Expand Down Expand Up @@ -1035,12 +1027,6 @@ async def process_leader_appeal(
)
transaction.appeal_undetermined = True

context.contract_snapshot_supplier = (
lambda: context.contract_snapshot_factory(
context.transaction.to_address
)
)

# Begin state transitions starting from PendingState
state = PendingState()
while True:
Expand Down Expand Up @@ -1590,9 +1576,9 @@ async def handle(self, context):
context.msg_handler,
)

context.transactions_processor.create_rollup_transaction(
context.transaction.hash
)
# context.transactions_processor.create_rollup_transaction(
# context.transaction.hash
# )

# The leader is elected randomly
random.shuffle(context.involved_validators)
Expand All @@ -1604,21 +1590,16 @@ async def handle(self, context):
if context.transaction.leader_only:
context.remaining_validators = []

# Create a contract snapshot for the transaction if not exists
if context.transaction.contract_snapshot:
contract_snapshot = deepcopy(context.transaction.contract_snapshot)
else:
contract_snapshot_supplier = lambda: context.contract_snapshot_factory(
context.transaction.to_address
)
context.contract_snapshot_supplier = contract_snapshot_supplier
contract_snapshot = contract_snapshot_supplier()
# Create a contract snapshot for the transaction
contract_snapshot_supplier = lambda: context.contract_snapshot_factory(
context.transaction.to_address
)

# Create a leader node for executing the transaction
leader_node = context.node_factory(
leader,
ExecutionMode.LEADER,
contract_snapshot,
contract_snapshot_supplier(),
None,
context.msg_handler,
context.contract_snapshot_factory,
Expand All @@ -1638,6 +1619,7 @@ async def handle(self, context):

# Set the validators and other context attributes
context.num_validators = len(context.remaining_validators) + 1
context.contract_snapshot_supplier = contract_snapshot_supplier
context.votes = votes

# Transition to the CommittingState
Expand Down Expand Up @@ -1667,20 +1649,16 @@ async def handle(self, context):
context.msg_handler,
)

context.transactions_processor.create_rollup_transaction(
context.transaction.hash
)
# context.transactions_processor.create_rollup_transaction(
# context.transaction.hash
# )

# Create validator nodes for each validator
context.validator_nodes = [
context.node_factory(
validator,
ExecutionMode.VALIDATOR,
(
context.transaction.contract_snapshot
if context.transaction.contract_snapshot
else context.contract_snapshot_supplier()
),
context.contract_snapshot_supplier(),
context.consensus_data.leader_receipt,
context.msg_handler,
context.contract_snapshot_factory,
Expand Down Expand Up @@ -1727,9 +1705,9 @@ async def handle(self, context):
context.msg_handler,
)

context.transactions_processor.create_rollup_transaction(
context.transaction.hash
)
# context.transactions_processor.create_rollup_transaction(
# context.transaction.hash
# )

# Process each validation result and update the context
for i, validation_result in enumerate(context.validation_results):
Expand Down Expand Up @@ -1974,9 +1952,9 @@ async def handle(self, context):
context.validation_results,
)

context.transactions_processor.create_rollup_transaction(
context.transaction.hash
)
# context.transactions_processor.create_rollup_transaction(
# context.transaction.hash
# )

# Send a message indicating consensus was reached
context.msg_handler.send_message(
Expand All @@ -2002,10 +1980,9 @@ async def handle(self, context):
leaders_contract_snapshot = context.contract_snapshot_supplier()

# Set the contract snapshot for the transaction for a future rollback
if not context.transaction.contract_snapshot:
context.transactions_processor.set_transaction_contract_snapshot(
context.transaction.hash, leaders_contract_snapshot.to_dict()
)
context.transactions_processor.set_transaction_contract_snapshot(
context.transaction.hash, leaders_contract_snapshot.to_dict()
)

# Do not deploy or update the contract if the execution failed
if leader_receipt.execution_result == ExecutionResultStatus.SUCCESS:
Expand Down Expand Up @@ -2105,12 +2082,6 @@ async def handle(self, context):
else:
consensus_round = "Undetermined"

# Save the contract snapshot for potential future appeals
if not context.transaction.contract_snapshot:
context.transactions_processor.set_transaction_contract_snapshot(
context.transaction.hash, context.contract_snapshot_supplier().to_dict()
)

# Set the transaction result with the current consensus data
context.transactions_processor.set_transaction_result(
context.transaction.hash,
Expand Down Expand Up @@ -2138,9 +2109,9 @@ async def handle(self, context):
context.consensus_data.validators,
)

context.transactions_processor.create_rollup_transaction(
context.transaction.hash
)
# context.transactions_processor.create_rollup_transaction(
# context.transaction.hash
# )

return None

Expand Down Expand Up @@ -2183,9 +2154,9 @@ async def handle(self, context):
context.msg_handler,
)

context.transactions_processor.create_rollup_transaction(
context.transaction.hash
)
# context.transactions_processor.create_rollup_transaction(
# context.transaction.hash
# )

if context.transaction.status != TransactionStatus.UNDETERMINED:
# Insert pending transactions generated by contract-to-contract calls
Expand Down
17 changes: 12 additions & 5 deletions backend/database_handler/accounts_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ def create_new_account(self) -> Account:
self.create_new_account_with_address(account.address)
return account

def create_new_account_with_address(self, address: str):
if not self.is_valid_address(address):
raise ValueError(f"Invalid address: {address}")
account_state = CurrentState(id=address, data={})
self.session.add(account_state)
def create_new_account_with_address(self, address: str) -> Account:
# Check if account already exists
existing_account = (
self.session.query(CurrentState).filter(CurrentState.id == address).first()
)
if existing_account is not None:
return existing_account

# If account doesn't exist, create it
account = CurrentState(id=address, data="{}", balance=0)
self.session.add(account)
self.session.commit()
return account

def is_valid_address(self, address: str) -> bool:
return is_address(address)
Expand Down
17 changes: 13 additions & 4 deletions backend/protocol_rpc/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from flask_jsonrpc.exceptions import JSONRPCError
from sqlalchemy import Table
from sqlalchemy.orm import Session
import backend.node.genvm.origin.calldata as genvm_calldata

from backend.database_handler.contract_snapshot import ContractSnapshot
from backend.database_handler.llm_providers import LLMProviderRegistry
Expand Down Expand Up @@ -44,7 +43,6 @@
from backend.node.types import ExecutionMode, ExecutionResultStatus
from backend.consensus.base import ConsensusAlgorithm

from flask import request
from flask_jsonrpc.exceptions import JSONRPCError
import base64
import os
Expand Down Expand Up @@ -490,6 +488,10 @@ def send_raw_transaction(
if not transaction_signature_valid:
raise InvalidTransactionError("Transaction signature verification failed")

rollup_transaction_details = consensus_service.forward_transaction(
signed_rollup_transaction, from_address
)

to_address = decoded_rollup_transaction.to_address
nonce = decoded_rollup_transaction.nonce
value = decoded_rollup_transaction.value
Expand All @@ -506,7 +508,15 @@ def send_raw_transaction(
if value > 0:
raise InvalidTransactionError("Deploy Transaction can't send value")

new_contract_address = accounts_manager.create_new_account().address
if (
rollup_transaction_details is None
or not "recipient" in rollup_transaction_details
):
new_account = accounts_manager.create_new_account()
new_contract_address = new_account.address
else:
new_contract_address = rollup_transaction_details["recipient"]
accounts_manager.create_new_account_with_address(new_contract_address)

transaction_data = {
"contract_address": new_contract_address,
Expand Down Expand Up @@ -534,7 +544,6 @@ def send_raw_transaction(
nonce,
leader_only,
)
consensus_service.forward_transaction(signed_rollup_transaction)

return transaction_hash

Expand Down
Loading
Loading