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

[DO NOT MERGE] fix: remove instant-finality hack #334

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 45 additions & 190 deletions packages/api-server/src/filter-web3-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,27 @@ import { Hash, HexNumber } from "@ckb-lumos/base";
import {
HexU32,
U32,
L2Transaction,
L2TransactionReceipt,
L2Transaction,
GodwokenClient,
schemas,
U64,
} from "@godwoken-web3/godwoken";
import { Reader } from "@ckb-lumos/toolkit";
import { EthTransaction, EthTransactionReceipt } from "./base/types/api";
import { Uint128, Uint256, Uint32, Uint64 } from "./base/types/uint";
import { PolyjuiceSystemLog, PolyjuiceUserLog } from "./base/types/gw-log";
import {
CKB_SUDT_ID,
ZERO_ETH_ADDRESS,
DEFAULT_LOGS_BLOOM,
POLYJUICE_SYSTEM_LOG_FLAG,
POLYJUICE_USER_LOG_FLAG,
} from "./methods/constant";
import { EthLog, EthTransaction } from "./base/types/api";
import { Uint128, Uint32, Uint64 } from "./base/types/uint";
import { POLYJUICE_USER_LOG_FLAG } from "./methods/constant";
import { gwConfig } from "./base/index";
import { logger } from "./base/logger";
import { EthRegistryAddress } from "./base/address";
import { PolyjuiceUserLog } from "./base/types/gw-log";

const PENDING_TRANSACTION_INDEX = "0x0";
// null when it's pending, https://eth.wiki/json-rpc/API
const PENDING_TRANSACTION_INDEX = null;
const PENDING_BLOCK_HASH = null;
const PENDING_BLOCK_NUMBER = null;

export async function filterWeb3Transaction(
export async function filterEthTransaction(
ethTxHash: Hash,
rpc: GodwokenClient,
tipBlockNumber: U64,
tipBlockHash: Hash,
l2Tx: L2Transaction,
l2TxReceipt?: L2TransactionReceipt
): Promise<[EthTransaction, EthTransactionReceipt | undefined] | undefined> {
const pendingBlockHash = bumpHash(tipBlockHash);
const pendingBlockNumber = new Uint64(tipBlockNumber + 1n).toHex();

l2Tx: L2Transaction
): Promise<EthTransaction | undefined> {
const fromId: U32 = +l2Tx.raw.from_id;
const fromScriptHash: Hash | undefined = await rpc.getScriptHash(fromId);
if (fromScriptHash == null) {
Expand Down Expand Up @@ -91,23 +78,18 @@ export async function filterWeb3Transaction(
const polyjuiceArgs = decodePolyjuiceArgs(l2TxArgs);

let toAddress: HexString | undefined;
// let polyjuiceChainId: HexNumber | undefined;
if (polyjuiceArgs.isCreate) {
// polyjuiceChainId = toIdHex;
} else {

if (!polyjuiceArgs.isCreate) {
// 74 = 2 + (32 + 4) * 2
toAddress = "0x" + toScript.args.slice(74);
// 32..36 bytes
// const data = "0x" + toScript.args.slice(66, 74);
// polyjuiceChainId = "0x" + readUInt32LE(data).toString(16);
}
// const chainId = polyjuiceChainId;
const input = polyjuiceArgs.input || "0x";

const ethTx: EthTransaction = {
blockHash: l2TxReceipt ? pendingBlockHash : null,
blockNumber: l2TxReceipt ? pendingBlockNumber : null,
transactionIndex: l2TxReceipt ? PENDING_TRANSACTION_INDEX : null,
blockHash: PENDING_BLOCK_HASH,
blockNumber: PENDING_BLOCK_NUMBER,
transactionIndex: PENDING_TRANSACTION_INDEX,
from: fromAddress,
gas: polyjuiceArgs.gasLimit,
gasPrice: polyjuiceArgs.gasPrice,
Expand All @@ -121,129 +103,38 @@ export async function filterWeb3Transaction(
s,
};

if (l2TxReceipt == null) {
return [ethTx, undefined];
}

// receipt info
const polyjuiceSystemLog = l2TxReceipt.logs.find(
(log) => log.service_flag === POLYJUICE_SYSTEM_LOG_FLAG
);
if (polyjuiceSystemLog == null) {
throw new Error("No system log found!");
}
const logInfo = parsePolyjuiceSystemLog(polyjuiceSystemLog.data);

let contractAddress = undefined;
if (polyjuiceArgs.isCreate && logInfo.createdAddress !== ZERO_ETH_ADDRESS) {
contractAddress = logInfo.createdAddress;
}

const txGasUsed = logInfo.gasUsed;
// or cumulativeGasUsed ?
const cumulativeGasUsed = txGasUsed;
return ethTx;
}

const web3Logs = l2TxReceipt.logs
.filter((log) => log.service_flag === POLYJUICE_USER_LOG_FLAG)
.map((log, index) => {
const info = parsePolyjuiceUserLog(log.data);
return {
address: info.address,
data: info.data,
logIndex: new Uint32(index).toHex(),
topics: info.topics,
};
});
return undefined;
}

const receipt: EthTransactionReceipt = {
transactionHash: ethTxHash,
export async function filterEthLog(
ethTxHash: Hash,
l2TxReceipt: L2TransactionReceipt
): Promise<EthLog[]> {
const web3Logs = l2TxReceipt.logs
.filter((log) => log.service_flag === POLYJUICE_USER_LOG_FLAG)
.map((log, index) => {
const info = parsePolyjuiceUserLog(log.data);
return {
address: info.address,
data: info.data,
logIndex: new Uint32(index).toHex(),
topics: info.topics,
};
});
return web3Logs.map((log) => {
return {
...log,
data: log.data === "0x" ? "0x" + "00".repeat(32) : log.data,
blockHash: PENDING_BLOCK_HASH,
blockNumber: PENDING_BLOCK_NUMBER,
transactionIndex: PENDING_TRANSACTION_INDEX,
blockHash: pendingBlockHash,
blockNumber: pendingBlockNumber,
from: fromAddress,
to: toAddress || null,
gasUsed: txGasUsed,
cumulativeGasUsed: cumulativeGasUsed,
logsBloom: DEFAULT_LOGS_BLOOM,
logs: web3Logs.map((log) => {
return {
...log,
data: log.data === "0x" ? "0x" + "00".repeat(32) : log.data,
blockHash: pendingBlockHash,
blockNumber: pendingBlockNumber,
transactionIndex: PENDING_TRANSACTION_INDEX,
transactionHash: ethTxHash,
removed: false,
};
}),
contractAddress: contractAddress || null,
status: "0x1",
transactionHash: ethTxHash,
removed: false,
};

return [ethTx, receipt];
} else if (
toId === +CKB_SUDT_ID &&
toScript.code_hash === gwConfig.gwScripts.l2Sudt.typeHash
) {
const sudtArgs = new schemas.SUDTArgs(new Reader(l2Tx.raw.args));
if (sudtArgs.unionType() === "SUDTTransfer") {
const sudtTransfer: schemas.SUDTTransfer = sudtArgs.value();
const toAddressRegistryAddress = new Reader(
sudtTransfer.getToAddress().raw()
).serializeJson();
const toAddress = EthRegistryAddress.Deserialize(
toAddressRegistryAddress
).address;
if (toAddress.length !== 42) {
return undefined;
}
const amount = Uint256.fromLittleEndian(
new Reader(sudtTransfer.getAmount().raw()).serializeJson()
);
const fee = Uint128.fromLittleEndian(
new Reader(sudtTransfer.getFee().getAmount().raw()).serializeJson()
);
const value: Uint256 = amount;
const gasPrice: Uint128 = new Uint128(1n);
const gasLimit: Uint128 = fee;

const ethTx: EthTransaction = {
blockHash: l2TxReceipt ? pendingBlockHash : null,
blockNumber: l2TxReceipt ? pendingBlockNumber : null,
transactionIndex: l2TxReceipt ? PENDING_TRANSACTION_INDEX : null,
from: fromAddress,
gas: gasLimit.toHex(),
gasPrice: gasPrice.toHex(),
hash: ethTxHash,
input: "0x",
nonce,
to: toAddress,
value: value.toHex(),
v: v.toHex(),
r,
s,
};

const receipt: EthTransactionReceipt = {
transactionHash: ethTxHash,
transactionIndex: PENDING_TRANSACTION_INDEX,
blockHash: pendingBlockHash,
blockNumber: pendingBlockNumber,
from: fromAddress,
to: toAddress,
gasUsed: gasLimit.toHex(),
cumulativeGasUsed: gasLimit.toHex(),
logsBloom: DEFAULT_LOGS_BLOOM,
logs: [],
contractAddress: null,
status: "0x1",
};

return [ethTx, receipt];
}
}

return undefined;
}) as EthLog[];
}

export interface PolyjuiceArgs {
Expand Down Expand Up @@ -285,37 +176,6 @@ function decodePolyjuiceArgs(args: HexString): PolyjuiceArgs {
};
}

function parsePolyjuiceSystemLog(data: HexString): PolyjuiceSystemLog {
// 2 + (8 + 8 + 20 + 4) * 2
if (data.length !== 82) {
throw new Error(`invalid system log raw data length: ${data.length}`);
}

const dataWithoutPrefix = data.slice(2);

// 0..8 bytes
const gasUsed: Uint64 = Uint64.fromLittleEndian(
"0x" + dataWithoutPrefix.slice(0, 16)
);
// 8..16 bytes
const cumulativeGasUsed: Uint64 = Uint64.fromLittleEndian(
"0x" + dataWithoutPrefix.slice(16, 32)
);
// 16..36 bytes
const createdAddress = "0x" + dataWithoutPrefix.slice(32, 72);
// 36..40 bytes
const statusCode = Uint32.fromLittleEndian(
"0x" + dataWithoutPrefix.slice(72, 80)
).toHex();

return {
gasUsed: gasUsed.toHex(),
cumulativeGasUsed: cumulativeGasUsed.toHex(),
createdAddress,
statusCode,
};
}

function parsePolyjuiceUserLog(data: HexString): PolyjuiceUserLog {
const dataWithoutPrefix = data.slice(2);

Expand Down Expand Up @@ -353,8 +213,3 @@ function parsePolyjuiceUserLog(data: HexString): PolyjuiceUserLog {
topics,
};
}

function bumpHash(hash: Hash): Hash {
const hashNum = BigInt(hash) + 1n;
return "0x" + hashNum.toString(16).padStart(64, "0");
}
52 changes: 6 additions & 46 deletions packages/api-server/src/methods/modules/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ import {
EthTransaction,
EthTransactionReceipt,
} from "../../base/types/api";
import { filterWeb3Transaction } from "../../filter-web3-tx";
import { filterEthTransaction } from "../../filter-web3-tx";
import { FilterManager } from "../../cache";
import { parseGwRunResultError } from "../gw-error";
import { Store } from "../../cache/store";
Expand Down Expand Up @@ -722,29 +722,23 @@ export class Eth {
if (godwokenTxWithStatus == null) {
return null;
}
const godwokenTxReceipt = await this.rpc.getTransactionReceipt(gwTxHash);
const tipBlock = await this.query.getTipBlock();
if (tipBlock == null) {
throw new Error("tip block not found!");
}
let ethTxInfo = undefined;
let ethTx: EthTransaction | undefined;
try {
ethTxInfo = await filterWeb3Transaction(
ethTx = await filterEthTransaction(
ethTxHash,
this.rpc,
tipBlock.number,
tipBlock.hash,
godwokenTxWithStatus.transaction,
godwokenTxReceipt
godwokenTxWithStatus.transaction
);
} catch (err) {
logger.error("filterWeb3Transaction:", err);
logger.info("godwoken tx:", godwokenTxWithStatus);
logger.info("godwoken receipt:", godwokenTxReceipt);
throw err;
}
if (ethTxInfo != null) {
const ethTx = ethTxInfo[0];
if (ethTx != null) {
return ethTx;
}

Expand Down Expand Up @@ -812,39 +806,6 @@ export class Eth {
return transactionReceipt;
}

const godwokenTxWithStatus = await this.rpc.getTransaction(gwTxHash);
if (godwokenTxWithStatus == null) {
return null;
}
const godwokenTxReceipt = await this.rpc.getTransactionReceipt(gwTxHash);
if (godwokenTxReceipt == null) {
return null;
}
const tipBlock = await this.query.getTipBlock();
if (tipBlock == null) {
throw new Error(`tip block not found`);
}
let ethTxInfo = undefined;
try {
ethTxInfo = await filterWeb3Transaction(
ethTxHash,
this.rpc,
tipBlock.number,
tipBlock.hash,
godwokenTxWithStatus.transaction,
godwokenTxReceipt
);
} catch (err) {
logger.error("filterWeb3Transaction:", err);
logger.info("godwoken tx:", godwokenTxWithStatus);
logger.info("godwoken receipt:", godwokenTxReceipt);
throw err;
}
if (ethTxInfo != null) {
const ethTxReceipt = ethTxInfo[1]!;
return ethTxReceipt;
}

return null;
}

Expand Down Expand Up @@ -1147,10 +1108,9 @@ export class Eth {
): Promise<GodwokenBlockParameter> {
switch (blockParameter) {
case "latest":
return undefined;
return await this.getTipNumber();
RetricSu marked this conversation as resolved.
Show resolved Hide resolved
case "earliest":
return 0n;
// It's supposed to be filtered in the validator, so throw an error if matched
case "pending":
// null means pending in godwoken
return undefined;
Expand Down