Skip to content

Commit

Permalink
feat(api-sync): populate username attribute (#795)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
Co-authored-by: sebastijankuzner <[email protected]>
  • Loading branch information
3 people authored Dec 5, 2024
1 parent eac22b3 commit d86880a
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 6 deletions.
3 changes: 3 additions & 0 deletions packages/api-sync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
41 changes: 39 additions & 2 deletions packages/api-sync/source/restore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<void> {
const mostRecentCommit = await (this.databaseService.isEmpty()
? this.stateStore.getGenesisCommit()
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -369,7 +380,8 @@ export class Restore {

...(userAttributes
? {
vote: userAttributes.vote,
...(userAttributes.vote ? { vote: userAttributes.vote } : {}),
...(username ? { username } : {}),
}
: {}),
},
Expand Down Expand Up @@ -535,4 +547,29 @@ export class Restore {
async #updateValidatorRanks(context: RestoreContext): Promise<void> {
await context.entityManager.query("SELECT update_validator_ranks();", []);
}

async #readUsername(account: string): Promise<string | null> {
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;
}
}
12 changes: 12 additions & 0 deletions packages/api-sync/source/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 3 additions & 0 deletions packages/contracts/source/contracts/evm/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface GenesisInfo {
readonly account: string;
readonly deployerAccount: string;
readonly validatorContract: string;
readonly usernameContract: string;
readonly initialSupply: bigint;
}

Expand Down Expand Up @@ -52,6 +53,8 @@ export interface AccountUpdate {

readonly vote?: string;
readonly unvote?: string;
readonly username?: string;
readonly usernameResigned: boolean;
}

export interface AccountUpdateContext {
Expand Down
2 changes: 1 addition & 1 deletion packages/database/source/database-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion packages/evm-consensus/source/deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions packages/evm-service/source/instances/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand Down
3 changes: 3 additions & 0 deletions packages/evm/bindings/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -280,6 +282,7 @@ impl TryFrom<JsGenesisContext> 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)?,
})
Expand Down
1 change: 1 addition & 0 deletions packages/evm/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
13 changes: 12 additions & 1 deletion packages/evm/bindings/src/result.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -129,6 +129,8 @@ pub struct JsAccountUpdate {
pub nonce: JsBigInt,
pub vote: Option<JsString>,
pub unvote: Option<JsString>,
pub username: Option<JsString>,
pub username_resigned: JsBoolean,
}

impl JsAccountUpdate {
Expand All @@ -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,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/evm/core/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

Expand Down
3 changes: 3 additions & 0 deletions packages/evm/core/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 4 additions & 0 deletions packages/evm/core/src/state_changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pub struct AccountUpdate {
pub vote: Option<Address>,
// Set when commit receipt contains "Unvoted" event
pub unvote: Option<Address>,
// Set when commit receipt contains "UsernameRegistered" event
pub username: Option<String>,
// Set when commit receipt contains "UsernameResigned" event
pub username_resigned: bool,
}

pub fn bundle_into_change_set(bundle_state: BundleState) -> StateChangeset {
Expand Down
27 changes: 27 additions & 0 deletions packages/evm/core/src/state_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ fn collect_dirty_accounts(
nonce: account.nonce,
vote: None,
unvote: None,
username: None,
username_resigned: false,
},
);
}
Expand Down Expand Up @@ -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
}
}
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

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

0 comments on commit d86880a

Please sign in to comment.