|
40 | 40 | import org.tron.common.TestConstants; |
41 | 41 | import org.tron.common.crypto.ECKey; |
42 | 42 | import org.tron.common.logsfilter.EventPluginLoader; |
| 43 | +import org.tron.common.logsfilter.capsule.BlockFilterCapsule; |
| 44 | +import org.tron.common.logsfilter.capsule.FilterTriggerCapsule; |
| 45 | +import org.tron.common.logsfilter.capsule.LogsFilterCapsule; |
43 | 46 | import org.tron.common.logsfilter.trigger.ContractLogTrigger; |
| 47 | +import org.tron.common.parameter.CommonParameter; |
44 | 48 | import org.tron.common.runtime.RuntimeImpl; |
45 | 49 | import org.tron.common.utils.ByteArray; |
46 | 50 | import org.tron.common.utils.Commons; |
@@ -1540,4 +1544,132 @@ public void adjustBalance(AccountStore accountStore, byte[] accountAddress, long |
1540 | 1544 | Commons.adjustBalance(accountStore, accountAddress, amount, |
1541 | 1545 | chainManager.getDynamicPropertiesStore().disableJavaLangMath()); |
1542 | 1546 | } |
| 1547 | + |
| 1548 | + /** |
| 1549 | + * Drives a real reorg and asserts what Manager posts to the jsonrpc filterCapsuleQueue. |
| 1550 | + */ |
| 1551 | + @Test |
| 1552 | + public void switchForkShouldPostFullNodeFilterForNewBranch() throws Exception { |
| 1553 | + CommonParameter.getInstance().jsonRpcHttpFullNodeEnable = true; |
| 1554 | + // filterProcessLoop only starts when isJsonRpcFilterEnabled() held at Manager.init() time; it |
| 1555 | + // was false then, so filterCapsuleQueue is produce-only here and fully observable. |
| 1556 | + |
| 1557 | + // bootstrap a head with a known witness |
| 1558 | + String key = PublicMethod.getRandomPrivateKey(); |
| 1559 | + byte[] privateKey = ByteArray.fromHexString(key); |
| 1560 | + final ECKey ecKey = ECKey.fromPrivate(privateKey); |
| 1561 | + byte[] address = ecKey.getAddress(); |
| 1562 | + ByteString addressByte = ByteString.copyFrom(address); |
| 1563 | + chainManager.getAccountStore().put(addressByte.toByteArray(), |
| 1564 | + new AccountCapsule(Protocol.Account.newBuilder().setAddress(addressByte).build())); |
| 1565 | + WitnessCapsule witnessCapsule = new WitnessCapsule(addressByte); |
| 1566 | + chainManager.getWitnessScheduleStore().saveActiveWitnesses(new ArrayList<>()); |
| 1567 | + chainManager.addWitness(addressByte); |
| 1568 | + chainManager.getWitnessStore().put(address, witnessCapsule); |
| 1569 | + Block block = blockGenerate.getSignedBlock( |
| 1570 | + witnessCapsule.getAddress(), 1533529947843L, privateKey); |
| 1571 | + dbManager.pushBlock(new BlockCapsule(block)); |
| 1572 | + |
| 1573 | + Map<ByteString, String> keys = addTestWitnessAndAccount(); |
| 1574 | + keys.put(addressByte, key); |
| 1575 | + |
| 1576 | + // fund an owner; transfers go owner -> witness 'address' (an existing account) |
| 1577 | + ECKey ownerKey = new ECKey(Utils.getRandom()); |
| 1578 | + byte[] owner = ownerKey.getAddress(); |
| 1579 | + AccountCapsule ownerAccount = new AccountCapsule( |
| 1580 | + Protocol.Account.newBuilder().setAddress(ByteString.copyFrom(owner)).build()); |
| 1581 | + ownerAccount.setBalance(1_000_000_000L); |
| 1582 | + chainManager.getAccountStore().put(owner, ownerAccount); |
| 1583 | + |
| 1584 | + long t = 1533529947843L; |
| 1585 | + long base = chainManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber(); |
| 1586 | + |
| 1587 | + // common ancestor P (empty) — fork point and tapos reference |
| 1588 | + BlockCapsule p = createTestBlockCapsule(t + 3000, base + 1, |
| 1589 | + chainManager.getDynamicPropertiesStore().getLatestBlockHeaderHash().getByteString(), keys); |
| 1590 | + dbManager.pushBlock(p); |
| 1591 | + |
| 1592 | + long expiration = t + 1_000_000L; |
| 1593 | + BlockingQueue<FilterTriggerCapsule> queue = |
| 1594 | + ReflectUtils.getFieldValue(dbManager, "filterCapsuleQueue"); |
| 1595 | + queue.clear(); |
| 1596 | + |
| 1597 | + // old branch: A carries a transfer; applied via the normal extend path |
| 1598 | + BlockCapsule a = blockWithTransfer(t + 6000, base + 2, p.getBlockId().getByteString(), keys, |
| 1599 | + transfer(owner, address, 1L, p, expiration)); |
| 1600 | + dbManager.pushBlock(a); |
| 1601 | + Assert.assertEquals("control: head should be A after normal extend", |
| 1602 | + a.getBlockId(), chainManager.getDynamicPropertiesStore().getLatestBlockHeaderHash()); |
| 1603 | + Assert.assertTrue("control: normal-path block A's logs must reach FULL stream (added)", |
| 1604 | + hasLogsFilterCapsule(queue, a, false)); |
| 1605 | + Assert.assertTrue("control: normal-path block A must reach the FULL block-filter stream", |
| 1606 | + hasBlockFilterCapsule(queue, a)); |
| 1607 | + |
| 1608 | + // heavier competing branch P -> B1 -> B2, each carrying a transfer, to force switchFork |
| 1609 | + BlockCapsule b1 = blockWithTransfer(t + 6001, base + 2, p.getBlockId().getByteString(), keys, |
| 1610 | + transfer(owner, address, 2L, p, expiration)); |
| 1611 | + dbManager.pushBlock(b1); // num <= head -> kept in khaosDb, no switch yet |
| 1612 | + BlockCapsule b2 = blockWithTransfer(t + 9000, base + 3, b1.getBlockId().getByteString(), keys, |
| 1613 | + transfer(owner, address, 3L, p, expiration)); |
| 1614 | + dbManager.pushBlock(b2); // num > head & parent != head -> triggers switchFork |
| 1615 | + |
| 1616 | + Assert.assertEquals("reorg must switch the canonical head to the competing branch (B2)", |
| 1617 | + b2.getBlockId(), chainManager.getDynamicPropertiesStore().getLatestBlockHeaderHash()); |
| 1618 | + |
| 1619 | + // reorg withdraws the orphaned old-branch logs (removed=true) |
| 1620 | + Assert.assertTrue("reorg: orphaned block A's logs must be withdrawn (removed=true)", |
| 1621 | + hasLogsFilterCapsule(queue, a, true)); |
| 1622 | + // the fix: new canonical blocks' logs and block filters are delivered |
| 1623 | + Assert.assertTrue("reorg: new canonical block B1's logs must reach FULL stream (added)", |
| 1624 | + hasLogsFilterCapsule(queue, b1, false)); |
| 1625 | + Assert.assertTrue("reorg: new canonical block B2's logs must reach FULL stream (added)", |
| 1626 | + hasLogsFilterCapsule(queue, b2, false)); |
| 1627 | + Assert.assertTrue("reorg: new canonical block B1 must reach the FULL block-filter stream", |
| 1628 | + hasBlockFilterCapsule(queue, b1)); |
| 1629 | + Assert.assertTrue("reorg: new canonical block B2 must reach the FULL block-filter stream", |
| 1630 | + hasBlockFilterCapsule(queue, b2)); |
| 1631 | + } |
| 1632 | + |
| 1633 | + private TransactionCapsule transfer(byte[] owner, byte[] to, long amount, |
| 1634 | + BlockCapsule refBlock, long expiration) { |
| 1635 | + TransferContract contract = TransferContract.newBuilder() |
| 1636 | + .setOwnerAddress(ByteString.copyFrom(owner)) |
| 1637 | + .setToAddress(ByteString.copyFrom(to)) |
| 1638 | + .setAmount(amount).build(); |
| 1639 | + TransactionCapsule tx = new TransactionCapsule(contract, ContractType.TransferContract); |
| 1640 | + tx.setReference(refBlock.getNum(), refBlock.getBlockId().getBytes()); |
| 1641 | + tx.setExpiration(expiration); |
| 1642 | + return tx; |
| 1643 | + } |
| 1644 | + |
| 1645 | + private BlockCapsule blockWithTransfer(long time, long number, ByteString parentHash, |
| 1646 | + Map<ByteString, String> keys, TransactionCapsule tx) { |
| 1647 | + ByteString witnessAddress = dposSlot.getScheduledWitness(dposSlot.getSlot(time)); |
| 1648 | + BlockCapsule blockCapsule = new BlockCapsule(number, Sha256Hash.wrap(parentHash), time, |
| 1649 | + witnessAddress); |
| 1650 | + blockCapsule.addTransaction(tx); |
| 1651 | + blockCapsule.generatedByMyself = true; |
| 1652 | + blockCapsule.setMerkleRoot(); |
| 1653 | + blockCapsule.sign(ByteArray.fromHexString(keys.get(witnessAddress))); |
| 1654 | + return blockCapsule; |
| 1655 | + } |
| 1656 | + |
| 1657 | + private boolean hasLogsFilterCapsule(BlockingQueue<FilterTriggerCapsule> queue, BlockCapsule b, |
| 1658 | + boolean removed) { |
| 1659 | + String blockHash = b.getBlockId().toString(); |
| 1660 | + return queue.stream() |
| 1661 | + .filter(c -> c instanceof LogsFilterCapsule) |
| 1662 | + .map(c -> (LogsFilterCapsule) c) |
| 1663 | + .anyMatch(c -> !c.isSolidified() && c.isRemoved() == removed |
| 1664 | + && blockHash.equals(c.getBlockHash())); |
| 1665 | + } |
| 1666 | + |
| 1667 | + private boolean hasBlockFilterCapsule(BlockingQueue<FilterTriggerCapsule> queue, |
| 1668 | + BlockCapsule b) { |
| 1669 | + String blockHash = b.getBlockId().toString(); |
| 1670 | + return queue.stream() |
| 1671 | + .filter(c -> c instanceof BlockFilterCapsule) |
| 1672 | + .map(c -> (BlockFilterCapsule) c) |
| 1673 | + .anyMatch(c -> !c.isSolidified() && blockHash.equals(c.getBlockHash())); |
| 1674 | + } |
1543 | 1675 | } |
0 commit comments