|
3 | 3 | import static org.junit.Assert.assertThrows; |
4 | 4 | import static org.mockito.ArgumentMatchers.any; |
5 | 5 | import static org.mockito.ArgumentMatchers.anyBoolean; |
| 6 | +import static org.mockito.Mockito.atLeastOnce; |
6 | 7 | import static org.mockito.Mockito.doThrow; |
7 | 8 | import static org.mockito.Mockito.mock; |
8 | 9 | import static org.mockito.Mockito.spy; |
| 10 | +import static org.mockito.Mockito.verify; |
9 | 11 | import static org.mockito.Mockito.when; |
10 | 12 | import static org.tron.common.utils.Commons.adjustAssetBalanceV2; |
11 | 13 | import static org.tron.common.utils.Commons.adjustTotalShieldedPoolValue; |
@@ -1771,6 +1773,77 @@ public void switchForkShouldPostFullNodeFilterForNewBranch() throws Exception { |
1771 | 1773 | hasBlockFilterCapsule(queue, b2)); |
1772 | 1774 | } |
1773 | 1775 |
|
| 1776 | + /** |
| 1777 | + * A fork switch re-applies the new branch on a rewound, diverged state, so any signature |
| 1778 | + * verification cached on those transactions (isVerified) must be cleared to force |
| 1779 | + * re-validation against the fork-chain state. Drives a real reorg and asserts that switchFork |
| 1780 | + * resets isVerified on the transactions of the branch it switches to. |
| 1781 | + */ |
| 1782 | + @Test |
| 1783 | + public void switchForkShouldResetTransactionSignVerifiedOnNewBranch() throws Exception { |
| 1784 | + // bootstrap a head with a known witness |
| 1785 | + String key = PublicMethod.getRandomPrivateKey(); |
| 1786 | + byte[] privateKey = ByteArray.fromHexString(key); |
| 1787 | + final ECKey ecKey = ECKey.fromPrivate(privateKey); |
| 1788 | + byte[] address = ecKey.getAddress(); |
| 1789 | + ByteString addressByte = ByteString.copyFrom(address); |
| 1790 | + chainManager.getAccountStore().put(addressByte.toByteArray(), |
| 1791 | + new AccountCapsule(Protocol.Account.newBuilder().setAddress(addressByte).build())); |
| 1792 | + WitnessCapsule witnessCapsule = new WitnessCapsule(addressByte); |
| 1793 | + chainManager.getWitnessScheduleStore().saveActiveWitnesses(new ArrayList<>()); |
| 1794 | + chainManager.addWitness(addressByte); |
| 1795 | + chainManager.getWitnessStore().put(address, witnessCapsule); |
| 1796 | + Block block = blockGenerate.getSignedBlock( |
| 1797 | + witnessCapsule.getAddress(), 1533529947843L, privateKey); |
| 1798 | + dbManager.pushBlock(new BlockCapsule(block)); |
| 1799 | + |
| 1800 | + Map<ByteString, String> keys = addTestWitnessAndAccount(); |
| 1801 | + keys.put(addressByte, key); |
| 1802 | + |
| 1803 | + // fund an owner; transfers go owner -> witness 'address' (an existing account) |
| 1804 | + ECKey ownerKey = new ECKey(Utils.getRandom()); |
| 1805 | + byte[] owner = ownerKey.getAddress(); |
| 1806 | + AccountCapsule ownerAccount = new AccountCapsule( |
| 1807 | + Protocol.Account.newBuilder().setAddress(ByteString.copyFrom(owner)).build()); |
| 1808 | + ownerAccount.setBalance(1_000_000_000L); |
| 1809 | + chainManager.getAccountStore().put(owner, ownerAccount); |
| 1810 | + |
| 1811 | + long t = 1533529947843L; |
| 1812 | + long base = chainManager.getDynamicPropertiesStore().getLatestBlockHeaderNumber(); |
| 1813 | + long expiration = t + 1_000_000L; |
| 1814 | + |
| 1815 | + // common ancestor P (empty) — fork point and tapos reference |
| 1816 | + BlockCapsule p = createTestBlockCapsule(t + 3000, base + 1, |
| 1817 | + chainManager.getDynamicPropertiesStore().getLatestBlockHeaderHash().getByteString(), keys); |
| 1818 | + dbManager.pushBlock(p); |
| 1819 | + |
| 1820 | + // old branch: A extends P via the normal path and becomes head |
| 1821 | + BlockCapsule a = blockWithTransfer(t + 6000, base + 2, p.getBlockId().getByteString(), keys, |
| 1822 | + transfer(owner, address, 1L, p, expiration)); |
| 1823 | + dbManager.pushBlock(a); |
| 1824 | + Assert.assertEquals("control: head should be A after normal extend", |
| 1825 | + a.getBlockId(), chainManager.getDynamicPropertiesStore().getLatestBlockHeaderHash()); |
| 1826 | + |
| 1827 | + // heavier competing branch P -> B1 -> B2 forces switchFork; spy the tx on the branch we |
| 1828 | + // switch to and pre-mark it verified to mimic a stale cache computed on a different state |
| 1829 | + BlockCapsule b1 = blockWithTransfer(t + 6001, base + 2, p.getBlockId().getByteString(), keys, |
| 1830 | + transfer(owner, address, 2L, p, expiration)); |
| 1831 | + dbManager.pushBlock(b1); // num <= head -> kept in khaosDb, no switch yet |
| 1832 | + |
| 1833 | + TransactionCapsule forkTx = transfer(owner, address, 3L, p, expiration); |
| 1834 | + forkTx.setVerified(true); |
| 1835 | + TransactionCapsule spyTx = spy(forkTx); |
| 1836 | + BlockCapsule b2 = blockWithTransfer(t + 9000, base + 3, b1.getBlockId().getByteString(), keys, |
| 1837 | + spyTx); |
| 1838 | + dbManager.pushBlock(b2); // num > head & parent != head -> triggers switchFork |
| 1839 | + |
| 1840 | + Assert.assertEquals("reorg must switch the canonical head to the competing branch (B2)", |
| 1841 | + b2.getBlockId(), chainManager.getDynamicPropertiesStore().getLatestBlockHeaderHash()); |
| 1842 | + // switchFork must clear the cached verification flag on the new branch's transaction so it |
| 1843 | + // re-validates against the fork-chain state |
| 1844 | + verify(spyTx, atLeastOnce()).setVerified(false); |
| 1845 | + } |
| 1846 | + |
1774 | 1847 | private TransactionCapsule transfer(byte[] owner, byte[] to, long amount, |
1775 | 1848 | BlockCapsule refBlock, long expiration) { |
1776 | 1849 | TransferContract contract = TransferContract.newBuilder() |
|
0 commit comments