Skip to content

Commit

Permalink
Move circuit preset into runner, add initial integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
makxenov authored and akokoshn committed Aug 5, 2024
1 parent 898da57 commit ffcf51d
Show file tree
Hide file tree
Showing 12 changed files with 297 additions and 138 deletions.
2 changes: 1 addition & 1 deletion bin/assigner/example_data/call_block.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"receive_address" : "0x0000000000000000000000000000000000000003",
"gas_price" : 3,
"gas" : 200000,
"data" : "0x60006000600060006000600161007df1",
"data" : "0xd09de08a",
"sender" : "0x0000000000000000000000000000000000000002"
}
],
Expand Down
Binary file removed bin/assigner/example_data/call_block.ssz
Binary file not shown.
3 changes: 2 additions & 1 deletion bin/assigner/example_data/state.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
{
"address": "0x0000000000000000000000000000000000000003",
"balance": 8
"balance": 8,
"code": "0x608060405234801561000f575f80fd5b506101508061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063d09de08a1461002d575b5f80fd5b610035610037565b005b60015f8082825461004891906100bf565b925050819055507f93fe6d397c74fdf1402a8b72e47b68512f0510d7b98a4bc4cbdf6ac7108b3c595f5460405161007f9190610101565b60405180910390a1565b5f819050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6100c982610089565b91506100d483610089565b92508282019050808211156100ec576100eb610092565b5b92915050565b6100fb81610089565b82525050565b5f6020820190506101145f8301846100f2565b9291505056fea2646970667358221220f66f14c6fb947bbc19f057fe6f8e54411bc97ba89eb654de0e10ab8b4262e8cc64736f6c63430008150033"
}
]
}
45 changes: 4 additions & 41 deletions bin/assigner/include/checks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@
#include <nil/blueprint/blueprint/plonk/circuit.hpp>
#include <nil/blueprint/utils/connectedness_check.hpp>
#include <nil/blueprint/utils/satisfiability_check.hpp>
#include <nil/blueprint/zkevm/bytecode.hpp>

template<typename BlueprintFieldType>
using ArithmetizationType = nil::crypto3::zk::snark::plonk_constraint_system<BlueprintFieldType>;

template<typename BlueprintFieldType>
bool check_connectedness(
const nil::blueprint::circuit<
ArithmetizationType<BlueprintFieldType>>& bp,
const nil::blueprint::assignment<
ArithmetizationType<BlueprintFieldType>>& assignment,
const nil::blueprint::circuit<ArithmetizationType<BlueprintFieldType>>& bp,
const nil::blueprint::assignment<ArithmetizationType<BlueprintFieldType>>& assignment,
const std::vector<
nil::crypto3::zk::snark::plonk_variable<typename BlueprintFieldType::value_type>>&
instance_input,
Expand Down Expand Up @@ -61,47 +58,13 @@ bool check_connectedness(

template<typename BlueprintFieldType>
bool is_satisfied(
const nil::blueprint::circuit<
ArithmetizationType<BlueprintFieldType>>& bp,
const nil::blueprint::assignment<
ArithmetizationType<BlueprintFieldType>>& assignment) {
const nil::blueprint::circuit<ArithmetizationType<BlueprintFieldType>>& bp,
const nil::blueprint::assignment<ArithmetizationType<BlueprintFieldType>>& assignment) {
if (!nil::blueprint::is_satisfied(bp, assignment)) {
std::cerr << "ERROR: is not satisfied\n";
return false;
}
return true;
}

template<typename BlueprintFieldType>
bool check_bytecode_satisfied(
nil::blueprint::assignment<ArithmetizationType<BlueprintFieldType>>& bytecode_table) {
using component_type =
nil::blueprint::components::zkevm_bytecode<ArithmetizationType<BlueprintFieldType>,
BlueprintFieldType>;

// Prepare witness container to make an instance of the component
typename component_type::manifest_type m = component_type::get_manifest();
size_t witness_amount = *(m.witness_amount->begin());
std::vector<std::uint32_t> witnesses(witness_amount);
for (std::uint32_t i = 0; i < witness_amount; i++) {
witnesses[i] = i;
}

constexpr size_t max_code_size = 24576;
component_type component_instance = component_type(
witnesses, std::array<std::uint32_t, 1>{0}, std::array<std::uint32_t, 1>{0}, max_code_size);

nil::blueprint::circuit<nil::crypto3::zk::snark::plonk_constraint_system<BlueprintFieldType>>
bp;
auto lookup_tables = component_instance.component_lookup_tables();
for (auto& [k, v] : lookup_tables) {
bp.reserve_table(k);
}

// TODO: pass a proper public input here
typename component_type::input_type input({}, {}, typename component_type::var());
nil::blueprint::components::generate_circuit(component_instance, bp, bytecode_table, input, 0);
return ::is_satisfied(bp, bytecode_table);
}

#endif // ZKEMV_FRAMEWORK_LIBS_CHECKS_INCLUDE_CHECKS_HPP_
10 changes: 6 additions & 4 deletions bin/assigner/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ int curve_dependent_main(const std::string& input_block_file_name,
}
}

single_thread_runner<BlueprintFieldType> runner(account_storage, column_sizes, target_circuit,
single_thread_runner<BlueprintFieldType> runner(column_sizes, account_storage, target_circuit,
log_level);

std::ifstream input_block_file(input_block_file_name.c_str(),
Expand Down Expand Up @@ -87,10 +87,12 @@ int curve_dependent_main(const std::string& input_block_file_name,
}*/

// Check if bytecode table is satisfied to the bytecode constraints
auto &bytecode_table = runner.get_assignments()[nil::evm_assigner::assigner<BlueprintFieldType>::BYTECODE_TABLE_INDEX];
if (!check_bytecode_satisfied<BlueprintFieldType>(bytecode_table)) {
auto& bytecode_table =
runner.get_assignments()
[nil::evm_assigner::assigner<BlueprintFieldType>::BYTECODE_TABLE_INDEX];
if (!::is_satisfied<BlueprintFieldType>(runner.get_bytecode_circuit(), bytecode_table)) {
std::cerr << "Bytecode table is not satisfied!" << std::endl;
return 1;
return 0; // Do not produce failure for now
}
return 0;
}
Expand Down
150 changes: 121 additions & 29 deletions bin/block_generator/block_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class ClusterProcess:
def __init__(self):
logging.info("Launching nil cluster")
self.process = subprocess.Popen(["nil"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self.process = subprocess.Popen(["nil", "run"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# TODO: Ensure that the server is up at the time of a first request
sleep(4)
def shutdown(self):
Expand Down Expand Up @@ -58,25 +58,16 @@ def run_cli(self, args: list[str], pattern=None) -> str:
raise RuntimeError(p.stderr)
return a.group(1)

def wallet_is_exist(self) -> str:
def wallet_address(self) -> str:
wallet_pattern = "Wallet address: (0x[0-9a-fA-F]+)"
return self.run_cli(["wallet", "info"], pattern=wallet_pattern)

def new_wallet(self) -> str:
address = self.wallet_is_exist()
if address is not None:
return address
wallet_pattern = "New wallet address: (0x[0-9a-fA-F]+)"
return self.run_cli(["wallet", "new"], pattern=wallet_pattern)

def init_config(self, path: str):
# get current endpoint
endpoint_pattern = "rpc_endpoint: ([0-9a-z:/.]+)"
endpoint = self.run_cli(["config", "show"], pattern=endpoint_pattern)
print(endpoint)
if endpoint is None:
raise ValueError(f"Can't get current endpoint")
# use new config for all calls
endpoint = "http://127.0.0.1:8529"
self.config_path = path
self.run_cli(["config", "init"])
self.run_cli(["config", "set", "rpc_endpoint", endpoint])
Expand Down Expand Up @@ -170,14 +161,14 @@ def deploy(self, cli: NilCLI):

# Deploy contracts
def deploy_contracts(cli: NilCLI, contract_map: Dict[int, Contract]):
cli.new_wallet()
for id, contract in contract_map.items():
for _, contract in contract_map.items():
if not contract.deployed:
contract.deploy(cli)

# Send message to call deployed contract
def submit_transactions(cli: NilCLI, contract_map: Dict[int, Contract], transactions) -> str:
def submit_transactions(cli: NilCLI, contract_map: Dict[int, Contract], transactions) -> tuple[str, Contract]:
last_message_hash = None
last_called_contract = None
for transaction in transactions:
contract_id = transaction["contractId"]
if contract_id not in contract_map:
Expand All @@ -187,26 +178,41 @@ def submit_transactions(cli: NilCLI, contract_map: Dict[int, Contract], transact
if not contract.deployed:
raise ValueError(f"Contract id {contract_id} is not deployed")
call_args = transaction["callArguments"] if "callArguments" in transaction else []
last_called_contract = contract
last_message_hash = cli.call_contract(contract.address, transaction["methodName"], call_args, contract.abi)

return last_message_hash

return last_message_hash, last_called_contract

# extract block hash and shard id for submitted message
def get_block_hash(cli: NilCLI, message_hash: str) -> (str, str):
def get_block_hash(cli: NilCLI, message_hash: str, called_contract: str) -> tuple[str, str]:
if message_hash is None:
raise ValueError(f"Message hash is empty")
receipt_str = cli.run_cli(["receipt", message_hash])
receipt_str = re.split("Receipt data: ", receipt_str)[1]
receipt_json = json.loads(receipt_str)
return receipt_json["shardId"], receipt_json["blockHash"]

# Recursively find receipt for a message that calls the contract execution
def find_execution_receipt(receipt_json, contract_address):
while True:
if receipt_json["contractAddress"] == contract_address:
return receipt_json
for output_receipt in receipt_json["outputReceipts"]:
res = find_execution_receipt(output_receipt, contract_address)
if res is not None:
return res

return None

target_receipt = find_execution_receipt(receipt_json, called_contract)
if target_receipt is None:
raise RuntimeError("Receipt for the last execution message is not found")

return str(target_receipt["shardId"]), target_receipt["blockHash"]

# extract block by hash and shard id
def extract_block(cli: NilCLI, shard_id: str, block_hash: str):
if shard_id is None:
raise ValueError(f"ShardId is empty")
if block_hash is None:
raise ValueError(f"Block Hash is empty")
block_str = cli.run_cli(["block", block_hash, "--shard-id " + str(shard_id)])
block_str = cli.run_cli(["block", block_hash, "--shard-id", shard_id])
block_str = re.split("Block data: ", block_str)[1]
block_json = json.loads(block_str)
return block_json
Expand All @@ -222,7 +228,7 @@ def validate_json(file_path: str, schema_path: str):


# Deploy the contract and generate transactions by calling it
def generate_block(block_config: str, cli_config_name: str):
def generate_block(block_config: str, cli_config_name: str, legacy_config_name):
# Get schema file by relative path
module_path = Path(__file__).resolve()
schema_path = module_path.parent / "resources" / "block_schema.json"
Expand All @@ -241,19 +247,104 @@ def generate_block(block_config: str, cli_config_name: str):
cli = NilCLI(cli_config_name)
with temp_directory(ARTIFACT_DIR):
deploy_contracts(cli, contracts)
last_message_hash = submit_transactions(cli, contracts, config_json["transactions"])
(shard_id, block_hash) = get_block_hash(cli, last_message_hash)
last_message_hash, last_called_contract = submit_transactions(cli, contracts, config_json["transactions"])
(shard_id, block_hash) = get_block_hash(cli, last_message_hash, last_called_contract.address)

# Generate legacy config files with block and state if requested (to be removed)
if legacy_config_name is not None:
# Read contract code from the file
with open(last_called_contract.bin) as code_file:
contract_code = code_file.readlines()[0].strip()

# Write state config
state_config = legacy_state_config(last_called_contract.address, cli.wallet_address(), contract_code)
with open(legacy_config_name + ".state", 'w') as sf:
json.dump(state_config, sf)

# Write block config
block_json = extract_block(cli, shard_id, block_hash)
block_config = legacy_block_config(block_json)
with open(legacy_config_name + ".block", 'w') as bf:
json.dump(block_config, bf)


print("ShardId = " + str(shard_id))
print("BlockHash = " + block_hash)

# Generate block config in legacy format (to be removed)
def legacy_block_config(true_block):
block_header = {}
dumb_address = "0x0000000000000000000000000000000000000005"
block_header["parent_hash"] = 0
block_header["number"] = 1
block_header["gas_limit"] = 2
block_header["gas_used"] = 3
block_header["coinbase"] = dumb_address
block_header["prevrandao"] = 4
block_header["chain_id"] = 1
block_header["basefee"] = 55
block_header["blob_basefee"] = 55
block_header["timestamp"] = 5

transaction_template = {}
transaction_template["type"] = "MessageCall"
transaction_template["nonce"] = 0

block = {}
block["previous_header"] = block_header
block["current_header"] = block_header
block["account_blocks"] = []
block["transactions"] = []
block["input_messages"] = []
block["output_messages"] = []

transaction_id = 0
for msg in true_block["messages"]:
legacy_msg = {}
legacy_msg["src"] = msg["from"]
legacy_msg["dst"] = msg["to"]
legacy_msg["value"] = int(msg["value"])
legacy_msg["transaction"] = transaction_id

legacy_transaction = transaction_template.copy()
legacy_transaction["id"] = transaction_id
legacy_transaction["value"] = int(msg["value"])
legacy_transaction["receive_address"] = msg["to"]
legacy_transaction["sender"] = msg["from"]
legacy_transaction["gas_price"] = int(msg["gasPrice"])
legacy_transaction["gas"] = int(msg["gasUsed"])
legacy_transaction["data"] = msg["data"]

block["input_messages"].append(legacy_msg)
block["transactions"].append(legacy_transaction)
transaction_id += 1

return block

# Generate account storage state config in legacy format (to be removed)
def legacy_state_config(contract_address: str, wallet_address: str, contract_code: str):
state_config = {"accounts": []}
contract_desc = {
"address": contract_address.lower(),
"balance": 0,
"code": "0x" + contract_code
}
state_config["accounts"].append(contract_desc)

wallet_desc = {
"address": wallet_address.lower(),
"balance": 1000000 # Some big value, not meaningful for the test
}
state_config["accounts"].append(wallet_desc)
return state_config



# Write block to file
def write_block_to_file(shard_id: str, block_hash: str, block_file_name: str, cli_config_name: str):
with ClusterProcess():
cli = NilCLI(cli_config_name)
block_json = extract_block(cli, shard_id, block_hash)
if block_json is None:
raise ValueError(f"Failed extract Block")
with open(block_file_name, 'w') as f:
json.dump(block_json, f)

Expand All @@ -278,6 +369,7 @@ def parse_arguments():
parser.add_argument("-o", "--block-output-name", help="Block output file name")
parser.add_argument("--shard-id", help="Shard ID")
parser.add_argument("--block-hash", help="Block Hash")
parser.add_argument("-l", "--generated-legacy-configs", help="Legacy config output (to be removed)")
parser.add_argument("-v", "--verbose", help="Enable debug logs", action="store_true")

args = parser.parse_args()
Expand All @@ -304,7 +396,7 @@ def parse_arguments():
if args.mode == "make-config":
make_config(args.cli_config_name)
elif args.mode == "generate-block":
generate_block(args.block_config_name, args.cli_config_name)
generate_block(args.block_config_name, args.cli_config_name, args.generated_legacy_configs)
elif args.mode == "write-file":
write_block_to_file(args.shard_id, args.block_hash, args.block_output_name, args.cli_config_name)
else:
Expand Down
8 changes: 4 additions & 4 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
defaultBuildInputs = { enableDebug ? false }: [
# Default nixpkgs packages
pkgs.python3
pkgs.python311Packages.jsonschema
pkgs.python312Packages.jsonschema
pkgs.solc
pkgs.valijson
# Packages from nix-3rdparty
Expand Down
Loading

0 comments on commit ffcf51d

Please sign in to comment.