Skip to content
This repository has been archived by the owner on Mar 24, 2023. It is now read-only.

feat: Delete dropped auto create account tx #445

Merged
merged 4 commits into from Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/api-server/src/convert-tx.ts
Expand Up @@ -95,7 +95,9 @@ export async function generateRawTransaction(
return [godwokenTx, cacheKeyAndValue];
}

function decodeRawTransactionData(dataParams: HexString): PolyjuiceTransaction {
export function decodeRawTransactionData(
dataParams: HexString
): PolyjuiceTransaction {
const result: Buffer[] = rlp.decode(dataParams) as Buffer[];
// todo: r might be "0x" which cause inconvenient for down-stream
const resultHex = result.map((r) => "0x" + Buffer.from(r).toString("hex"));
Expand Down Expand Up @@ -173,7 +175,7 @@ function encodePolyjuiceTransaction(tx: PolyjuiceTransaction) {
return "0x" + result.toString("hex");
}

async function parseRawTransactionData(
export async function parseRawTransactionData(
rawTx: PolyjuiceTransaction,
rpc: GodwokenClient,
polyjuiceRawTx: HexString
Expand Down
134 changes: 111 additions & 23 deletions packages/api-server/src/methods/modules/eth.ts
Expand Up @@ -16,11 +16,13 @@ import {
verifyIntrinsicGas,
} from "../validator";
import { AutoCreateAccountCacheValue } from "../../cache/types";
import { Address, Hash, HexNumber, HexString } from "@ckb-lumos/base";
import { HexNumber, Hash, Address, HexString, utils } from "@ckb-lumos/base";
import {
GodwokenClient,
normalizers,
RawL2Transaction,
RunResult,
schemas,
GodwokenClient,
} from "@godwoken-web3/godwoken";
import {
CKB_SUDT_ID,
Expand Down Expand Up @@ -69,8 +71,11 @@ import {
import {
autoCreateAccountCacheKey,
calcEthTxHash,
decodeRawTransactionData,
generateRawTransaction,
parseRawTransactionData,
polyjuiceRawTransactionToApiTransaction,
PolyjuiceTransaction,
} from "../../convert-tx";
import { ethAddressToAccountId, EthRegistryAddress } from "../../base/address";
import { keccakFromString } from "ethereumjs-util";
Expand All @@ -79,6 +84,7 @@ import { gwConfig } from "../../base/index";
import { logger } from "../../base/logger";
import { calcIntrinsicGas } from "../../util";
import { FilterFlag, FilterParams, RpcFilterRequest } from "../../base/filter";
import { Reader } from "@ckb-lumos/toolkit";

const Config = require("../../../config/eth.json");

Expand Down Expand Up @@ -786,15 +792,25 @@ export class Eth {
// Convert polyjuice tx to api transaction
const { tx, fromAddress }: AutoCreateAccountCacheValue =
JSON.parse(polyjuiceRawTx);
const apiTransaction: EthTransaction =
polyjuiceRawTransactionToApiTransaction(
tx,
ethTxHash,
tipBlock.hash,
tipBlock.number,
fromAddress
);
return apiTransaction;
const isAcaTxExist: boolean = await this.isAcaTxExist(
ethTxHash,
tx,
fromAddress
);
if (isAcaTxExist) {
const apiTransaction: EthTransaction =
polyjuiceRawTransactionToApiTransaction(
tx,
ethTxHash,
tipBlock.hash,
tipBlock.number,
fromAddress
);
return apiTransaction;
} else {
// If not found, means dropped by godwoken, should delete cache
this.cacheStore.delete(cacheKey);
}
}

return null;
Expand Down Expand Up @@ -1050,18 +1066,7 @@ export class Eth {

// save the tx hash mapping for instant finality
if (gwTxHash != null) {
const ethTxHashKey = ethTxHashCacheKey(ethTxHash);
await this.cacheStore.insert(
ethTxHashKey,
gwTxHash,
TX_HASH_MAPPING_CACHE_EXPIRED_TIME_MILSECS
);
const gwTxHashKey = gwTxHashCacheKey(gwTxHash);
await this.cacheStore.insert(
gwTxHashKey,
ethTxHash,
TX_HASH_MAPPING_CACHE_EXPIRED_TIME_MILSECS
);
await this.cacheTxHashMapping(ethTxHash, gwTxHash);
}

return ethTxHash;
Expand All @@ -1071,6 +1076,21 @@ export class Eth {
}
}

private async cacheTxHashMapping(ethTxHash: Hash, gwTxHash: Hash) {
const ethTxHashKey = ethTxHashCacheKey(ethTxHash);
await this.cacheStore.insert(
ethTxHashKey,
gwTxHash,
TX_HASH_MAPPING_CACHE_EXPIRED_TIME_MILSECS
);
const gwTxHashKey = gwTxHashCacheKey(gwTxHash);
await this.cacheStore.insert(
gwTxHashKey,
ethTxHash,
TX_HASH_MAPPING_CACHE_EXPIRED_TIME_MILSECS
);
}

private async getTipNumber(): Promise<U64> {
const num = await this.query.getTipBlockNumber();
if (num == null) {
Expand Down Expand Up @@ -1209,6 +1229,74 @@ export class Eth {

return [normalizedFromBlock, normalizedToBlock];
}

// aca = auto create account
// `acaTx` is the first transaction (nonce=0) of an undeposited account which account_id/from_id is not undetermined yet.
// `signature_hash` is used here to get an `acaTx` from GodwokenRPC, see also:
// https://github.com/nervosnetwork/godwoken/blob/develop/docs/RPC.md#method-gw_submit_l2transaction
//
// `gw_get_transaction(signature_hash)`
// |-> if `txWithStatus.transaction` != null
// |-> found!
// |-> if `txWithStatus.transaction` == null
// |-> if `from_id` == null
// |-> not found!
// |-> if `from_id` != null
keroro520 marked this conversation as resolved.
Show resolved Hide resolved
// |-> `gw_get_transaction(gw_tx_hash)`
// |-> `txWithStatus.transaction` != null
// |-> found!
// |-> `txWithStatus.transaction` == null
// |-> not found!
private async isAcaTxExist(
ethTxHash: Hash,
rawTx: HexString,
fromAddress: HexString
): Promise<boolean> {
const tx: PolyjuiceTransaction = decodeRawTransactionData(rawTx);
const real_v = +tx.v % 2 === 0 ? "0x01" : "0x00";
const signature: HexString = tx.r + tx.s.slice(2) + real_v.slice(2);
const signatureHash: Hash = utils
.ckbHash(new Reader(signature).toArrayBuffer())
.serializeJson();
const txWithStatus = await this.rpc.getTransactionFromFullnode(
signatureHash
);
if (txWithStatus != null) {
logger.debug(
`aca tx: ${ethTxHash} found by signature hash: ${signatureHash}`
);
// transaction found by signature hash
return true;
}

const fromId = await ethAddressToAccountId(fromAddress, this.rpc);
keroro520 marked this conversation as resolved.
Show resolved Hide resolved
logger.debug(`aca tx's (${ethTxHash}) from_id:`, fromId);
if (fromId == null) {
return false;
}
const [godwokenTx, _cacheKeyAndValue] = await parseRawTransactionData(
tx,
this.rpc,
rawTx
);
if (godwokenTx.raw.from_id === AUTO_CREATE_ACCOUNT_FROM_ID) {
logger.warn("aca generated tx's from_id = 0");
return false;
}
const gwTxHash: Hash = utils
.ckbHash(
new Reader(
schemas.SerializeRawL2Transaction(
normalizers.NormalizeRawL2Transaction(godwokenTx.raw)
)
).toArrayBuffer()
)
.serializeJson();
logger.debug(`aca tx: ${ethTxHash} gw_tx_hash: ${gwTxHash}`);
const gwTx = await this.rpc.getTransactionFromFullnode(gwTxHash);

return !!gwTx;
}
}

function ethTxHashCacheKey(ethTxHash: string) {
Expand Down
14 changes: 14 additions & 0 deletions packages/godwoken/src/client.ts
Expand Up @@ -29,6 +29,12 @@ export class GodwokenClient {
this.readonlyRpc = !!readonlyUrl ? new RPC(readonlyUrl) : this.rpc;
}

// This RPC only for fullnode
public async isRequestInQueue(hash: Hash): Promise<boolean> {
const result = await this.writeRpcCall("is_request_in_queue", hash);
return result;
}

public async getScriptHash(accountId: U32): Promise<Hash> {
const hash = await this.rpcCall("get_script_hash", toHex(accountId));
return hash;
Expand Down Expand Up @@ -169,6 +175,14 @@ export class GodwokenClient {
return await this.rpcCall("get_transaction", hash);
}

// TODO: replace by `getTransaction` later
// Only fullnode can get queue info
public async getTransactionFromFullnode(
hash: Hash
): Promise<L2TransactionWithStatus | undefined> {
return await this.writeRpcCall("get_transaction", hash);
}

public async getTransactionReceipt(
hash: Hash
): Promise<L2TransactionReceipt | undefined> {
Expand Down