
관찰
블록 4129631이 생성된 후, NEM 네트워크의 블록 생성 속도가 느려졌습니다. 이러한 상황은 미확인 트랜잭션 캐시에 예기치 않은 트랜잭션이 들어와서 발생했습니다. 이 트랜잭션이 만료되자 블록 생성은 정상으로 돌아갔습니다.
저희는 예기치 않은 트랜잭션의 구성과 미확인 트랜잭션 캐시에 들어간 경위를 확인했습니다.
2대1 다중 서명으로 구성된 다중 서명 계정이 두 개의 상충되는 트랜잭션을 보냈습니다. 두 명의 공동 서명자의 이름은 A와 B이고 다중 서명 계정의 이름은 M이라고 가정합니다.
근본 원인 분석
다음과 같은 일련의 이벤트가 발생했습니다:
M이 A가 서명한 내부 트랜잭션 X가 포함된 다중서명 트랜잭션 MT를 보냅니다.
다중서명 트랜잭션이 체인에 커밋되고 (블록트랜잭션팩토리의 일부로 실행되는) TransactionHashesObserver가 H(MT), H(X)를 모두 해시트랜잭션캐시에 추가합니다.
public void notify(final TransactionHashesNotification notification, final BlockNotificationContext context) { if (NotificationTrigger.Execute == context.getTrigger()) { this.transactionHashCache.putAll(notification.getPairs()); } ...중요한 것은
TransactionsHashesNotification이 생성될 때 외부 트랜잭션과 내부 트랜잭션 모두로 확장되는streamDefault가 사용된다는 점입니다:protected void notifyTransactionHashes() { final List<HashMetaDataPair> pairs = BlockExtensions.streamDefault(this.block) .map(t -> new HashMetaDataPair(HashUtils.calculateHash(t), new HashMetaData(this.block.getHeight(), t.getTimeStamp()))))) .collect(Collectors.toList()); this.observer.notify(새로운 트랜잭션해시알림(쌍)); }M은 B가 서명한 SAME 내부 트랜잭션 X와 함께 다중서명 트랜잭션 MT'를 보냅니다.
BatchUniqueHashTransactionValidator는 H(MT')가 해시트랜잭션캐시에 없는지 확인하여 UnconfirmedTransactionsCache에 추가합니다 ✅.public ValidationResult validate(final List<TransactionsContextPair> groupedTransactions) { ... final List<Hash> hashes = groupedTransactions.stream().flatMap(pair -> pair.getTransactions().stream()) .map(HashUtils::calculateHash).collect(Collectors.toList()); return this.validate(hashes); }중요한 것은
pair.getTransactions()가 외부 트랜잭션만 포함하는Collection<Transaction>을 반환한다는 점입니다. 결과적으로 H(MT') != H(MT)는 확인되지만 H(X)는 확인되지 않습니다.MT'가 하베스터에 의해 블록으로 선택됩니다. BlockUniqueHashTransactionValidator
는BatchUniqueHashTransactionValidator`와 동일한 로직을 가지고 있기 때문에 외부의 MT'의 해시만 확인합니다. 결과적으로 해당 트랜잭션은 수집된 블록에 ✅public ValidationResult validate(final Block ... final List<Hash> hashes = block.getTransactions().stream().map(HashUtils::calculateHash).collect(Collectors.toList()); return this.transactionHashCache.anyHashExists(hashes) ? ValidationResult.NEUTRAL : ValidationResult.SUCCESS; }새로 수집된 블록(MT'를 포함)이 처리될 때,
TransactionHashesObserver는 상태를 변경하는 블록 처리의 일부에서HashTransactionsCache에 H(MT'와 H(X)를 모두 추가하려고 시도합니다. H(X)가 이미UnconfirmedTransactionsCache에 존재하기 때문에 실패하고, 수집된 블록은 거부됩니다 🛑.5단계로 이동하여
UnconfirmedTransactionsCache에서 H(MT')가 만료될 때까지 기다립니다.

수정
수정 사항은 외부 및 내부 트랜잭션 해시를 모두 확인하도록 두 고유 해시 트랜잭션 유효성 검사기를 모두 업데이트하는 것입니다. 트랜잭션 유효성 검사기는 TransactionExtensions.streamDefault()를 사용하여 간단히 수행할 수 있습니다.
배치유니크해시 트랜잭션 검증자
트랜잭션 익스텐션에 TransactionExtensions.streamDefault를 추가하면 MT'(또는 이와 유사한)가 UnconfirmedTransactionsCache에 추가되는 것을 방지할 수 있습니다.
public ValidationResult validate(final List<TransactionsContextPair> groupedTransactions) {
if (groupedTransactions.isEmpty()) {
ValidationResult.SUCCESS를 반환합니다;
}
final List<Hash> hashes = groupedTransactions.stream().flatMap(pair -> pair.getTransactions().stream())
.flatMap(트랜잭션 -> 트랜잭션익스텐션.streamDefault(트랜잭션)).map(해시유틸스::계산해시)
.collect(Collectors.toList());
return this.validate(hashes);
}
블록유니크해시 트랜잭션 검증자
TransactionExtensions.streamDefault`를 추가하면 새로 수집된 블록에 MT(또는 이와 유사한)가 추가되는 것을 방지할 수 있습니다.
public ValidationResult validate(final Block 블록) {
if (block.getTransactions().isEmpty()) {
ValidationResult.SUCCESS를 반환합니다;
}
final List<Hash> hashes = block.getTransactions().stream().flatMap(transaction -> TransactionExtensions.streamDefault(transaction))
.map(HashUtils::calculateHash).collect(Collect
Translated with www.DeepL.com/Translator (free version)