Update 3/30/2023
public void notify(final TransactionHashesNotification notification, final BlockNotificationContext context) { if (NotificationTrigger.Execute == context.getTrigger()) { this.transactionHashCache.putAll(notification.getPairs()); } ...
TransactionsHashesNotification
is created, streamDefault
is used, which expands to both outer and inner transactions: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(new TransactionHashesNotification(pairs)); }
BatchUniqueHashTransactionValidator
checks that H(MT') is not in HashTransactionsCache, so it gets added to 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()
returns Collection<Transaction>
, which only contains outer transactions. As a result, H(MT') != H(MT) is checked but H(X) is not checked.BlockUniqueHashTransactionValidator
has the same logic as BatchUniqueHashTransactionValidator
, so only the hash of the outer MT' is checked. As a result, the transaction is included in the harvested block ✅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; }
TransactionHashesObserver
attempts to add both H(MT') and H(X) to HashTransactionsCache
during part of the block processing that makes state changes. This fails because H(X) is already present in the UnconfirmedTransactionsCache
, so the harvested block gets rejected 🛑UnconfirmedTransactionsCache
TransactionExtensions.streamDefault()
.TransactionExtensions.streamDefault
will prevent MT' (or similar) from getting added to the `UnconfirmedTransactionsCache.public ValidationResult validate(final List<TransactionsContextPair> groupedTransactions) { if (groupedTransactions.isEmpty()) { return ValidationResult.SUCCESS; } final List<Hash> hashes = groupedTransactions.stream().flatMap(pair -> pair.getTransactions().stream()) .flatMap(transaction -> TransactionExtensions.streamDefault(transaction)).map(HashUtils::calculateHash) .collect(Collectors.toList()); return this.validate(hashes); }
TransactionExtensions.streamDefault
will prevent MT' (or similar) from getting added to a newly harvested block.public ValidationResult validate(final Block block) { if (block.getTransactions().isEmpty()) { return ValidationResult.SUCCESS; } final List<Hash> hashes = block.getTransactions().stream().flatMap(transaction -> TransactionExtensions.streamDefault(transaction)) .map(HashUtils::calculateHash).collect(Collectors.toList()); return this.transactionHashCache.anyHashExists(hashes) ? ValidationResult.NEUTRAL : ValidationResult.SUCCESS; }