Skip to content

Commit a49d54c

Browse files
kp2pml30cristiam86
andauthored
feat: update genvm to 0.0.14 (#904)
* feat: update genvm to 0.0.14 * fix: update integration of balances and messages for genvm --------- Co-authored-by: Cristiam Da Silva <cristiam86@gmail.com>
1 parent d9297e2 commit a49d54c

25 files changed

+321
-156
lines changed

backend/consensus/base.py

+71-55
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import asyncio
88
from collections import deque
99
import traceback
10-
from typing import Callable, Iterator, List
10+
from typing import Callable, Iterator, List, Iterable, Literal
1111
import time
1212
from abc import ABC, abstractmethod
1313
import threading
@@ -29,7 +29,13 @@
2929
Validator,
3030
)
3131
from backend.node.base import Node
32-
from backend.node.types import ExecutionMode, Receipt, Vote, ExecutionResultStatus
32+
from backend.node.types import (
33+
ExecutionMode,
34+
Receipt,
35+
Vote,
36+
ExecutionResultStatus,
37+
PendingTransaction,
38+
)
3339
from backend.protocol_rpc.message_handler.base import MessageHandler
3440
from backend.protocol_rpc.message_handler.types import (
3541
LogEvent,
@@ -107,6 +113,7 @@ def contract_snapshot_factory(
107113
ret = ContractSnapshot(None, session)
108114
ret.contract_address = transaction.to_address
109115
ret.contract_code = transaction.data["contract_code"]
116+
ret.balance = transaction.value or 0
110117
ret.states = {"accepted": {}, "finalized": {}}
111118
ret.encoded_state = ret.states["accepted"]
112119
return ret
@@ -1861,6 +1868,8 @@ async def handle(self, context):
18611868
accepted_state=leader_receipt.contract_state
18621869
)
18631870

1871+
_emit_transactions(context, leader_receipt.pending_transactions, "accepted")
1872+
18641873
# Set the transaction appeal undetermined status to false and return appeal status
18651874
if context.transaction.appeal_undetermined:
18661875
context.transactions_processor.set_transaction_appeal_undetermined(
@@ -2009,62 +2018,69 @@ async def handle(self, context):
20092018

20102019
if context.transaction.status != TransactionStatus.UNDETERMINED:
20112020
# Insert pending transactions generated by contract-to-contract calls
2012-
pending_transactions = (
2013-
context.transaction.consensus_data.leader_receipt.pending_transactions
2021+
_emit_transactions(
2022+
context,
2023+
context.transaction.consensus_data.leader_receipt.pending_transactions,
2024+
"finalized",
20142025
)
2015-
for pending_transaction in pending_transactions:
2016-
nonce = context.transactions_processor.get_transaction_count(
2017-
context.transaction.to_address
2026+
2027+
2028+
def _emit_transactions(
2029+
context: TransactionContext,
2030+
pending_transactions: Iterable[PendingTransaction],
2031+
on: Literal["accepted", "finalized"],
2032+
):
2033+
for pending_transaction in filter(lambda t: t.on == on, pending_transactions):
2034+
nonce = context.transactions_processor.get_transaction_count(
2035+
context.transaction.to_address
2036+
)
2037+
data: dict
2038+
transaction_type: TransactionType
2039+
if pending_transaction.is_deploy():
2040+
transaction_type = TransactionType.DEPLOY_CONTRACT
2041+
new_contract_address: str
2042+
if pending_transaction.salt_nonce == 0:
2043+
# NOTE: this address is random, which doesn't 100% align with consensus spec
2044+
new_contract_address = (
2045+
context.accounts_manager.create_new_account().address
20182046
)
2019-
data: dict
2020-
transaction_type: TransactionType
2021-
if pending_transaction.is_deploy():
2022-
transaction_type = TransactionType.DEPLOY_CONTRACT
2023-
new_contract_address: str
2024-
if pending_transaction.salt_nonce == 0:
2025-
# NOTE: this address is random, which doesn't 100% align with consensus spec
2026-
new_contract_address = (
2027-
context.accounts_manager.create_new_account().address
2028-
)
2029-
else:
2030-
from eth_utils.crypto import keccak
2031-
from backend.node.types import Address
2032-
from backend.node.base import SIMULATOR_CHAIN_ID
2033-
2034-
arr = bytearray()
2035-
arr.append(1)
2036-
arr.extend(Address(context.transaction.to_address).as_bytes)
2037-
arr.extend(
2038-
pending_transaction.salt_nonce.to_bytes(
2039-
32, "big", signed=False
2040-
)
2041-
)
2042-
arr.extend(SIMULATOR_CHAIN_ID.to_bytes(32, "big", signed=False))
2043-
new_contract_address = Address(keccak(arr)[:20]).as_hex
2044-
context.accounts_manager.create_new_account_with_address(
2045-
new_contract_address
2046-
)
2047-
pending_transaction.address = new_contract_address
2048-
data = {
2049-
"contract_address": new_contract_address,
2050-
"contract_code": pending_transaction.code,
2051-
"calldata": pending_transaction.calldata,
2052-
}
2053-
else:
2054-
transaction_type = TransactionType.RUN_CONTRACT
2055-
data = {
2056-
"calldata": pending_transaction.calldata,
2057-
}
2058-
context.transactions_processor.insert_transaction(
2059-
context.transaction.to_address, # new calls are done by the contract
2060-
pending_transaction.address,
2061-
data,
2062-
value=0, # we only handle EOA transfers at the moment, so no value gets transferred
2063-
type=transaction_type.value,
2064-
nonce=nonce,
2065-
leader_only=context.transaction.leader_only, # Cascade
2066-
triggered_by_hash=context.transaction.hash,
2047+
else:
2048+
from eth_utils.crypto import keccak
2049+
from backend.node.types import Address
2050+
from backend.node.base import SIMULATOR_CHAIN_ID
2051+
2052+
arr = bytearray()
2053+
arr.append(1)
2054+
arr.extend(Address(context.transaction.to_address).as_bytes)
2055+
arr.extend(
2056+
pending_transaction.salt_nonce.to_bytes(32, "big", signed=False)
2057+
)
2058+
arr.extend(SIMULATOR_CHAIN_ID.to_bytes(32, "big", signed=False))
2059+
new_contract_address = Address(keccak(arr)[:20]).as_hex
2060+
context.accounts_manager.create_new_account_with_address(
2061+
new_contract_address
20672062
)
2063+
pending_transaction.address = new_contract_address
2064+
data = {
2065+
"contract_address": new_contract_address,
2066+
"contract_code": pending_transaction.code,
2067+
"calldata": pending_transaction.calldata,
2068+
}
2069+
else:
2070+
transaction_type = TransactionType.RUN_CONTRACT
2071+
data = {
2072+
"calldata": pending_transaction.calldata,
2073+
}
2074+
context.transactions_processor.insert_transaction(
2075+
context.transaction.to_address, # new calls are done by the contract
2076+
pending_transaction.address,
2077+
data,
2078+
value=0, # we only handle EOA transfers at the moment, so no value gets transferred
2079+
type=transaction_type.value,
2080+
nonce=nonce,
2081+
leader_only=context.transaction.leader_only, # Cascade
2082+
triggered_by_hash=context.transaction.hash,
2083+
)
20682084

20692085

20702086
def rotate(nodes: list) -> Iterator[list]:

backend/database_handler/contract_snapshot.py

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class ContractSnapshot:
1515
contract_address: str
1616
contract_code: str
1717
encoded_state: dict[str, str]
18+
balance: int
1819
states: dict[str, dict[str, str]]
1920
ghost_contract_address: str | None
2021

@@ -27,6 +28,8 @@ def __init__(self, contract_address: str | None, session: Session):
2728
contract_account = self._load_contract_account()
2829
self.contract_data = contract_account.data
2930
self.contract_code = self.contract_data["code"]
31+
self.balance = contract_account.balance
32+
3033
if ("accepted" in self.contract_data["state"]) and (
3134
isinstance(self.contract_data["state"]["accepted"], dict)
3235
):
@@ -35,6 +38,7 @@ def __init__(self, contract_address: str | None, session: Session):
3538
# Convert old state format
3639
self.states = {"accepted": self.contract_data["state"], "finalized": {}}
3740
self.encoded_state = self.states["accepted"]
41+
3842
self.ghost_contract_address = (
3943
self.contract_data["ghost_contract_address"]
4044
if "ghost_contract_address" in self.contract_data

backend/node/base.py

+6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ def storage_write(
7676
data[index : index + len(mem)] = mem
7777
snap.encoded_state[slot_id] = base64.b64encode(data).decode("utf-8")
7878

79+
def get_balance(self, addr: Address) -> int:
80+
snap = self._get_snapshot(addr)
81+
# FIXME(core-team): it is not obvious where `value` is added to `self.balance`
82+
# but return must be increased by it
83+
return snap.balance
84+
7985

8086
class Node:
8187
def __init__(

backend/node/genvm/base.py

+44-15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import collections.abc
1515
import functools
1616
import datetime
17+
import abc
1718

1819
from backend.node.types import (
1920
PendingTransaction,
@@ -64,10 +65,12 @@ def encode_result_to_bytes(result: ExecutionReturn | ExecutionError) -> bytes:
6465

6566
# Interface for accessing the blockchain state, it is needed to not tangle current (awfully unoptimized)
6667
# storage format with the genvm source code
67-
class StateProxy(typing.Protocol):
68+
class StateProxy(metaclass=abc.ABCMeta):
69+
@abc.abstractmethod
6870
def storage_read(
6971
self, account: Address, slot: bytes, index: int, le: int, /
7072
) -> bytes: ...
73+
@abc.abstractmethod
7174
def storage_write(
7275
self,
7376
account: Address,
@@ -76,7 +79,10 @@ def storage_write(
7679
got: collections.abc.Buffer,
7780
/,
7881
) -> None: ...
82+
@abc.abstractmethod
7983
def get_code(self, addr: Address) -> bytes: ...
84+
@abc.abstractmethod
85+
def get_balance(self, addr: Address) -> int: ...
8086

8187

8288
# GenVM protocol just in case it is needed for mocks or bringing back the old one
@@ -124,6 +130,9 @@ def get_code(self, addr: Address) -> bytes:
124130
assert addr == self.my_address
125131
return self.code
126132

133+
def get_balance(self, addr: Address) -> int:
134+
return 0
135+
127136

128137
# Actual genvm wrapper that will start process and handle all communication
129138
class GenVMHost(IGenVM):
@@ -143,9 +152,9 @@ async def run_contract(
143152
) -> ExecutionResult:
144153
message = {
145154
"is_init": is_init,
146-
"contract_account": contract_address.as_b64,
147-
"sender_account": from_address.as_b64,
148-
"origin_account": from_address.as_b64, # FIXME: no origin in simulator #751
155+
"contract_address": contract_address.as_b64,
156+
"sender_address": from_address.as_b64,
157+
"origin_address": from_address.as_b64, # FIXME: no origin in simulator #751
149158
"value": None,
150159
"chain_id": str(
151160
chain_id
@@ -154,7 +163,7 @@ async def run_contract(
154163
if date is not None:
155164
assert date.tzinfo is not None
156165
message["datetime"] = date.isoformat()
157-
perms = "rc" # read/call
166+
perms = "rcn" # read/call/spawn nondet
158167
if not readonly:
159168
perms += "ws" # write/send
160169
return await _run_genvm_host(
@@ -172,16 +181,16 @@ async def get_contract_schema(self, contract_code: bytes) -> ExecutionResult:
172181
NO_ADDR = str(base64.b64encode(b"\x00" * 20), encoding="ascii")
173182
message = {
174183
"is_init": False,
175-
"contract_account": NO_ADDR,
176-
"sender_account": NO_ADDR,
177-
"origin_account": NO_ADDR,
184+
"contract_address": NO_ADDR,
185+
"sender_address": NO_ADDR,
186+
"origin_address": NO_ADDR,
178187
"value": None,
179188
"chain_id": "0",
180189
}
181190
return await _run_genvm_host(
182191
functools.partial(
183192
_Host,
184-
calldata_bytes=calldata.encode({"method": "__get_schema__"}),
193+
calldata_bytes=calldata.encode({"method": "#get-schema"}),
185194
state_proxy=_StateProxyNone(Address(NO_ADDR), contract_code),
186195
leader_results=None,
187196
),
@@ -283,11 +292,14 @@ async def consume_result(
283292

284293
async def get_leader_nondet_result(
285294
self, call_no: int, /
286-
) -> tuple[ResultCode, collections.abc.Buffer] | None:
295+
) -> tuple[ResultCode, collections.abc.Buffer] | ResultCode:
287296
leader_results = self._leader_results
288297
if leader_results is None:
289-
return None
290-
leader_results_mem = memoryview(leader_results[call_no])
298+
return ResultCode.NONE
299+
res = leader_results.get(call_no, None)
300+
if res is None:
301+
return ResultCode.NO_LEADERS
302+
leader_results_mem = memoryview(res)
291303
return (ResultCode(leader_results_mem[0]), leader_results_mem[1:])
292304

293305
async def post_nondet_result(
@@ -298,10 +310,19 @@ async def post_nondet_result(
298310
encoded_result.extend(memoryview(data))
299311
self._eq_outputs[call_no] = bytes(encoded_result)
300312

301-
async def post_message(self, account: bytes, calldata: bytes, _data, /) -> None:
313+
async def post_message(
314+
self, account: bytes, calldata: bytes, data: genvmhost.DefaultTransactionData, /
315+
) -> None:
316+
on = data.get("on", "finalized")
317+
value = int(data.get("value", "0x0"), 16)
302318
self._pending_transactions.append(
303319
PendingTransaction(
304-
Address(account).as_hex, calldata, code=None, salt_nonce=0
320+
Address(account).as_hex,
321+
calldata,
322+
code=None,
323+
salt_nonce=0,
324+
value=value,
325+
on=on,
305326
)
306327
)
307328

@@ -315,12 +336,17 @@ async def deploy_contract(
315336
data: genvmhost.DeployDefaultTransactionData,
316337
/,
317338
) -> None:
339+
on = data.get("on", "finalized")
340+
value = int(data.get("value", "0x0"), 16)
341+
salt_nonce = int(data.get("salt_nonce", "0x0"), 16)
318342
self._pending_transactions.append(
319343
PendingTransaction(
320344
address="0x",
321345
calldata=calldata,
322346
code=code,
323-
salt_nonce=data.get("salt_nonce", 0),
347+
salt_nonce=salt_nonce,
348+
value=value,
349+
on=on,
324350
)
325351
)
326352

@@ -332,6 +358,9 @@ async def eth_call(self, account: bytes, calldata: bytes, /) -> bytes:
332358
# FIXME(core-team): #748
333359
assert False
334360

361+
async def get_balance(self, account: bytes, /) -> int:
362+
return self._state_proxy.get_balance(Address(account))
363+
335364

336365
async def _run_genvm_host(
337366
host_supplier: typing.Callable[[socket.socket], _Host],

0 commit comments

Comments
 (0)