教程区块链区块链技术ch1414.3 交易构造、发送与状态追踪

本页目录

一笔交易的旅程:前端构造 → 钱包签名 → RPC 广播 → mempool 等待 → 矿工/PBS 打包 → 区块确认。理解这个流程的每一步,才能写出不丢交易、不错 gas、不卡 UX 的 DApp。


14.3.1 交易对象的基本结构

graph LR
    subgraph Tx["EIP-1559 交易对象"]
        T1[to: 目标地址]
        T2[data: 0x 调用数据]
        T3[value: ETH 转账金额]
        T4[maxFeePerGas: 每 gas 最高总费]
        T5[maxPriorityFeePerGas: 给矿工/validator 的小费]
        T6[gasLimit: 最大 gas 数量]
        T7[nonce: 发送者交易计数]
        T8[chainId: 网络标识]
    end
    
    T4 --" - "--> T4a[" = baseFee + maxPriorityFee"]
    T4a --"但最多只收"--> T4b["baseFee 由网络动态调整\n小费给 validator 决定顺序"]
    
    style Tx fill:#e3f2fd

Gas 价格计算

totalFeePerGas=baseFee+priorityFee\text{totalFeePerGas} = \text{baseFee} + \text{priorityFee}
typescript
/**
 * Gas 价格估算与交易构造
 */
interface TransactionRequest {
  to: string;
  data: string;
  value: bigint;
  maxFeePerGas: bigint;
  maxPriorityFeePerGas: bigint;
  gasLimit: bigint;
  nonce: number;
  chainId: number;
}

class GasEstimator {
  // EIP-1559 的 baseFee 由网络根据区块容量动态调整:
  // target = 15M gas/block(上限 30M)
  // 如果区块超过 target,baseFee 增加最多 12.5%
  // 如果低于,baseFee 减少最多 12.5%
  
  async estimate1559Fees(blockUtilization: number): Promise<{[key: string]: bigint}> {
    const currentBaseFee = 20n * 10n**9n; // 20 gwei (示例)
    const target = 0.5; // 50% 利用率 = 稳定
    
    // baseFee 变化公式 (简化)
    let newBaseFee: bigint;
    if (blockUtilization > 0.5) {
      // 拥堵:baseFee 增加
      const increase = 1n + BigInt(Math.floor((blockUtilization - 0.5) * 1000));
      newBaseFee = currentBaseFee * increase / 1000n;
    } else {
      // 空闲:baseFee 减少
      const decrease = 1n - BigInt(Math.floor((0.5 - blockUtilization) * 1000));
      newBaseFee = currentBaseFee * decrease / 1000n;
    }
    
    const priorityFee = 2n * 10n**9n; // 2 gwei 小费
    const maxFee = newBaseFee * 2n + priorityFee; // 余量
    
    return {
      baseFee: newBaseFee,
      maxPriorityFeePerGas: priorityFee,
      maxFeePerGas: maxFee,
    };
  }
  
  // gas 限制估算(调用节点的 gasEstimate 模拟执行)
  async estimateGasLimit(tx: Partial<TransactionRequest>): Promise<bigint> {
    // 模拟:简单 ETH 转账 21,000 gas
    if (!tx.data || tx.data === '0x') return 21000n;
    // 合约调用:通常 50K-500K 不等
    return 150000n; // 示例
  }
}

// 自动构造交易
async function buildTransaction(args: {
  to: string;
  value?: bigint;
  data?: string;
  priority?: 'low' | 'normal' | 'high';
}): Promise<TransactionRequest> {
  const estimator = new GasEstimator();
  const fees = await estimator.estimate1559Fees(0.7); // 70% 利用率 = 轻度拥堵
  const gasLimit = await estimator.estimateGasLimit(args);
  
  // 根据 priority 调整小费
  const multiplier = { low: 0.8, normal: 1.0, high: 2.0 }[args.priority || 'normal'];
  
  return {
    to: args.to,
    data: args.data || '0x',
    value: args.value || 0n,
    maxFeePerGas: fees.maxFeePerGas * BigInt(Math.floor(multiplier * 100)) / 100n,
    maxPriorityFeePerGas: fees.maxPriorityFeePerGas * BigInt(Math.floor(multiplier * 100)) / 100n,
    gasLimit,
    nonce: 42, // 实际需要 eth_getTransactionCount
    chainId: 1,
  };
}

console.log("Gas 估算器已定义");

14.3.2 交易生命周期

sequenceDiagram
    participant User as 用户
    participant DApp as 前端 DApp
    participant Wallet as 钱包
    participant RPC as RPC 节点
    participant Mempool as Mempool
    participant Builder as Block Builder
    participant Chain as 链
    
    User ->> DApp: 点击 "Swap"
    DApp ->> DApp: 构造交易对象
    DApp ->> Wallet: eth_sendTransaction
    Wallet ->> Wallet: 用户确认 + 签名
    Wallet -->> DApp: raw signed tx
    DApp ->> RPC: eth_sendRawTransaction
    RPC ->> Mempool: 加入 mempool
    Mempool -->> RPC: tx hash
    RPC -->> DApp: pending 回执
    
    Builder ->> Mempool: SEAL 搜索 MEV
    Builder ->> Builder: 构建区块
    Builder ->> Chain: 打包入块
    Chain -->> Builder: 区块收据
    
    DApp ->> RPC: eth_getTransactionReceipt (轮询)
    RPC -->> DApp: receipt (status, gas, logs)
    DApp ->> User: 显示结果
    
    Note over DApp: 乐观更新:先显示成功,<br/>等 receipt 最终确认

14.3.3 状态追踪与乐观更新

typescript
/**
 * 交易状态追踪 + 乐观更新
 */
type TxStatus = "initiated" | "pending" | "success" | "reverted" | "dropped";

interface TransactionState {
  hash: string;
  status: TxStatus;
  confirmations: number;
  receipt?: {
    gasUsed: bigint;
    logs: any[];
    status: string; // "0x1" = success, "0x0" = revert
  };
}

class TransactionTracker {
  private transactions: Map<string, TransactionState> = new Map();
  private listeners: Set<(txs: TransactionState[]) => void> = new Set();
  
  // 乐观更新:前端先假设成功
  async optimisticallySend(
    sendFn: () => Promise<string>,  // 返回 txHash
    preview: () => void,            // 先更新 UI
  ): Promise<string> {
    preview(); // 立即更新UI(乐观)
    const hash = await sendFn();   // 但实际还要等待链上确认
    
    this.transactions.set(hash, {
      hash,
      status: "pending",
      confirmations: 0,
    });
    
    this.pollForConfirmation(hash);
    return hash;
  }
  
  private async pollForConfirmation(hash: string) {
    for (let i = 0; i < 60; i++) { // 最多等 5 分钟
      await new Promise(r => setTimeout(r, 5000));
      
      try {
        const receipt = await this.fetchReceipt(hash);
        if (receipt) {
          const status: TxStatus = receipt.status === "0x1" ? "success" : "reverted";
          this.transactions.set(hash, {
            hash,
            status,
            confirmations: 1, // 后续可增加
            receipt,
          });
          this.notifyListeners();
          
          if (status === "reverted") {
            // 回滚乐观更新
            this.rollbackPreview(hash);
          }
          return;
        }
      } catch {
        // 继续轮询
      }
    }
    
    // 超时:标记为 dropped
    this.transactions.set(hash, { hash, status: "dropped", confirmations: 0 });
  }
  
  private fetchReceipt(hash: string): Promise<any> {
    // 实际调用 eth_getTransactionReceipt
    return Promise.resolve({ gasUsed: 145000n, logs: [], status: "0x1" });
  }
  
  private rollbackPreview(hash: string) {
    console.log("Transaction reverted, rolling back UI:", hash);
  }
  
  private notifyListeners() {
    const txs = Array.from(this.transactions.values());
    for (const cb of this.listeners) cb(txs);
  }
  
  subscribe(cb: (txs: TransactionState[]) => void) {
    this.listeners.add(cb);
    return () => this.listeners.delete(cb);
  }
}

// React 风格的自定义 hook(概念性展示)
function useSendTransaction() {
  const [status, setStatus] = React.useState<TxStatus>("initiated");
  
  async function send(tx: TransactionRequest) {
    setStatus("pending");
    try {
      // 实际签名广播
      const hash = "0x...";
      setStatus("success");
      return hash;
    } catch (e) {
      setStatus("dropped");
      throw e;
    }
  }
  
  return { send, status };
}

14.3.4 常见错误诊断

错误原因解决
insufficient funds余额不足以支付 gas + value检查 ETH 余额
maxFeePerGas < baseFeegas 价格设定低于网络 baseFee使用最新 baseFee 的 2 倍
nonce too high/lownonce 不正确顺序发送,或用 eth_getTransactionCount
replacement fee too low加速旧交易时新交易小费不够高小费至少增加 10%
intrinsic gas too lowgas 限制 < 21,000至少 21,000 基础费用
execution reverted合约拒绝(无 gas 问题)检查 revert reason 和参数

> ← 14.2 钱包连接 | 前往 → 14.4 事件订阅与状态同步 |*

评论

0

评论加载中…

发表评论

0/2000