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 1 commit
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: 109 additions & 25 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 @@ -744,7 +750,7 @@ export class Eth {
const gwTxHash: Hash | null = await this.cacheStore.get(ethTxHashKey);
if (gwTxHash != null) {
const godwokenTxWithStatus = await this.rpc.getTransaction(gwTxHash);
if (godwokenTxWithStatus == null) {
if (godwokenTxWithStatus?.transaction == null) {
return null;
}
const godwokenTxReceipt = await this.rpc.getTransactionReceipt(gwTxHash);
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 isTxExists: boolean | Hash = await this.isAcaTxExist(
keroro520 marked this conversation as resolved.
Show resolved Hide resolved
ethTxHash,
tx,
fromAddress
);
if (isTxExists) {
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 @@ -862,7 +878,7 @@ export class Eth {
}

const godwokenTxWithStatus = await this.rpc.getTransaction(gwTxHash);
if (godwokenTxWithStatus == null) {
if (godwokenTxWithStatus?.transaction == null) {
return null;
}
const godwokenTxReceipt = await this.rpc.getTransactionReceipt(gwTxHash);
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,70 @@ export class Eth {

return [normalizedFromBlock, normalizedToBlock];
}

// aca = auto create account
// Search tx by `signature_hash` when tx's `from_id = 0`
classicalliu marked this conversation as resolved.
Show resolved Hide resolved
//
// `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.getTransactionByFullnode(signatureHash);
if (txWithStatus?.transaction != 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 === "0x0") {
keroro520 marked this conversation as resolved.
Show resolved Hide resolved
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.getTransactionByFullnode(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 getTransactionByFullnode(
keroro520 marked this conversation as resolved.
Show resolved Hide resolved
hash: Hash
): Promise<L2TransactionWithStatus | undefined> {
return await this.writeRpcCall("get_transaction", hash);
}

public async getTransactionReceipt(
hash: Hash
): Promise<L2TransactionReceipt | undefined> {
Expand Down
2 changes: 1 addition & 1 deletion packages/godwoken/src/types.ts
Expand Up @@ -38,7 +38,7 @@ export interface L2Transaction {
}

export interface L2TransactionWithStatus {
transaction: L2Transaction;
transaction: L2Transaction | undefined;
keroro520 marked this conversation as resolved.
Show resolved Hide resolved
tx_status: {
status: "committed" | "pending";
block_hash?: Hash;
Expand Down