Update 2023/3/30

2023 年 2 月 27 日 NEM 攤位事後分析

strapi-blog-api-image

觀察

在區塊 4129631 生成後,NEM 網絡上的區塊生成速度變得緩慢。 這種情況是由進入未確認交易緩存的意外交易引起的。 當這筆交易到期時,區塊生產恢複正常。
我們已經確定了意外交易的構成以及它是如何進入未確認交易緩存的。
配置了 1-of-2 多重簽名的多重簽名帳戶發送了兩個相互衝突的交易。 假設兩個共同簽名者名為 A 和 B,多重簽名賬戶名為 M。

根本原因分析

發生了以下事件序列:
  1. M 發送一個多重簽名交易 MT,其中包含一個內部交易 - X - 由 A 簽名
  2. 多重簽名交易提交到鏈上,TransactionHashesObserver(作為 BlockTransactionFactory 的一部分運行)將 H(MT)、H(X) 添加到 HashTransactionsCache
    public void notify(final TransactionHashesNotification notification, final BlockNotificationContext context) {
    如果 (NotificationTrigger.Execute == context.getTrigger()) {
    this.transactionHashCache.putAll(notification.getPairs());
    }
    ...
    重要的是,當創建 TransactionsHashesNotification 時,會使用 streamDefault,它會擴展到外部和內部交易:
    protected void notifyTransactionHashes() {
    final List<HashMetaDataPair>= BlockExtensions.streamDefault(this.block)
    .map(t -> new HashMetaDataPair(HashUtils.calculateHash(t), new HashMetaData(this.block.getHeight(), t.getTimeStamp())))
    .collect(Collectors.toList());
    this.observer.notify(new TransactionHashesNotification(pairs));
    }
    
  3. M 發送一個多重簽名交易 MT',其中包含由 B 簽名的 SAME 內部交易 X
  4. BatchUniqueHashTransactionValidator 檢查 H(MT') 不在 HashTransactionsCache 中,因此它被添加到 UnconfirmedTransactionsCache ✅
    public ValidationResult validate(final List<TransactionsContextPair> groupedTransactions) {
            ...
    最終 List<Hash> hashes = groupedTransactions.stream().flatMap(pair -> pair.getTransactions().stream())
    .map(HashUtils::calculateHash).collect(Collectors.toList());
    
    返回 this.validate(hashes);
    }
    
    重要的是,pair.getTransactions() 返回僅包含外部交易的 Collection<Transaction>。 結果,檢查了 H(MT') != H(MT) 但未檢查 H(X)。
  5. MT' 被收割機選為一個區塊。 BlockUniqueHashTransactionValidatorBatchUniqueHashTransactionValidator 的邏輯是一樣的,所以隻檢查外層 MT 的 hash。 結果,交易被包含在收獲的區塊中 ✅
    公共 ValidationResult 驗證(最終塊
            ...
    final List<Hash> hashes = block.getTransactions().stream().map(HashUtils::calculateHash).collect(Collectors.toList());
    返回 this.transactionHashCache.anyHashExists(hashes)ValidationResult.NEUTRAL : ValidationResult.SUCCESS;
    }
  6. 當處理新收獲的塊 - 包含 MT' - 時,TransactionHashesObserver 嚐試在進行狀態更改的部分塊處理期間將 H(MT') 和 H(X) 添加到 HashTransactionsCache。 這失敗了,因為 H(X) 已經存在於 UnconfirmedTransactionsCache 中,所以收獲的塊被拒絕 🛑
  7. 轉到第 5 步,直到 H(MT') 從 UnconfirmedTransactionsCache 中過期
strapi-blog-api-image

使固定

解決方法是更新兩個唯一的哈希交易驗證器,以檢查外部和內部交易哈希。 這可以簡單地通過使用 TransactionExtensions.streamDefault() 來完成。

BatchUniqueHashTransactionValidator

添加“TransactionExtensions.streamDefault”將阻止 MT(或類似的)添加到“UnconfirmedTransactionsCache”。
public ValidationResult validate(final List<TransactionsContextPair> groupedTransactions) {
     如果(groupedTransactions.isEmpty()){
         返回 ValidationResult.SUCCESS;
     }

     最終 List<Hash> hashes = groupedTransactions.stream().flatMap(pair -> pair.getTransactions().stream())
             .flatMap(事務 -> TransactionExtensions.streamDefault(事務)).map(HashUtils::calculateHash)
             .collect(Collectors.toList());
     返回 this.validate(hashes);
}

BlockUniqueHashTransactionValidator

添加“TransactionExtensions.streamDefault”將阻止 MT(或類似的)被添加到新收獲的區塊中。
公共 ValidationResult 驗證(最終塊塊){
     如果 (block.getTransactions().isEmpty()) {
         返回 ValidationResult.SUCCESS;
     }

     最終 List<Hash> hashes = block.getTransactions().stream().flatMap(transaction -> TransactionExtensions.streamDefault(transaction))
             .map(HashUtils::calculateHash).collect(收集ors.toList());
     返回 this.transactionHashCache.anyHashExists(hashes)ValidationResult.NEUTRAL : ValidationResult.SUCCESS;
}

News
Community
Docs
Contact