diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 21297082b4..be0f49a8e9 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -676,13 +676,21 @@ public TransactionApprovedList getTransactionApprovedList(Transaction trx) { } } + List approveList = new ArrayList<>(); if (trx.getSignatureCount() > 0) { - List approveList = new ArrayList<>(); byte[] hash = Sha256Hash.hash(CommonParameter .getInstance().isECKeyCryptoEngine(), trx.getRawData().toByteArray()); TransactionCapsule.checkWeight(permission, trx.getSignatureList(), hash, approveList); - tswBuilder.addAllApprovedList(approveList); } + if (trx.getPqAuthSigCount() > 0) { + if (!chainBaseManager.getDynamicPropertiesStore().isAnyPqSchemeAllowed()) { + throw new PermissionException( + "pq_auth_sig not allowed: no post-quantum scheme is activated"); + } + TransactionCapsule.validatePQSignatureGetWeight(trx, permission, + chainBaseManager.getDynamicPropertiesStore(), approveList); + } + tswBuilder.addAllApprovedList(approveList); resultBuilder.setCode(TransactionApprovedList.Result.response_code.SUCCESS); } catch (SignatureFormatException signEx) { resultBuilder.setCode(TransactionApprovedList.Result.response_code.SIGNATURE_FORMAT_ERROR); diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index 92231a762a..8e67b62dc1 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -9,6 +9,7 @@ import static org.tron.protos.Protocol.Transaction.Contract.ContractType.TransferContract; import static org.tron.protos.Protocol.Transaction.Result.contractResult.SUCCESS; +import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; @@ -969,7 +970,7 @@ public boolean pushTransaction(final TransactionCapsule trx) public void consumeMultiSignFee(TransactionCapsule trx, TransactionTrace trace) throws AccountResourceInsufficientException { - if (trx.getInstance().getSignatureCount() > 1) { + if (trx.getInstance().getSignatureCount() + trx.getInstance().getPqAuthSigCount() > 1) { long fee = getDynamicPropertiesStore().getMultiSignFee(); boolean disableJavaLangMath = getDynamicPropertiesStore().disableJavaLangMath(); List contracts = trx.getInstance().getRawData().getContractList(); @@ -1223,12 +1224,14 @@ private void switchFork(BlockCapsule newHead) } - private boolean isSameSig(TransactionCapsule tx1, TransactionCapsule tx2) { + @VisibleForTesting + boolean isSameSig(TransactionCapsule tx1, TransactionCapsule tx2) { if (tx1 == null || tx2 == null) { return false; } - if (tx1.getInstance().getSignatureCount() != tx2.getInstance().getSignatureCount()) { + if (tx1.getInstance().getSignatureCount() != tx2.getInstance().getSignatureCount() + || tx1.getInstance().getPqAuthSigCount() != tx2.getInstance().getPqAuthSigCount()) { return false; } @@ -1242,6 +1245,15 @@ private boolean isSameSig(TransactionCapsule tx1, TransactionCapsule tx2) { } } + if (flag) { + for (int i = 0; i < tx1.getInstance().getPqAuthSigCount(); i++) { + if (!tx1.getInstance().getPqAuthSig(i).equals(tx2.getInstance().getPqAuthSig(i))) { + flag = false; + break; + } + } + } + return flag; } diff --git a/framework/src/test/java/org/tron/core/WalletTest.java b/framework/src/test/java/org/tron/core/WalletTest.java index f441ac856c..2512d4f104 100644 --- a/framework/src/test/java/org/tron/core/WalletTest.java +++ b/framework/src/test/java/org/tron/core/WalletTest.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import javax.annotation.Resource; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -51,8 +52,11 @@ import org.tron.common.BaseTest; import org.tron.common.TestConstants; import org.tron.common.crypto.ECKey; +import org.tron.common.crypto.pqc.FNDSA512; +import org.tron.common.crypto.pqc.PQSchemeRegistry; import org.tron.common.parameter.CommonParameter; import org.tron.common.utils.ByteArray; +import org.tron.common.utils.Sha256Hash; import org.tron.common.utils.Utils; import org.tron.core.actuator.DelegateResourceActuator; import org.tron.core.actuator.FreezeBalanceActuator; @@ -88,6 +92,11 @@ import org.tron.protos.Protocol.BlockHeader; import org.tron.protos.Protocol.BlockHeader.raw; import org.tron.protos.Protocol.Exchange; +import org.tron.protos.Protocol.Key; +import org.tron.protos.Protocol.PQAuthSig; +import org.tron.protos.Protocol.PQScheme; +import org.tron.protos.Protocol.Permission; +import org.tron.protos.Protocol.Permission.PermissionType; import org.tron.protos.Protocol.Proposal; import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.Transaction.Contract; @@ -1565,5 +1574,152 @@ public void testApprovedListTooManySigs() { Assert.assertTrue(rejected.getResult().getMessage().contains("too many signatures")); assertEquals(0, rejected.getApprovedListCount()); } + + private static byte[] txIdOf(Transaction unsigned) { + return Sha256Hash.of(CommonParameter.getInstance().isECKeyCryptoEngine(), + unsigned.getRawData().toByteArray()).getBytes(); + } + + private Transaction buildApprovedListTransferTx(byte[] ownerAddress) { + return Transaction.newBuilder().setRawData( + Transaction.raw.newBuilder().addContract( + Contract.newBuilder().setType(ContractType.TransferContract) + .setParameter(Any.pack(TransferContract.newBuilder().setAmount(1) + .setOwnerAddress(ByteString.copyFrom(ownerAddress)) + .setToAddress(ByteString.copyFrom( + ByteArray.fromHexString(RECEIVER_ADDRESS))) + .build())).build()).build()).build(); + } + + @Test + public void testApprovedListPqOnlySigner() throws Exception { + chainBaseManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L); + FNDSA512 kp = new FNDSA512(); + byte[] signerAddr = PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, kp.getPublicKey()); + + ECKey accountKey = new ECKey(Utils.getRandom()); + byte[] ownerAddress = accountKey.getAddress(); + AccountCapsule owner = new AccountCapsule( + ByteString.copyFromUtf8("approved-pq-owner"), + ByteString.copyFrom(ownerAddress), + Protocol.AccountType.Normal, + initBalance); + Permission ownerPermission = Permission.newBuilder() + .setType(PermissionType.Owner) + .setPermissionName("owner") + .setThreshold(1) + .addKeys(Key.newBuilder().setAddress(ByteString.copyFrom(signerAddr)).setWeight(1L).build()) + .build(); + owner.updatePermissions(ownerPermission, null, Collections.emptyList()); + chainBaseManager.getAccountStore().put(ownerAddress, owner); + + Transaction unsigned = buildApprovedListTransferTx(ownerAddress); + byte[] sig = FNDSA512.sign(kp.getPrivateKey(), txIdOf(unsigned)); + Transaction signed = unsigned.toBuilder() + .addPqAuthSig(PQAuthSig.newBuilder() + .setScheme(PQScheme.FN_DSA_512) + .setPublicKey(ByteString.copyFrom(kp.getPublicKey())) + .setSignature(ByteString.copyFrom(sig)) + .build()) + .build(); + + GrpcAPI.TransactionApprovedList reply = wallet.getTransactionApprovedList(signed); + assertEquals(GrpcAPI.TransactionApprovedList.Result.response_code.SUCCESS, + reply.getResult().getCode()); + assertEquals(1, reply.getApprovedListCount()); + assertEquals(ByteString.copyFrom(signerAddr), reply.getApprovedList(0)); + } + + @Test + public void testApprovedListHybridEcdsaAndPq() throws Exception { + chainBaseManager.getDynamicPropertiesStore().saveAllowFnDsa512(1L); + ECKey ecKey = new ECKey(Utils.getRandom()); + FNDSA512 kp = new FNDSA512(); + byte[] pqSignerAddr = PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, kp.getPublicKey()); + + ECKey accountKey = new ECKey(Utils.getRandom()); + byte[] ownerAddress = accountKey.getAddress(); + AccountCapsule owner = new AccountCapsule( + ByteString.copyFromUtf8("approved-hybrid-owner"), + ByteString.copyFrom(ownerAddress), + Protocol.AccountType.Normal, + initBalance); + Permission ownerPermission = Permission.newBuilder() + .setType(PermissionType.Owner) + .setPermissionName("owner") + .setThreshold(2) + .addKeys(Key.newBuilder().setAddress(ByteString.copyFrom(ecKey.getAddress())) + .setWeight(1L).build()) + .addKeys(Key.newBuilder().setAddress(ByteString.copyFrom(pqSignerAddr)) + .setWeight(1L).build()) + .build(); + owner.updatePermissions(ownerPermission, null, Collections.emptyList()); + chainBaseManager.getAccountStore().put(ownerAddress, owner); + + Transaction unsigned = buildApprovedListTransferTx(ownerAddress); + + TransactionCapsule capsule = new TransactionCapsule(unsigned); + capsule.sign(ecKey.getPrivKeyBytes()); + ByteString ecdsaSig = capsule.getInstance().getSignature(0); + + byte[] pqSig = FNDSA512.sign(kp.getPrivateKey(), txIdOf(unsigned)); + Transaction signed = unsigned.toBuilder() + .addSignature(ecdsaSig) + .addPqAuthSig(PQAuthSig.newBuilder() + .setScheme(PQScheme.FN_DSA_512) + .setPublicKey(ByteString.copyFrom(kp.getPublicKey())) + .setSignature(ByteString.copyFrom(pqSig)) + .build()) + .build(); + + GrpcAPI.TransactionApprovedList reply = wallet.getTransactionApprovedList(signed); + assertEquals(GrpcAPI.TransactionApprovedList.Result.response_code.SUCCESS, + reply.getResult().getCode()); + assertEquals(2, reply.getApprovedListCount()); + } + + @Test + public void testApprovedListPqNotActivated() throws Exception { + // Disable every registered PQ scheme, not just FN_DSA_512: Wallet gates on + // isAnyPqSchemeAllowed(), so a scheme left enabled by default or a prior test + // would silently skip the "no post-quantum scheme is activated" path. + chainBaseManager.getDynamicPropertiesStore().saveAllowFnDsa512(0L); + chainBaseManager.getDynamicPropertiesStore().saveAllowMlDsa44(0L); + Assert.assertFalse(chainBaseManager.getDynamicPropertiesStore().isAnyPqSchemeAllowed()); + FNDSA512 kp = new FNDSA512(); + byte[] signerAddr = PQSchemeRegistry.computeAddress(PQScheme.FN_DSA_512, kp.getPublicKey()); + + ECKey accountKey = new ECKey(Utils.getRandom()); + byte[] ownerAddress = accountKey.getAddress(); + AccountCapsule owner = new AccountCapsule( + ByteString.copyFromUtf8("approved-pq-inactive-owner"), + ByteString.copyFrom(ownerAddress), + Protocol.AccountType.Normal, + initBalance); + Permission ownerPermission = Permission.newBuilder() + .setType(PermissionType.Owner) + .setPermissionName("owner") + .setThreshold(1) + .addKeys(Key.newBuilder().setAddress(ByteString.copyFrom(signerAddr)).setWeight(1L).build()) + .build(); + owner.updatePermissions(ownerPermission, null, Collections.emptyList()); + chainBaseManager.getAccountStore().put(ownerAddress, owner); + + Transaction unsigned = buildApprovedListTransferTx(ownerAddress); + Transaction signed = unsigned.toBuilder() + .addPqAuthSig(PQAuthSig.newBuilder() + .setScheme(PQScheme.FN_DSA_512) + .setPublicKey(ByteString.copyFrom(kp.getPublicKey())) + .setSignature(ByteString.copyFrom(new byte[1])) + .build()) + .build(); + + GrpcAPI.TransactionApprovedList reply = wallet.getTransactionApprovedList(signed); + assertEquals(GrpcAPI.TransactionApprovedList.Result.response_code.OTHER_ERROR, + reply.getResult().getCode()); + Assert.assertTrue(reply.getResult().getMessage().contains( + "no post-quantum scheme is activated")); + assertEquals(0, reply.getApprovedListCount()); + } } diff --git a/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java b/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java index cedbe691f7..2eef375c5b 100644 --- a/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java +++ b/framework/src/test/java/org/tron/core/actuator/utils/ProposalUtilTest.java @@ -816,8 +816,8 @@ public void validateAllowFnDsa512() { long maintenanceTimeInterval = forkUtils.getManager().getDynamicPropertiesStore() .getMaintenanceTimeInterval(); long hardForkTime = - ((ForkBlockVersionEnum.VERSION_4_8_2_PQ1.getHardForkTime() - 1) / maintenanceTimeInterval + 1) - * maintenanceTimeInterval; + ((ForkBlockVersionEnum.VERSION_4_8_2_PQ1.getHardForkTime() - 1) + / maintenanceTimeInterval + 1) * maintenanceTimeInterval; forkUtils.getManager().getDynamicPropertiesStore() .saveLatestBlockHeaderTimestamp(hardForkTime - 1); @@ -878,8 +878,8 @@ public void validateAllowMlDsa44() { long maintenanceTimeInterval = forkUtils.getManager().getDynamicPropertiesStore() .getMaintenanceTimeInterval(); long hardForkTime = - ((ForkBlockVersionEnum.VERSION_4_8_2_PQ1.getHardForkTime() - 1) / maintenanceTimeInterval + 1) - * maintenanceTimeInterval; + ((ForkBlockVersionEnum.VERSION_4_8_2_PQ1.getHardForkTime() - 1) + / maintenanceTimeInterval + 1) * maintenanceTimeInterval; forkUtils.getManager().getDynamicPropertiesStore() .saveLatestBlockHeaderTimestamp(hardForkTime - 1); diff --git a/framework/src/test/java/org/tron/core/db/ManagerTest.java b/framework/src/test/java/org/tron/core/db/ManagerTest.java index b31af7557f..3dc5bab277 100755 --- a/framework/src/test/java/org/tron/core/db/ManagerTest.java +++ b/framework/src/test/java/org/tron/core/db/ManagerTest.java @@ -109,7 +109,10 @@ import org.tron.core.store.StoreFactory; import org.tron.protos.Protocol; import org.tron.protos.Protocol.Account; +import org.tron.protos.Protocol.AccountType; import org.tron.protos.Protocol.Block; +import org.tron.protos.Protocol.PQAuthSig; +import org.tron.protos.Protocol.PQScheme; import org.tron.protos.Protocol.Transaction; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.AccountContract; @@ -210,6 +213,112 @@ public void updateRecentTransaction() throws Exception { Assert.assertEquals(trx.getTransactionId().toString(), item.getTransactionIds().get(0)); } + private static PQAuthSig dummyPqAuthSig() { + return PQAuthSig.newBuilder() + .setScheme(PQScheme.FN_DSA_512) + .setPublicKey(ByteString.copyFrom(new byte[1])) + .setSignature(ByteString.copyFrom(new byte[1])) + .build(); + } + + private AccountCapsule putMultiSignFeeOwner(String name) { + ECKey ownerKey = new ECKey(Utils.getRandom()); + byte[] ownerAddress = ownerKey.getAddress(); + AccountCapsule owner = new AccountCapsule(ByteString.copyFromUtf8(name), + ByteString.copyFrom(ownerAddress), AccountType.Normal, 10_000_000_000L); + chainManager.getAccountStore().put(ownerAddress, owner); + return owner; + } + + private TransactionCapsule buildTransferTxFrom(byte[] ownerAddress) { + TransferContract tc = TransferContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom(ownerAddress)) + .setToAddress(ByteString.copyFrom(ownerAddress)) + .setAmount(1L) + .build(); + return new TransactionCapsule(tc, ContractType.TransferContract); + } + + @Test + public void consumeMultiSignFeePqOnlyCharged() throws Exception { + AccountCapsule owner = putMultiSignFeeOwner("multisignfee-pq"); + byte[] ownerAddress = owner.getAddress().toByteArray(); + + Transaction tx = buildTransferTxFrom(ownerAddress).getInstance().toBuilder() + .addPqAuthSig(dummyPqAuthSig()) + .addPqAuthSig(dummyPqAuthSig()) + .build(); + TransactionCapsule trx = new TransactionCapsule(tx); + TransactionTrace trace = new TransactionTrace(trx, StoreFactory.getInstance(), + new RuntimeImpl()); + + long fee = dbManager.getDynamicPropertiesStore().getMultiSignFee(); + dbManager.consumeMultiSignFee(trx, trace); + + Assert.assertEquals(10_000_000_000L - fee, + chainManager.getAccountStore().get(ownerAddress).getBalance()); + Assert.assertEquals(fee, trace.getReceipt().getMultiSignFee()); + } + + @Test + public void consumeMultiSignFeeHybridCharged() throws Exception { + AccountCapsule owner = putMultiSignFeeOwner("multisignfee-hybrid"); + byte[] ownerAddress = owner.getAddress().toByteArray(); + + Transaction tx = buildTransferTxFrom(ownerAddress).getInstance().toBuilder() + .addSignature(ByteString.copyFrom(new byte[]{1})) + .addPqAuthSig(dummyPqAuthSig()) + .build(); + TransactionCapsule trx = new TransactionCapsule(tx); + TransactionTrace trace = new TransactionTrace(trx, StoreFactory.getInstance(), + new RuntimeImpl()); + + long fee = dbManager.getDynamicPropertiesStore().getMultiSignFee(); + dbManager.consumeMultiSignFee(trx, trace); + + Assert.assertEquals(10_000_000_000L - fee, + chainManager.getAccountStore().get(ownerAddress).getBalance()); + Assert.assertEquals(fee, trace.getReceipt().getMultiSignFee()); + } + + @Test + public void consumeMultiSignFeeSingleEcdsaNotCharged() throws Exception { + AccountCapsule owner = putMultiSignFeeOwner("multisignfee-single-ecdsa"); + byte[] ownerAddress = owner.getAddress().toByteArray(); + + Transaction tx = buildTransferTxFrom(ownerAddress).getInstance().toBuilder() + .addSignature(ByteString.copyFrom(new byte[]{1})) + .build(); + TransactionCapsule trx = new TransactionCapsule(tx); + TransactionTrace trace = new TransactionTrace(trx, StoreFactory.getInstance(), + new RuntimeImpl()); + + dbManager.consumeMultiSignFee(trx, trace); + + Assert.assertEquals(10_000_000_000L, + chainManager.getAccountStore().get(ownerAddress).getBalance()); + Assert.assertEquals(0L, trace.getReceipt().getMultiSignFee()); + } + + @Test + public void consumeMultiSignFeeSinglePqNotCharged() throws Exception { + AccountCapsule owner = putMultiSignFeeOwner("multisignfee-single-pq"); + byte[] ownerAddress = owner.getAddress().toByteArray(); + + Transaction tx = buildTransferTxFrom(ownerAddress).getInstance().toBuilder() + .addPqAuthSig(dummyPqAuthSig()) + .build(); + TransactionCapsule trx = new TransactionCapsule(tx); + TransactionTrace trace = new TransactionTrace(trx, StoreFactory.getInstance(), + new RuntimeImpl()); + + dbManager.consumeMultiSignFee(trx, trace); + + Assert.assertEquals(10_000_000_000L, + chainManager.getAccountStore().get(ownerAddress).getBalance()); + Assert.assertEquals(0L, trace.getReceipt().getMultiSignFee()); + } + @Test public void setBlockReference() throws ContractExeException, UnLinkedBlockException, ValidateScheduleException, @@ -885,6 +994,99 @@ public void getVerifyTxsTest() { Assert.assertEquals(txs.size(), 1); } + private static PQAuthSig pqAuthSig(String publicKey, String signature) { + return PQAuthSig.newBuilder() + .setScheme(PQScheme.FN_DSA_512) + .setPublicKey(ByteString.copyFrom(publicKey.getBytes())) + .setSignature(ByteString.copyFrom(signature.getBytes())) + .build(); + } + + private TransactionCapsule pqAndEcdsaSignedCapsule(String ecdsaSig, PQAuthSig... pqSigs) { + TransferContract c = TransferContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom("f1".getBytes())) + .setAmount(1).build(); + Transaction.Builder builder = new TransactionCapsule(c, ContractType.TransferContract) + .getInstance().toBuilder(); + if (ecdsaSig != null) { + builder.addSignature(ByteString.copyFrom(ecdsaSig.getBytes())); + } + for (PQAuthSig sig : pqSigs) { + builder.addPqAuthSig(sig); + } + return new TransactionCapsule(builder.build()); + } + + // isSameSig is package-private (@VisibleForTesting) so these exercise it directly, + // instead of indirectly through getVerifyTxs's pending-pool/BlockCapsule scaffolding. + + @Test + public void isSameSigNullIsFalse() { + TransactionCapsule tx = pqAndEcdsaSignedCapsule(null); + Assert.assertFalse(dbManager.isSameSig(null, tx)); + Assert.assertFalse(dbManager.isSameSig(tx, null)); + } + + @Test + public void isSameSigPqAuthSigContentDiffers() { + Assert.assertFalse(dbManager.isSameSig( + pqAndEcdsaSignedCapsule(null, pqAuthSig("pubA", "sigA")), + pqAndEcdsaSignedCapsule(null, pqAuthSig("pubB", "sigB")))); + } + + @Test + public void isSameSigPqAuthSigCountDiffers() { + Assert.assertFalse(dbManager.isSameSig( + pqAndEcdsaSignedCapsule(null), + pqAndEcdsaSignedCapsule(null, pqAuthSig("pubA", "sigA")))); + } + + @Test + public void isSameSigPqAuthSigIdenticalIsTrue() { + PQAuthSig sig = pqAuthSig("pubA", "sigA"); + Assert.assertTrue(dbManager.isSameSig( + pqAndEcdsaSignedCapsule(null, sig), + pqAndEcdsaSignedCapsule(null, sig))); + } + + @Test + public void isSameSigHybridEcdsaMatchesButPqDiffers() { + // The exact bug scenario: an identical ECDSA list must not mask a differing + // pq_auth_sig list. + Assert.assertFalse(dbManager.isSameSig( + pqAndEcdsaSignedCapsule("a", pqAuthSig("pubA", "sigA")), + pqAndEcdsaSignedCapsule("a", pqAuthSig("pubB", "sigB")))); + } + + @Test + public void getVerifyTxsClosesPqAuthSigBypass() { + // Integration proof: a block tx sharing the pending tx's txId (raw_data is + // identical) but carrying a different pq_auth_sig must still be sent back for + // re-verification by getVerifyTxs, not silently marked already-verified. + TransferContract c1 = TransferContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom("f1".getBytes())) + .setAmount(1).build(); + Transaction base = new TransactionCapsule(c1, ContractType.TransferContract).getInstance(); + Transaction pendingTx = base.toBuilder().addPqAuthSig(pqAuthSig("pubA", "sigA")).build(); + Transaction blockTx = base.toBuilder().addPqAuthSig(pqAuthSig("pubB", "sigB")).build(); + Assert.assertEquals(new TransactionCapsule(pendingTx).getTransactionId(), + new TransactionCapsule(blockTx).getTransactionId()); + + dbManager.getPendingTransactions().clear(); + try { + dbManager.getPendingTransactions().add(new TransactionCapsule(pendingTx)); + + List list = new ArrayList<>(); + list.add(blockTx); + BlockCapsule capsule = new BlockCapsule(0, ByteString.EMPTY, 0, list); + + List txs = dbManager.getVerifyTxs(capsule); + Assert.assertEquals(1, txs.size()); + } finally { + dbManager.getPendingTransactions().clear(); + } + } + @Test public void getVerifyTxsSkipsBlockWhenPermissionTxAlreadyConsumed() throws Exception { // Scenario: a permission-change tx (A) for owner X has been processed and consumed,