Skip to content

Commit

Permalink
chore: bump genvm version to v0.0.11, add contract deployment from IC (
Browse files Browse the repository at this point in the history
…#753)

* bump genvm version to v0.0.11, add contract deployment from IC
  • Loading branch information
kp2pml30 authored Dec 19, 2024
1 parent 839d6ec commit 9e29926
Show file tree
Hide file tree
Showing 23 changed files with 371 additions and 88 deletions.
43 changes: 39 additions & 4 deletions backend/consensus/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,14 +1163,49 @@ async def handle(self, context):
nonce = context.transactions_processor.get_transaction_count(
context.transaction.to_address
)
data: dict
transaction_type: TransactionType
if pending_transaction.is_deploy():
transaction_type = TransactionType.DEPLOY_CONTRACT
new_contract_address: str
if pending_transaction.salt_nonce == 0:
# NOTE: this address is random, which doesn't 100% align with consensus spec
new_contract_address = (
context.accounts_manager.create_new_account().address
)
else:
from eth_utils.crypto import keccak
from backend.node.types import Address
from backend.node.base import SIMULATOR_CHAIN_ID

arr = bytearray()
arr.append(1)
arr.extend(Address(context.transaction.to_address).as_bytes)
arr.extend(
pending_transaction.salt_nonce.to_bytes(32, "big", signed=False)
)
arr.extend(SIMULATOR_CHAIN_ID.to_bytes(32, "big", signed=False))
new_contract_address = Address(keccak(arr)[:20]).as_hex
context.accounts_manager.create_new_account_with_address(
new_contract_address
)
pending_transaction.address = new_contract_address
data = {
"contract_address": new_contract_address,
"contract_code": pending_transaction.code,
"calldata": pending_transaction.calldata,
}
else:
transaction_type = TransactionType.RUN_CONTRACT
data = {
"calldata": pending_transaction.calldata,
}
context.transactions_processor.insert_transaction(
context.transaction.to_address, # new calls are done by the contract
pending_transaction.address,
{
"calldata": pending_transaction.calldata,
},
data,
value=0, # we only handle EOA transfers at the moment, so no value gets transferred
type=TransactionType.RUN_CONTRACT.value,
type=transaction_type.value,
nonce=nonce,
leader_only=context.transaction.leader_only, # Cascade
triggered_by_hash=context.transaction.hash,
Expand Down
2 changes: 1 addition & 1 deletion backend/database_handler/contract_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ContractSnapshot:

contract_address: str
contract_code: str
encoded_state: dict[str, dict[str, str]]
encoded_state: dict[str, str]

def __init__(self, contract_address: str | None, session: Session):
self.session = session
Expand Down
29 changes: 17 additions & 12 deletions backend/node/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from .types import Address

SIMULATOR_CHAIN_ID: typing.Final[int] = 61999


class _SnapshotView(genvmbase.StateProxy):
def __init__(
Expand All @@ -43,14 +45,14 @@ def _get_snapshot(self, addr: Address) -> ContractSnapshot:
return res

def get_code(self, addr: Address) -> bytes:
return self._get_snapshot(addr).contract_code.encode("utf-8")
return base64.b64decode(self._get_snapshot(addr).contract_code)

def storage_read(
self, account: Address, slot: bytes, index: int, le: int, /
) -> tuple[bytes, int]:
) -> bytes:
snap = self._get_snapshot(account)
for_acc = snap.encoded_state.setdefault(account.as_b64, {})
for_slot = for_acc.setdefault(base64.b64encode(slot).decode("ascii"), "")
slot_id = base64.b64encode(slot).decode("ascii")
for_slot = snap.encoded_state.setdefault(slot_id, "")
data = bytearray(base64.b64decode(for_slot))
data.extend(b"\x00" * (index + le - len(data)))
return data[index : index + le]
Expand All @@ -66,14 +68,13 @@ def storage_write(
assert account == self.contract_address
assert not self.readonly
snap = self._get_snapshot(account)
for_acc = snap.encoded_state.setdefault(account.as_b64, {})
slot_id = base64.b64encode(slot).decode("ascii")
for_slot = for_acc.setdefault(slot_id, "")
for_slot = snap.encoded_state.setdefault(slot_id, "")
data = bytearray(base64.b64decode(for_slot))
mem = memoryview(got)
data.extend(b"\x00" * (index + len(mem) - len(data)))
data[index : index + len(mem)] = mem
for_acc[slot_id] = base64.b64encode(data).decode("utf-8")
snap.encoded_state[slot_id] = base64.b64encode(data).decode("utf-8")


class Node:
Expand Down Expand Up @@ -102,10 +103,11 @@ async def exec_transaction(self, transaction: Transaction) -> Receipt:
transaction_data = transaction.data
assert transaction.from_address is not None
if transaction.type == TransactionType.DEPLOY_CONTRACT:
code = base64.b64decode(transaction_data["contract_code"])
calldata = base64.b64decode(transaction_data["calldata"])
receipt = await self.deploy_contract(
transaction.from_address,
transaction_data["contract_code"],
code,
calldata,
transaction.hash,
transaction.created_at,
Expand Down Expand Up @@ -149,13 +151,15 @@ def _date_from_str(self, date: str | None) -> datetime.datetime | None:
async def deploy_contract(
self,
from_address: str,
code_to_deploy: str,
code_to_deploy: bytes,
calldata: bytes,
transaction_hash: str,
transaction_created_at: str | None = None,
) -> Receipt:
assert self.contract_snapshot is not None
self.contract_snapshot.contract_code = code_to_deploy
self.contract_snapshot.contract_code = base64.b64encode(code_to_deploy).decode(
"ascii"
)
return await self._run_genvm(
from_address,
calldata,
Expand Down Expand Up @@ -220,9 +224,9 @@ async def _execution_finished(
)
)

async def get_contract_schema(self, code: str) -> str:
async def get_contract_schema(self, code: bytes) -> str:
genvm = self._create_genvm()
res = await genvm.get_contract_schema(code.encode("utf-8"))
res = await genvm.get_contract_schema(code)
await self._execution_finished(res, None)
err_data = {
"stdout": res.stdout,
Expand Down Expand Up @@ -298,6 +302,7 @@ async def _run_genvm(
leader_results=leader_res,
config=json.dumps(config),
date=transaction_datetime,
chain_id=SIMULATOR_CHAIN_ID,
)
await self._execution_finished(res, transaction_hash)

Expand Down
49 changes: 42 additions & 7 deletions backend/node/genvm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ async def run_contract(
leader_results: None | dict[int, bytes],
config: str,
date: datetime.datetime | None,
chain_id: int,
) -> ExecutionResult: ...

async def get_contract_schema(self, contract_code: bytes) -> ExecutionResult: ...
Expand Down Expand Up @@ -137,15 +138,20 @@ async def run_contract(
leader_results: None | dict[int, bytes],
config: str,
date: datetime.datetime | None,
chain_id: int,
) -> ExecutionResult:
assert date.tzinfo is not None
message = {
"is_init": is_init,
"contract_account": contract_address.as_b64,
"sender_account": from_address.as_b64,
"origin_account": from_address.as_b64, # FIXME: no origin in simulator #751
"value": None,
"chain_id": str(
chain_id
), # NOTE: it can overflow u64 so better to wrap it into a string
}
if date is not None:
assert date.tzinfo is not None
message["datetime"] = date.isoformat()
return await _run_genvm_host(
functools.partial(
Expand All @@ -164,8 +170,9 @@ async def get_contract_schema(self, contract_code: bytes) -> ExecutionResult:
"is_init": False,
"contract_account": NO_ADDR,
"sender_account": NO_ADDR,
"origin_account": NO_ADDR,
"value": None,
"gas": 2**64 - 1,
"chain_id": "0",
}
return await _run_genvm_host(
functools.partial(
Expand Down Expand Up @@ -227,9 +234,13 @@ async def get_calldata(self, /) -> bytes:
async def get_code(self, addr: bytes, /) -> bytes:
return self._state_proxy.get_code(Address(addr))

def has_result(self) -> bool:
return self._result is not None

async def storage_read(
self, account: bytes, slot: bytes, index: int, le: int, /
self, type: StorageType, account: bytes, slot: bytes, index: int, le: int, /
) -> bytes:
assert type != StorageType.LATEST_FINAL
return self._state_proxy.storage_read(Address(account), slot, index, le)

async def storage_write(
Expand Down Expand Up @@ -273,16 +284,40 @@ async def post_nondet_result(
encoded_result.extend(memoryview(data))
self._eq_outputs[call_no] = bytes(encoded_result)

async def post_message(
self, gas: int, account: bytes, calldata: bytes, code: bytes, /
) -> None:
async def post_message(self, account: bytes, calldata: bytes, _data, /) -> None:
self._pending_transactions.append(
PendingTransaction(Address(account).as_hex, calldata)
PendingTransaction(
Address(account).as_hex, calldata, code=None, salt_nonce=0
)
)

async def consume_gas(self, gas: int, /) -> None:
pass

async def deploy_contract(
self,
calldata: bytes,
code: bytes,
data: genvmhost.DeployDefaultTransactionData,
/,
) -> None:
self._pending_transactions.append(
PendingTransaction(
address="0x",
calldata=calldata,
code=code,
salt_nonce=data.get("salt_nonce", 0),
)
)

async def eth_send(self, account: bytes, calldata: bytes, /) -> None:
# FIXME(core-team): #748
assert False

async def eth_call(self, account: bytes, calldata: bytes, /) -> bytes:
# FIXME(core-team): #748
assert False


async def _run_genvm_host(
host_supplier: typing.Callable[[socket.socket], _Host],
Expand Down
Loading

0 comments on commit 9e29926

Please sign in to comment.