Skip to content

Commit c3263dc

Browse files
authored
fix(jsonrpc): post log/block filters for reorg-applied blocks (#6819)
1 parent ea3ffb4 commit c3263dc

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

framework/src/main/java/org/tron/core/db/Manager.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,8 @@ private void switchFork(BlockCapsule newHead)
12011201
}
12021202
}
12031203
}
1204+
// only reached when the whole new branch applied cleanly; a failed switch rethrows above
1205+
reApplyLogsFilter(first);
12041206
}
12051207

12061208
}
@@ -2284,6 +2286,20 @@ private void reOrgLogsFilter() {
22842286
}
22852287
}
22862288

2289+
// Post the FULL-stream block and logs filters for each block of the new canonical branch
2290+
// (oldest-first). Must be kept in sync with the FULL-filter section of blockTrigger.
2291+
// Solidity filters are intentionally not posted here: solidification events for these
2292+
// blocks arrive later, when postSolidityFilter runs against the then-canonical chain.
2293+
private void reApplyLogsFilter(List<KhaosBlock> newBranch) {
2294+
if (CommonParameter.getInstance().isJsonRpcHttpFullNodeEnable()) {
2295+
for (KhaosBlock khaosBlock : newBranch) {
2296+
BlockCapsule blockCapsule = khaosBlock.getBlk();
2297+
postBlockFilter(blockCapsule, false);
2298+
postLogsFilter(blockCapsule, false, false);
2299+
}
2300+
}
2301+
}
2302+
22872303
private void postBlockFilter(final BlockCapsule blockCapsule, boolean solidified) {
22882304
BlockFilterCapsule blockFilterCapsule =
22892305
new BlockFilterCapsule(blockCapsule, solidified);

framework/src/test/java/org/tron/core/db/ManagerTest.java

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@
4040
import org.tron.common.TestConstants;
4141
import org.tron.common.crypto.ECKey;
4242
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;
4346
import org.tron.common.logsfilter.trigger.ContractLogTrigger;
47+
import org.tron.common.parameter.CommonParameter;
4448
import org.tron.common.runtime.RuntimeImpl;
4549
import org.tron.common.utils.ByteArray;
4650
import org.tron.common.utils.Commons;
@@ -1540,4 +1544,132 @@ public void adjustBalance(AccountStore accountStore, byte[] accountAddress, long
15401544
Commons.adjustBalance(accountStore, accountAddress, amount,
15411545
chainManager.getDynamicPropertiesStore().disableJavaLangMath());
15421546
}
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+
}
15431675
}

0 commit comments

Comments
 (0)