From d86880ab12fd00c19d6b481e93eb694a4f594a89 Mon Sep 17 00:00:00 2001 From: oXtxNt9U <120286271+oXtxNt9U@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:23:15 +0900 Subject: [PATCH] feat(api-sync): populate username attribute (#795) * parse username contract events * populate username attribute * style: resolve style guide violations * read usernames from contract on restore * style: resolve style guide violations * Fix read commit --------- Co-authored-by: oXtxNt9U Co-authored-by: sebastijankuzner --- packages/api-sync/package.json | 3 ++ packages/api-sync/source/restore.ts | 41 ++++++++++++++++++- packages/api-sync/source/service.ts | 12 ++++++ .../source/generators/genesis-block.ts | 3 +- .../contracts/source/contracts/evm/evm.ts | 3 ++ packages/database/source/database-service.ts | 2 +- packages/evm-consensus/source/deployer.ts | 4 +- packages/evm-service/source/instances/evm.ts | 1 + packages/evm/bindings/src/ctx.rs | 3 ++ packages/evm/bindings/src/lib.rs | 1 + packages/evm/bindings/src/result.rs | 13 +++++- packages/evm/core/src/db.rs | 1 + packages/evm/core/src/events.rs | 3 ++ packages/evm/core/src/state_changes.rs | 4 ++ packages/evm/core/src/state_commit.rs | 27 ++++++++++++ pnpm-lock.yaml | 9 ++++ 16 files changed, 124 insertions(+), 6 deletions(-) diff --git a/packages/api-sync/package.json b/packages/api-sync/package.json index 5adbb1509..bf38a0813 100644 --- a/packages/api-sync/package.json +++ b/packages/api-sync/package.json @@ -25,8 +25,11 @@ "@mainsail/api-database": "workspace:*", "@mainsail/container": "workspace:*", "@mainsail/contracts": "workspace:*", + "@mainsail/evm-consensus": "workspace:*", + "@mainsail/evm-contracts": "workspace:*", "@mainsail/kernel": "workspace:*", "@mainsail/utils": "workspace:*", + "ethers": "^6.11.0", "joi": "17.12.2" }, "devDependencies": { diff --git a/packages/api-sync/source/restore.ts b/packages/api-sync/source/restore.ts index e9f61d897..e78c02702 100644 --- a/packages/api-sync/source/restore.ts +++ b/packages/api-sync/source/restore.ts @@ -5,8 +5,11 @@ import { } from "@mainsail/api-database"; import { inject, injectable, tagged } from "@mainsail/container"; import { Contracts, Identifiers } from "@mainsail/contracts"; +import { Identifiers as EvmConsensusIdentifiers } from "@mainsail/evm-consensus"; +import { UsernamesAbi } from "@mainsail/evm-contracts"; import { Utils } from "@mainsail/kernel"; import { chunk, validatorSetPack } from "@mainsail/utils"; +import { ethers } from "ethers"; import { performance } from "perf_hooks"; interface RestoreContext { @@ -107,6 +110,12 @@ export class Restore { @inject(Identifiers.Evm.ContractService.Consensus) private readonly consensusContractService!: Contracts.Evm.ConsensusContractService; + @inject(EvmConsensusIdentifiers.Contracts.Addresses.Usernames) + private readonly usernamesContractAddress!: string; + + @inject(EvmConsensusIdentifiers.Internal.Addresses.Deployer) + private readonly deployerAddress!: string; + public async restore(): Promise { const mostRecentCommit = await (this.databaseService.isEmpty() ? this.stateStore.getGenesisCommit() @@ -155,7 +164,7 @@ export class Restore { await this.#ingestBlocksAndTransactions(context); // 3) All `accounts` are read from the EVM storage and written to: - // - `wallets` table (NOTE: this does not include attributes yet) + // - `wallets` table await this.#ingestWallets(context); // 4) All `receipts` are read from the EVM storage and written to: @@ -338,6 +347,8 @@ export class Restore { const validatorAttributes = context.validatorAttributes[account.address]; const userAttributes = context.userAttributes[account.address]; + const username = await this.#readUsername(account.address); + accounts.push({ address: account.address, attributes: { @@ -369,7 +380,8 @@ export class Restore { ...(userAttributes ? { - vote: userAttributes.vote, + ...(userAttributes.vote ? { vote: userAttributes.vote } : {}), + ...(username ? { username } : {}), } : {}), }, @@ -535,4 +547,29 @@ export class Restore { async #updateValidatorRanks(context: RestoreContext): Promise { await context.entityManager.query("SELECT update_validator_ranks();", []); } + + async #readUsername(account: string): Promise { + const iface = new ethers.Interface(UsernamesAbi.abi); + const data = iface.encodeFunctionData("getUsername", [account]).slice(2); + + const { evmSpec } = this.configuration.getMilestone(0); + + const result = await this.evm.view({ + caller: this.deployerAddress, + data: Buffer.from(data, "hex"), + recipient: this.usernamesContractAddress, + specId: evmSpec, + }); + + if (!result.success) { + await this.app.terminate("getUsername failed"); + } + + const [username] = iface.decodeFunctionResult("getUsername", result.output!); + if (!username) { + return null; + } + + return username; + } } diff --git a/packages/api-sync/source/service.ts b/packages/api-sync/source/service.ts index 306a74192..12a1cc42e 100644 --- a/packages/api-sync/source/service.ts +++ b/packages/api-sync/source/service.ts @@ -195,6 +195,11 @@ export class Sync implements Contracts.ApiSync.Service { const attributes = { ...validatorAttributes(account.address), ...(account.unvote ? { unvote: account.unvote } : account.vote ? { vote: account.vote } : {}), + ...(account.usernameResigned + ? { usernameResigned: account.usernameResigned } + : account.username + ? { username: account.username } + : {}), }; return [ @@ -420,6 +425,13 @@ export class Sync implements Contracts.ApiSync.Service { ELSE COALESCE(EXCLUDED.attributes->>'vote', "Wallet".attributes->>'vote') END, + -- if any username is present, it will overwrite the previous username + 'username', + CASE + WHEN (EXCLUDED.attributes->>'usernameResigned')::boolean IS TRUE THEN NULL + ELSE COALESCE(EXCLUDED.attributes->>'username', "Wallet".attributes->>'username') + END, + 'validatorPublicKey', COALESCE(EXCLUDED.attributes->>'validatorPublicKey', "Wallet".attributes->>'validatorPublicKey'), 'validatorResigned', diff --git a/packages/configuration-generator/source/generators/genesis-block.ts b/packages/configuration-generator/source/generators/genesis-block.ts index efaa23565..7ce9cf89d 100644 --- a/packages/configuration-generator/source/generators/genesis-block.ts +++ b/packages/configuration-generator/source/generators/genesis-block.ts @@ -87,7 +87,8 @@ export class GenesisBlockGenerator extends Generator { account: genesisWalletAddress, deployerAccount: this.#deployerAddress, initialSupply: Utils.BigNumber.make(options.premine).toBigInt(), - validatorContract: ethers.getCreateAddress({ from: genesisWalletAddress, nonce: 0 }), + usernameContract: ethers.getCreateAddress({ from: genesisWalletAddress, nonce: 3 }), + validatorContract: ethers.getCreateAddress({ from: genesisWalletAddress, nonce: 1 }), }; await this.evm.initializeGenesis(genesisInfo); diff --git a/packages/contracts/source/contracts/evm/evm.ts b/packages/contracts/source/contracts/evm/evm.ts index c79e5ad46..4049ffaac 100644 --- a/packages/contracts/source/contracts/evm/evm.ts +++ b/packages/contracts/source/contracts/evm/evm.ts @@ -9,6 +9,7 @@ export interface GenesisInfo { readonly account: string; readonly deployerAccount: string; readonly validatorContract: string; + readonly usernameContract: string; readonly initialSupply: bigint; } @@ -52,6 +53,8 @@ export interface AccountUpdate { readonly vote?: string; readonly unvote?: string; + readonly username?: string; + readonly usernameResigned: boolean; } export interface AccountUpdateContext { diff --git a/packages/database/source/database-service.ts b/packages/database/source/database-service.ts index 5918a7273..356299479 100644 --- a/packages/database/source/database-service.ts +++ b/packages/database/source/database-service.ts @@ -192,7 +192,7 @@ export class DatabaseService implements Contracts.Database.DatabaseService { const data = this.#readCommitBytes(height); if (!data) { - throw new Error(`Failed to read commit at height ${height}`); + return; } const commit = await this.commitFactory.fromBytes(data); diff --git a/packages/evm-consensus/source/deployer.ts b/packages/evm-consensus/source/deployer.ts index a56b424c1..4639df230 100644 --- a/packages/evm-consensus/source/deployer.ts +++ b/packages/evm-consensus/source/deployer.ts @@ -55,7 +55,9 @@ export class Deployer { account: genesisBlock.block.generatorAddress, deployerAccount: this.deployerAddress, initialSupply: Utils.BigNumber.make(genesisBlock.block.totalAmount).toBigInt(), - validatorContract: ethers.getCreateAddress({ from: this.deployerAddress, nonce: 1 }), // PROXY Uses nonce 1 + // PROXY Uses nonce 1 + usernameContract: ethers.getCreateAddress({ from: this.deployerAddress, nonce: 3 }), + validatorContract: ethers.getCreateAddress({ from: this.deployerAddress, nonce: 1 }), // PROXY Uses nonce 3 }; await this.evm.initializeGenesis(genesisInfo); diff --git a/packages/evm-service/source/instances/evm.ts b/packages/evm-service/source/instances/evm.ts index aa936006c..3a36a75d8 100644 --- a/packages/evm-service/source/instances/evm.ts +++ b/packages/evm-service/source/instances/evm.ts @@ -31,6 +31,7 @@ export class EvmInstance implements Contracts.Evm.Instance { account: commit.account, deployerAccount: commit.deployerAccount, initialSupply: commit.initialSupply, + usernameContract: commit.usernameContract, validatorContract: commit.validatorContract, }); } diff --git a/packages/evm/bindings/src/ctx.rs b/packages/evm/bindings/src/ctx.rs index e2c164fe7..8eeb79f50 100644 --- a/packages/evm/bindings/src/ctx.rs +++ b/packages/evm/bindings/src/ctx.rs @@ -43,6 +43,7 @@ pub struct JsGenesisContext { pub account: JsString, pub deployer_account: JsString, pub validator_contract: JsString, + pub username_contract: JsString, pub initial_supply: JsBigInt, } @@ -116,6 +117,7 @@ pub struct GenesisContext { pub account: Address, pub deployer_account: Address, pub validator_contract: Address, + pub username_contract: Address, pub initial_supply: U256, } @@ -280,6 +282,7 @@ impl TryFrom for GenesisContext { Ok(GenesisContext { account: utils::create_address_from_js_string(value.account)?, validator_contract: utils::create_address_from_js_string(value.validator_contract)?, + username_contract: utils::create_address_from_js_string(value.username_contract)?, deployer_account: utils::create_address_from_js_string(value.deployer_account)?, initial_supply: utils::convert_bigint_to_u256(value.initial_supply)?, }) diff --git a/packages/evm/bindings/src/lib.rs b/packages/evm/bindings/src/lib.rs index 2f7f4b4e6..4b5a759eb 100644 --- a/packages/evm/bindings/src/lib.rs +++ b/packages/evm/bindings/src/lib.rs @@ -133,6 +133,7 @@ impl EvmInner { account: genesis_ctx.account, deployer_account: genesis_ctx.deployer_account, validator_contract: genesis_ctx.validator_contract, + username_contract: genesis_ctx.username_contract, initial_supply: genesis_ctx.initial_supply, }); diff --git a/packages/evm/bindings/src/result.rs b/packages/evm/bindings/src/result.rs index 59480ba57..b61bed02f 100644 --- a/packages/evm/bindings/src/result.rs +++ b/packages/evm/bindings/src/result.rs @@ -1,5 +1,5 @@ use mainsail_evm_core::{receipt::TxReceipt, state_changes::AccountUpdate}; -use napi::{JsBigInt, JsBuffer, JsString}; +use napi::{JsBigInt, JsBoolean, JsBuffer, JsString}; use napi_derive::napi; use revm::primitives::{AccountInfo, Bytes, B256}; @@ -129,6 +129,8 @@ pub struct JsAccountUpdate { pub nonce: JsBigInt, pub vote: Option, pub unvote: Option, + pub username: Option, + pub username_resigned: JsBoolean, } impl JsAccountUpdate { @@ -143,12 +145,21 @@ impl JsAccountUpdate { None => None, }; + let username = match &account_update.username { + Some(username) => Some(node_env.create_string_from_std(username.to_string())?), + None => None, + }; + + let username_resigned = node_env.get_boolean(account_update.username_resigned)?; + Ok(JsAccountUpdate { address: node_env.create_string_from_std(account_update.address.to_checksum(None))?, nonce: node_env.create_bigint_from_u64(account_update.nonce)?, balance: utils::convert_u256_to_bigint(node_env, account_update.balance)?, vote, unvote, + username, + username_resigned, }) } } diff --git a/packages/evm/core/src/db.rs b/packages/evm/core/src/db.rs index 7ee2eaa8b..afd650f3c 100644 --- a/packages/evm/core/src/db.rs +++ b/packages/evm/core/src/db.rs @@ -115,6 +115,7 @@ pub struct GenesisInfo { pub account: Address, pub deployer_account: Address, pub validator_contract: Address, + pub username_contract: Address, pub initial_supply: U256, } diff --git a/packages/evm/core/src/events.rs b/packages/evm/core/src/events.rs index 910bfcd2c..7aaff26f9 100644 --- a/packages/evm/core/src/events.rs +++ b/packages/evm/core/src/events.rs @@ -3,4 +3,7 @@ use alloy_sol_types::sol; sol! { event Voted(address voter, address validator); event Unvoted(address voter, address validator); + + event UsernameRegistered(address addr, string username, string previousUsername); + event UsernameResigned(address addr, string username); } diff --git a/packages/evm/core/src/state_changes.rs b/packages/evm/core/src/state_changes.rs index 57cb6b107..a9161c239 100644 --- a/packages/evm/core/src/state_changes.rs +++ b/packages/evm/core/src/state_changes.rs @@ -35,6 +35,10 @@ pub struct AccountUpdate { pub vote: Option
, // Set when commit receipt contains "Unvoted" event pub unvote: Option
, + // Set when commit receipt contains "UsernameRegistered" event + pub username: Option, + // Set when commit receipt contains "UsernameResigned" event + pub username_resigned: bool, } pub fn bundle_into_change_set(bundle_state: BundleState) -> StateChangeset { diff --git a/packages/evm/core/src/state_commit.rs b/packages/evm/core/src/state_commit.rs index 5320df0cc..986ffd0b9 100644 --- a/packages/evm/core/src/state_commit.rs +++ b/packages/evm/core/src/state_commit.rs @@ -112,6 +112,8 @@ fn collect_dirty_accounts( nonce: account.nonce, vote: None, unvote: None, + username: None, + username_resigned: false, }, ); } @@ -156,6 +158,31 @@ fn collect_dirty_accounts( break; } } + _ if log.address == info.username_contract => { + // Attempt to decode log as a UsernameRegistered event + if let Ok(event) = + crate::events::UsernameRegistered::decode_log(&log, true) + { + dirty_accounts.get_mut(&event.addr).and_then(|account| { + account.username = Some(event.username.clone()); + account.username_resigned = false; // cancel out any previous resignation if one happened in same commit + Some(account) + }); + break; + } + + // Attempt to decode log as a UsernameResigned event + if let Ok(event) = + crate::events::UsernameResigned::decode_log(&log, true) + { + dirty_accounts.get_mut(&event.addr).and_then(|account| { + account.username = None; // cancel out any previous registration if one happened in same commit + account.username_resigned = true; + Some(account) + }); + break; + } + } _ => (), // ignore } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 21970026b..d443afea3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -401,12 +401,21 @@ importers: '@mainsail/contracts': specifier: workspace:* version: link:../contracts + '@mainsail/evm-consensus': + specifier: workspace:* + version: link:../evm-consensus + '@mainsail/evm-contracts': + specifier: workspace:* + version: link:../evm-contracts '@mainsail/kernel': specifier: workspace:* version: link:../kernel '@mainsail/utils': specifier: workspace:* version: link:../utils + ethers: + specifier: ^6.11.0 + version: 6.13.1 joi: specifier: 17.12.2 version: 17.12.2