![post-mortem.png](../images/post_mortem_7efc587c07.png)

## 观察

在区块 4129631 生成后，NEM 网络上的区块生成速度变得缓慢。 这种情况是由进入未确认交易缓存的意外交易引起的。 当这笔交易到期时，区块生产恢复正常。

我们已经确定了意外交易的构成以及它是如何进入未确认交易缓存的。

配置了 1-of-2 多重签名的多重签名帐户发送了两个相互冲突的交易。 假设两个共同签名者名为 A 和 B，多重签名账户名为 M。

## 根本原因分析

发生了以下事件序列：

1. M 发送一个多重签名交易 MT，其中包含一个内部交易 - X - 由 A 签名

1. 多重签名交易提交到链上，TransactionHashesObserver（作为 BlockTransactionFactory 的一部分运行）将 H(MT)、H(X) 添加到 HashTransactionsCache

    ```java
    public void notify(final TransactionHashesNotification notification, final BlockNotificationContext context) {
    如果 (NotificationTrigger.Execute == context.getTrigger()) {
    this.transactionHashCache.putAll(notification.getPairs());
    }
    ...
    ```

    重要的是，当创建 TransactionsHashesNotification 时，会使用 streamDefault，它会扩展到外部和内部交易：

    ```java
    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));
    }
   
    ```

1. M 发送一个多重签名交易 MT'，其中包含由 B 签名的 **SAME** 内部交易 X

1. `BatchUniqueHashTransactionValidator` 检查 H(MT') 不在 HashTransactionsCache 中，因此它被添加到 UnconfirmedTransactionsCache ✅

    ```java
    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)。

1. MT' 被收割机选为一个区块。 `BlockUniqueHashTransactionValidator` 和`BatchUniqueHashTransactionValidator` 的逻辑是一样的，所以只检查外层 MT 的 hash。 结果，交易被包含在收获的区块中 ✅

    ```java
    公共 ValidationResult 验证（最终块
            ...
    final List<Hash> hashes = block.getTransactions().stream().map(HashUtils::calculateHash).collect(Collectors.toList());
    返回 this.transactionHashCache.anyHashExists(hashes) ？ ValidationResult.NEUTRAL : ValidationResult.SUCCESS;
    }
    ```

1. 当处理新收获的块 - 包含 MT' - 时，`TransactionHashesObserver` 尝试在进行状态更改的部分块处理期间将 H(MT') 和 H(X) 添加到 `HashTransactionsCache`。 这失败了，因为 H(X) 已经存在于 `UnconfirmedTransactionsCache` 中，所以收获的块被拒绝 🛑

1. 转到第 5 步，直到 H(MT') 从 `UnconfirmedTransactionsCache` 中过期

![2023 02 27 NEM Stall Post Mortem](../images/2023_02_27_NEM_Stall_Post_Mortem_544cb72628.png)

## 使固定

解决方法是更新两个唯一的哈希交易验证器，以检查外部和内部交易哈希。 这可以简单地通过使用 TransactionExtensions.streamDefault() 来完成。

### BatchUniqueHashTransactionValidator

添加“TransactionExtensions.streamDefault”将阻止 MT（或类似的）添加到“UnconfirmedTransactionsCache”。

```java
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（或类似的）被添加到新收获的区块中。

```java
公共 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;
}
```
