From 53c49b44f7e0c3898cf1df778950e1670331b5ba Mon Sep 17 00:00:00 2001 From: Valentin Tronkov Date: Mon, 18 Dec 2023 21:02:28 +0200 Subject: [PATCH 01/80] feat: Unconditional, enriched, traced records of UtilPrng system contract calls (#10074) Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../systemcontracts/HtsSystemContract.java | 11 +- .../systemcontracts/PrngSystemContract.java | 69 +++++-- .../records/ContractCallRecordBuilder.java | 4 + .../impl/utils/SystemContractUtils.java | 31 ++- .../HandleSystemContractOperationsTest.java | 26 ++- .../HtsSystemContractTest.java | 23 +++ .../PrngSystemContractTest.java | 45 ++++ .../test/state/ProxyWorldUpdaterTest.java | 6 +- .../test/utils/SystemContractUtilsTest.java | 14 +- .../record-snapshots/PrngPrecompile.json | 194 ++++++++++++++++++ .../spec/utilops/records/SnapshotModeOp.java | 3 +- .../precompile/PrngPrecompileSuite.java | 70 ++++++- 12 files changed, 463 insertions(+), 33 deletions(-) create mode 100644 hedera-node/test-clients/record-snapshots/PrngPrecompile.json diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java index cc49356e622d..838f6f4aaed9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java @@ -30,6 +30,8 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; +import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.spi.workflows.HandleException; import edu.umd.cs.findbugs.annotations.NonNull; @@ -108,11 +110,18 @@ private static FullResult resultOfExecuting( if (responseCode == SUCCESS) { final var output = pricedResult.fullResult().result().getOutput(); + var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); + final var senderId = ((ProxyEvmAccount) updater.getAccount(frame.getSenderAddress())).hederaId(); + enhancement .systemOperations() .externalizeResult( contractFunctionResultSuccessFor( - pricedResult.fullResult().gasRequirement(), output, HTS_CONTRACT_ID), + pricedResult.fullResult().gasRequirement(), + output, + frame.getRemainingGas(), + frame.getInputData(), + senderId), responseCode); } else { enhancement diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java index dd53bcc39749..ee56a62e75a1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java @@ -16,18 +16,28 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts; -import static com.hedera.hapi.node.base.ResponseCodeEnum.FAIL_INVALID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaOperations.ZERO_ENTROPY; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmContractId; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.HTS_PRECOMPILE_MIRROR_ID; import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedFor; import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultSuccessFor; +import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FAIL_INVALID; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; import static java.util.Objects.requireNonNull; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.hapi.node.util.UtilPrngTransactionBody; +import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; +import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; +import com.hedera.node.app.service.mono.pbj.PbjConverter; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -74,6 +84,7 @@ public FullResult computeFully(@NonNull final Bytes input, @NonNull final Messag final ContractID contractID = asEvmContractId(Address.fromHexString(PRNG_PRECOMPILE_ADDRESS)); try { + validateTrue(frame.getRemainingGas() >= gasRequirement, INSUFFICIENT_GAS); // compute the pseudorandom number final var randomNum = generatePseudoRandomData(input, frame); requireNonNull(randomNum); @@ -85,7 +96,7 @@ public FullResult computeFully(@NonNull final Bytes input, @NonNull final Messag return new FullResult(result, gasRequirement, null); } catch (InvalidTransactionException e) { // This error is caused by the user sending in the wrong selector - createFailedRecord(frame, FAIL_INVALID.toString(), contractID); + createFailedRecord(frame, e.getResponseCode(), contractID); return new FullResult( PrecompiledContract.PrecompileContractResult.halt(Bytes.EMPTY, Optional.of(INVALID_OPERATION)), gasRequirement, @@ -93,7 +104,7 @@ public FullResult computeFully(@NonNull final Bytes input, @NonNull final Messag } catch (NullPointerException e) { // Log a warning as this error will be caused by insufficient entropy log.warn("Internal precompile failure", e); - createFailedRecord(frame, FAIL_INVALID.toString(), contractID); + createFailedRecord(frame, FAIL_INVALID, contractID); return new FullResult( PrecompiledContract.PrecompileContractResult.halt(Bytes.EMPTY, Optional.of(INVALID_OPERATION)), gasRequirement, @@ -108,30 +119,64 @@ void createSuccessfulRecord( requireNonNull(randomNum); requireNonNull(contractID); var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); - updater.externalizeSystemContractResults( - contractFunctionResultSuccessFor(gasRequirement, randomNum, contractID), SUCCESS); + final var senderId = ((ProxyEvmAccount) updater.getAccount(frame.getSenderAddress())).hederaId(); + + var data = contractFunctionResultSuccessFor( + gasRequirement, randomNum, frame.getRemainingGas(), frame.getInputData(), senderId); + + updater.enhancement() + .systemOperations() + .dispatch( + synthBody(), + new ActiveContractVerificationStrategy( + senderId.accountNum(), contractID.evmAddress(), false, UseTopLevelSigs.NO), + senderId, + ContractCallRecordBuilder.class) + .contractCallResult(data) + .entropyBytes(tuweniToPbjBytes(randomNum)); } } void createFailedRecord( - @NonNull MessageFrame frame, @NonNull final String errorMsg, @NonNull final ContractID contractID) { + @NonNull MessageFrame frame, + @NonNull final ResponseCodeEnum responseCode, + @NonNull final ContractID contractID) { if (!frame.isStatic()) { requireNonNull(frame); requireNonNull(contractID); - contractFunctionResultFailedFor(gasRequirement, errorMsg, contractID); var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); - updater.externalizeSystemContractResults( - contractFunctionResultFailedFor(gasRequirement, errorMsg, contractID), FAIL_INVALID); + + final var senderId = ((ProxyEvmAccount) updater.getAccount(frame.getSenderAddress())).hederaId(); + var contractResult = contractFunctionResultFailedFor(gasRequirement, responseCode.toString(), contractID); + contractResult = contractResult + .copyBuilder() + .functionParameters(tuweniToPbjBytes(frame.getInputData())) + .errorMessage(null) + .contractID(HTS_PRECOMPILE_MIRROR_ID) + .senderId(senderId) + .gas(frame.getRemainingGas()) + .build(); + + updater.enhancement() + .systemOperations() + .externalizePreemptedDispatch(synthBody(), PbjConverter.toPbj(responseCode)) + .contractCallResult(contractResult); } } + private TransactionBody synthBody() { + return TransactionBody.newBuilder() + .utilPrng(UtilPrngTransactionBody.DEFAULT) + .build(); + } + Bytes generatePseudoRandomData(@NonNull final Bytes input, @NonNull final MessageFrame frame) { final var selector = input.getInt(0); if (selector == PSEUDORANDOM_SEED_GENERATOR_SELECTOR) { return random256BitGenerator(frame); } throw new InvalidTransactionException( - "Invalid selector for PRNG precompile", ResponseCodeEnum.INVALID_TRANSACTION); + "Invalid selector for PRNG precompile", ResponseCodeEnum.REVERTED_SUCCESS); } Bytes random256BitGenerator(final MessageFrame frame) { @@ -139,7 +184,7 @@ Bytes random256BitGenerator(final MessageFrame frame) { if (entropy.equals(Bytes.wrap(ZERO_ENTROPY.toByteArray()))) { return null; } - return entropy; + return entropy.slice(0, 32); } long calculateGas(@NonNull final Instant now) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java index e059d255585c..3975d72f47b5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java @@ -20,6 +20,7 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; @@ -86,4 +87,7 @@ public interface ContractCallRecordBuilder extends GasFeeRecordBuilder { * @return new total supply of a token */ long getNewTotalSupply(); + + @NonNull + ContractCallRecordBuilder entropyBytes(@NonNull final Bytes prngBytes); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java index 41288e1f161c..d8a39596917b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java @@ -18,16 +18,30 @@ import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import com.google.common.primitives.Longs; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.contract.ContractFunctionResult; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; /** * Utilities for system contracts. */ public final class SystemContractUtils { + /* + The contractFunctionResultSuccessFor is called from Prgn contract and we are setting the HTS address - this is done + to mirror the current mono behaviour(PrngSystemPrecompiledContract.computePrecompile > createSuccessfulChildRecord > + addContractCallResultToRecord > PrecompileUtils.addContractCallResultToRecord). This will be + fixed after the differential testing in this story https://github.com/hashgraph/hedera-services/issues/10552 + */ + public static final String HTS_PRECOMPILED_CONTRACT_ADDRESS = "0x167"; + public static final ContractID HTS_PRECOMPILE_MIRROR_ID = contractIdFromEvmAddress( + Address.fromHexString(HTS_PRECOMPILED_CONTRACT_ADDRESS).toArrayUnsafe()); + private SystemContractUtils() { throw new UnsupportedOperationException("Utility Class"); } @@ -41,16 +55,21 @@ public enum ResultStatus { * Create a successful contract function result. * @param gasUsed Report the gas used. * @param result The result of the contract call. - * @param contractID The contract ID. + * @param gas The remaining gas. + * @param inputData The input data. + * @param senderId The sender id. * @return The created contract function result for a successful call. */ @NonNull public static ContractFunctionResult contractFunctionResultSuccessFor( - final long gasUsed, final Bytes result, final ContractID contractID) { + final long gasUsed, final Bytes result, long gas, Bytes inputData, AccountID senderId) { return ContractFunctionResult.newBuilder() .gasUsed(gasUsed) + .gas(gas) .contractCallResult(tuweniToPbjBytes(result)) - .contractID(contractID) + .functionParameters(tuweniToPbjBytes(inputData)) + .senderId(senderId) + .contractID(HTS_PRECOMPILE_MIRROR_ID) .build(); } @@ -70,4 +89,10 @@ public static ContractFunctionResult contractFunctionResultFailedFor( .contractID(contractID) .build(); } + + private static ContractID contractIdFromEvmAddress(final byte[] bytes) { + return ContractID.newBuilder() + .contractNum(Longs.fromByteArray(Arrays.copyOfRange(bytes, 12, 20))) + .build(); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java index e67c91e23aa7..749a40b2f07b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java @@ -27,7 +27,7 @@ import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.verify; -import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.transaction.TransactionBody; @@ -42,6 +42,7 @@ import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.HandleContext; import java.util.function.Predicate; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -70,6 +71,9 @@ class HandleSystemContractOperationsTest { @Mock private SignatureVerification failed; + @Mock + private MessageFrame messageFrame; + private HandleSystemContractOperations subject; @BeforeEach @@ -110,19 +114,23 @@ void dispatchesRespectingGivenStrategy() { @Test void externalizeSuccessfulResultTest() { var contractFunctionResult = SystemContractUtils.contractFunctionResultSuccessFor( - 0, org.apache.tuweni.bytes.Bytes.EMPTY, ContractID.DEFAULT); + 0, + org.apache.tuweni.bytes.Bytes.EMPTY, + 100L, + org.apache.tuweni.bytes.Bytes.EMPTY, + AccountID.newBuilder().build()); // given given(context.addChildRecordBuilder(ContractCallRecordBuilder.class)).willReturn(recordBuilder); given(recordBuilder.transaction(Transaction.DEFAULT)).willReturn(recordBuilder); given(recordBuilder.status(ResponseCodeEnum.SUCCESS)).willReturn(recordBuilder); - given(recordBuilder.contractID(ContractID.DEFAULT)).willReturn(recordBuilder); + given(recordBuilder.contractID(any())).willReturn(recordBuilder); // when subject.externalizeResult(contractFunctionResult, ResponseCodeEnum.SUCCESS); // then - verify(recordBuilder).contractID(ContractID.DEFAULT); + verify(recordBuilder).contractID(any()); verify(recordBuilder).transaction(Transaction.DEFAULT); verify(recordBuilder).status(ResponseCodeEnum.SUCCESS); verify(recordBuilder).contractCallResult(contractFunctionResult); @@ -131,19 +139,23 @@ void externalizeSuccessfulResultTest() { @Test void externalizeFailedResultTest() { var contractFunctionResult = SystemContractUtils.contractFunctionResultSuccessFor( - 0, org.apache.tuweni.bytes.Bytes.EMPTY, ContractID.DEFAULT); + 0, + org.apache.tuweni.bytes.Bytes.EMPTY, + 100L, + org.apache.tuweni.bytes.Bytes.EMPTY, + AccountID.newBuilder().build()); // given given(context.addChildRecordBuilder(ContractCallRecordBuilder.class)).willReturn(recordBuilder); given(recordBuilder.transaction(Transaction.DEFAULT)).willReturn(recordBuilder); given(recordBuilder.status(ResponseCodeEnum.FAIL_INVALID)).willReturn(recordBuilder); - given(recordBuilder.contractID(ContractID.DEFAULT)).willReturn(recordBuilder); + given(recordBuilder.contractID(any())).willReturn(recordBuilder); // when subject.externalizeResult(contractFunctionResult, ResponseCodeEnum.FAIL_INVALID); // then - verify(recordBuilder).contractID(ContractID.DEFAULT); + verify(recordBuilder).contractID(any()); verify(recordBuilder).transaction(Transaction.DEFAULT); verify(recordBuilder).status(ResponseCodeEnum.FAIL_INVALID); verify(recordBuilder).contractCallResult(contractFunctionResult); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java index e870160fb05b..0b5de8a0a930 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java @@ -27,7 +27,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.hedera.hapi.node.base.AccountID; import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; @@ -35,9 +38,11 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import java.nio.ByteBuffer; import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -93,6 +98,7 @@ void clear() { @Test void returnsResultFromImpliedCall() { + messageFrameMock(); givenValidCallAttempt(); final var pricedResult = gasOnly(successResult(ByteBuffer.allocate(1), 123L), SUCCESS, true); @@ -122,6 +128,8 @@ void internalErrorAttemptHaltsAndConsumesRemainingGas() { @Test void callWithNonGasCostNotImplemented() { + messageFrameMock(); + givenValidCallAttempt(); final var pricedResult = new HtsCall.PricedResult(successResult(ByteBuffer.allocate(1), 123L), 456L, SUCCESS, true); @@ -138,4 +146,19 @@ private void givenValidCallAttempt() { given(attemptFactory.createCallAttemptFrom(Bytes.EMPTY, frame)).willReturn(attempt); given(attempt.asExecutableCall()).willReturn(call); } + + private void messageFrameMock() { + final var worldUpdater = mock(ProxyWorldUpdater.class); + final var address = Address.fromHexString("0x100"); + final var remainingGas = 10000L; + when(frame.getWorldUpdater()).thenReturn(worldUpdater); + when(frame.getSenderAddress()).thenReturn(address); + when(frame.getRemainingGas()).thenReturn(remainingGas); + when(frame.getInputData()).thenReturn(org.apache.tuweni.bytes.Bytes.EMPTY); + + final var mutableAccount = mock(ProxyEvmAccount.class); + final var accountID = mock(AccountID.class); + when(worldUpdater.getAccount(address)).thenReturn(mutableAccount); + when(mutableAccount.hederaId()).thenReturn(accountID); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/PrngSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/PrngSystemContractTest.java index 942b710316e8..5486991fd875 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/PrngSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/PrngSystemContractTest.java @@ -23,12 +23,20 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.PSEUDO_RANDOM_SYSTEM_CONTRACT_ADDRESS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.hedera.hapi.node.base.AccountID; +import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.PrngSystemContract; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -57,6 +65,18 @@ class PrngSystemContractTest { @Mock ContractCallRecordBuilder contractCallRecordBuilder; + @Mock + private ProxyEvmAccount mutableAccount; + + @Mock + private AccountID accountID; + + @Mock + private Enhancement enhancement; + + @Mock + private SystemContractOperations systemContractOperations; + private PrngSystemContract subject; @BeforeEach @@ -98,10 +118,15 @@ void computePrecompileStaticSuccessTest() { void computePrecompileMutableSuccessTest() { // given: givenCommon(); + commonMocks(); given(messageFrame.isStatic()).willReturn(false); given(messageFrame.getWorldUpdater()).willReturn(proxyWorldUpdater); given(proxyWorldUpdater.entropy()).willReturn(EXPECTED_RANDOM_NUMBER); + when(systemContractOperations.dispatch(any(), any(), any(), any())).thenReturn(contractCallRecordBuilder); + when(contractCallRecordBuilder.contractCallResult(any())).thenReturn(contractCallRecordBuilder); + when(contractCallRecordBuilder.entropyBytes(any())).thenReturn(contractCallRecordBuilder); + // when: var actual = subject.computeFully(PSEUDO_RANDOM_SYSTEM_CONTRACT_ADDRESS, messageFrame) .result(); @@ -114,9 +139,12 @@ void computePrecompileMutableSuccessTest() { void computePrecompileFailedTest() { // given: givenCommon(); + commonMocks(); given(messageFrame.isStatic()).willReturn(false); given(messageFrame.getWorldUpdater()).willReturn(proxyWorldUpdater); given(proxyWorldUpdater.entropy()).willReturn(Bytes.wrap(ZERO_ENTROPY.toByteArray())); + when(systemContractOperations.externalizePreemptedDispatch(any(), any())) + .thenReturn(mock(ContractCallRecordBuilder.class)); // when: var actual = subject.computeFully(PSEUDO_RANDOM_SYSTEM_CONTRACT_ADDRESS, messageFrame) @@ -129,8 +157,12 @@ void computePrecompileFailedTest() { @Test void wrongFunctionSelectorFailedTest() { // given: + commonMocks(); givenCommon(); + when(systemContractOperations.externalizePreemptedDispatch(any(), any())) + .thenReturn(mock(ContractCallRecordBuilder.class)); + // when: var actual = subject.computeFully(EXPECTED_RANDOM_NUMBER, messageFrame).result(); @@ -153,4 +185,17 @@ private void assertEqualContractResult(PrecompileContractResult expected, Precom assertEquals(expected.getOutput(), actual.getOutput()); assertEquals(expected.getHaltReason(), actual.getHaltReason()); } + + private void commonMocks() { + final var address = Address.fromHexString("0x100"); + final var remainingGas = 10000L; + when(messageFrame.getSenderAddress()).thenReturn(address); + when(messageFrame.getRemainingGas()).thenReturn(remainingGas); + when(messageFrame.getInputData()).thenReturn(org.apache.tuweni.bytes.Bytes.EMPTY); + + when(proxyWorldUpdater.getAccount(any())).thenReturn(mutableAccount); + when(proxyWorldUpdater.enhancement()).thenReturn(enhancement); + when(enhancement.systemOperations()).thenReturn(systemContractOperations); + when(mutableAccount.hederaId()).thenReturn(accountID); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java index 8bd0c92a5a06..35f5235c2226 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java @@ -503,7 +503,11 @@ void delegatesEntropy() { @Test void externalizeSystemContractResultTest() { var contractFunctionResult = SystemContractUtils.contractFunctionResultSuccessFor( - 0, org.apache.tuweni.bytes.Bytes.EMPTY, ContractID.DEFAULT); + 0, + org.apache.tuweni.bytes.Bytes.EMPTY, + 100L, + org.apache.tuweni.bytes.Bytes.EMPTY, + AccountID.newBuilder().build()); subject.externalizeSystemContractResults(contractFunctionResult, ResponseCodeEnum.SUCCESS); verify(systemContractOperations).externalizeResult(contractFunctionResult, ResponseCodeEnum.SUCCESS); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java index b0af2a934ff8..4be9383023f6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java @@ -17,8 +17,10 @@ package com.hedera.node.app.service.contract.impl.test.utils; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.HTS_PRECOMPILE_MIRROR_ID; import static org.assertj.core.api.Assertions.assertThat; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractFunctionResult; @@ -35,12 +37,20 @@ class SystemContractUtilsTest { @Test void validateSuccessfulContractResults() { + final var gas = 100L; + final var inputData = org.apache.tuweni.bytes.Bytes.EMPTY; + final var senderId = AccountID.newBuilder().build(); + final var expected = ContractFunctionResult.newBuilder() .gasUsed(gasUsed) .contractCallResult(tuweniToPbjBytes(result)) - .contractID(contractID) + .contractID(HTS_PRECOMPILE_MIRROR_ID) + .gas(gas) + .senderId(senderId) + .functionParameters(tuweniToPbjBytes(inputData)) .build(); - final var actual = SystemContractUtils.contractFunctionResultSuccessFor(gasUsed, result, contractID); + final var actual = + SystemContractUtils.contractFunctionResultSuccessFor(gasUsed, result, gas, inputData, senderId); assertThat(actual).isEqualTo(expected); } diff --git a/hedera-node/test-clients/record-snapshots/PrngPrecompile.json b/hedera-node/test-clients/record-snapshots/PrngPrecompile.json new file mode 100644 index 000000000000..b0ffbaa39d78 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/PrngPrecompile.json @@ -0,0 +1,194 @@ +{ + "specSnapshots": { + "emptyInputCallFails": { + "placeholderNum": 1001, + "encodedItems": [ + { + "b64Body": "Cg8KCQjf3fCrBhD8AhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIBEW9GGy+j/YiVSjghgyMpM7yr3BxAyyXiVWKJ7DDguqEICU69wDSgUIgM7aAw==", + "b64Record": "CiUIFhIDGOoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDGq8C1WMRugVhM/NUfSjuwYrIQ1T2M3zz20TzAWwIKPjRyJa8IfBv3QiMqJ/zcxHcaDAib3vCrBhDb+MrXAiIPCgkI393wqwYQ/AISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjqBxCAqNa5Bw==" + }, + { + "b64Body": "Cg8KCQjg3fCrBhD+AhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwicrMuvBhDIuoBmGm0KIhIgi4VCLiFafYTHPJSODRAEb/0iNWl0z15QFKILB67VitgKIzohAxFrHd+aChEZIldaZpkP2XkjbpibvsMN1EF9BVEAnDyWCiISIPkbZkegYUbXoa1JE3bvzojCLmNP34lBxnPOeYx+51gWIgxIZWxsbyBXb3JsZCEqADIA", + "b64Record": "CiUIFhoDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAdWQLcPVvYPqfSF8l7PqrPNFh8qsmKChQBr2RpSO/53n/Agv6xQF04fKCffgLd7/UaCwic3vCrBhDDhMB8Ig8KCQjg3fCrBhD+AhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA=" + }, + { + "b64Body": "Cg8KCQjg3fCrBhCCAxICGAISAhgDGIflvDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmgsKAxjrByKSCzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDJhOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2Q4M2JmOWExMTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDAzODYxMDA0ZTU2NWI2MDQwNTE2MTAwNDU5MTkwNjEwMTY5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDYwMDA2MTAxNjk3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDgzYmY5YTE2MGUwMWI2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwZTQ5MTkwNjEwMWZlNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxMjE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxMjY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDEzNTU3NjAwMDgwZmQ1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwMTQ5OTE5MDYxMDI0NjU2NWI5MjUwNTA1MDkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMTYzODE2MTAxNTA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDE3ZTYwMDA4MzAxODQ2MTAxNWE1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDFiODU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDE5ZDU2NWI4MzgxMTExNTYxMDFjNzU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwMWQ4ODI2MTAxODQ1NjViNjEwMWUyODE4NTYxMDE4ZjU2NWI5MzUwNjEwMWYyODE4NTYwMjA4NjAxNjEwMTlhNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAyMGE4Mjg0NjEwMWNkNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwMjIzODE2MTAxNTA1NjViODExNDYxMDIyZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDI0MDgxNjEwMjFhNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI1YzU3NjEwMjViNjEwMjE1NTY1YjViNjAwMDYxMDI2YTg0ODI4NTAxNjEwMjMxNTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYmM4ZjVhZmZmYjNkMzkzNzFkNTAyYzhhZDJiYTJlMGExMmZjNTgzYzdhNjE4NTQzMzI3ODg5M2JjY2JiNDkyZDY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=", + "b64Record": "CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwGebJs7NvjVM+FaGd0RfQXGqJ30+63FJCLq7Z0g5JH2E3Vxnv9qR1J1DN+BeF9tZaGgwInN7wqwYQs8P5/QIiDwoJCODd8KsGEIIDEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA==" + }, + { + "b64Body": "Cg8KCQjh3fCrBhCEAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOsHGiISILfoLmUfjJE3E0osOuRjKpdYB9FCVDN4bPRf9iLVauM9IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==", + "b64Record": "CiUIFiIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA1lelroju58lL6iyQzptunw0F/XdZ5FFtndRo+0iILjhQc1Mq4eg9CvViwryY8cYAaDAid3vCrBhC77MWiASIPCgkI4d3wqwYQhAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt4HCgMY7AcSqQVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBj2Dv5oRRhADBXW2AAgP1bYQA4YQBOVltgQFFhAEWRkGEBaVZbYEBRgJEDkPNbYACAYABhAWlz//////////////////////////8WY9g7+aFg4BtgQFFgJAFgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAOSRkGEB/lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhASFXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hASZWW2BgkVBbUJFQkVCBYQE1V2AAgP1bgIBgIAGQUYEBkGEBSZGQYQJGVluSUFBQkFZbYACBkFCRkFBWW2EBY4FhAVBWW4JSUFBWW2AAYCCCAZBQYQF+YACDAYRhAVpWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQG4V4CCAVGBhAFSYCCBAZBQYQGdVluDgREVYQHHV2AAhIQBUltQUFBQVltgAGEB2IJhAYRWW2EB4oGFYQGPVluTUGEB8oGFYCCGAWEBmlZbgIQBkVBQkpFQUFZbYABhAgqChGEBzVZbkVCBkFCSkVBQVltgAID9W2ECI4FhAVBWW4EUYQIuV2AAgP1bUFZbYACBUZBQYQJAgWECGlZbkpFQUFZbYABgIIKEAxIVYQJcV2ECW2ECFVZbW2AAYQJqhIKFAWECMVZbkVBQkpFQUFb+omRpcGZzWCISILyPWv/7PTk3HVAsitK6LgoS/Fg8emGFQzJ4iTvMu0ktZHNvbGNDAAgJADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOwHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAPscgcKAxjsBxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN" + }, + { + "b64Body": "ChAKCQjh3fCrBhCGAxIDGOoHEgIYAxiAs8UNIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46CQoDGOwHEIC1GA==", + "b64Record": "CiUIISIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBebUIZ7F1FKN3wNwdgQ9xVQpEKM9nGHd8DaTYAcu6utnKtmBIFDf+L+oTLTzOtOUUaDAid3vCrBhDrxuGjAyIQCgkI4d3wqwYQhgMSAxjqByogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNzqCjoIGgIweCiAxBNSFwoJCgIYYhCAuNUVCgoKAxjqBxD/t9UV" + } + ] + }, + "functionCallWithLessThanFourBytesFailsGracefully": { + "placeholderNum": 1001, + "encodedItems": [ + { + "b64Body": "Cg8KCQjs5PCrBhDsAxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIHGTq3QdPxDikkYGvpAHO0z92/IxsX12lemeQIs/5f8YEICU69wDSgUIgM7aAw==", + "b64Record": "CiUIFhIDGOoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAENHLWOzK30fvz8PSk3J39wpESopwWbNaxgkEQuY0/fVtcs3rJ0jgBYlIBS1X1D28aDAio5fCrBhCL1+H7AiIPCgkI7OTwqwYQ7AMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjqBxCAqNa5Bw==" + }, + { + "b64Body": "Cg8KCQjt5PCrBhDuAxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAips8uvBhDQldGLARptCiISIL//7uFJ7qCbAFqWy/uyY9rWAFVsiATiX/7OLPcftQiFCiM6IQIST2OGk7P24Bt3WAEYUHbnZQvCuQREdCDH1lkKZvkKbQoiEiDdDlrVvVrwvBHRbyJZu3BLyp8oFrPB36YMc2a3WNonACIMSGVsbG8gV29ybGQhKgAyAA==", + "b64Record": "CiUIFhoDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAxauR5gU2+IYSFEz/Vcmd7Rwvp+Kd/ew1/s6Fn5VKIHRuwwWy/Xp0gzf9J5Vfdo/YaDAip5fCrBhCbqdegASIPCgkI7eTwqwYQ7gMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQjt5PCrBhDyAxICGAISAhgDGIflvDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmgsKAxjrByKSCzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDJhOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2Q4M2JmOWExMTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDAzODYxMDA0ZTU2NWI2MDQwNTE2MTAwNDU5MTkwNjEwMTY5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDYwMDA2MTAxNjk3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDgzYmY5YTE2MGUwMWI2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwZTQ5MTkwNjEwMWZlNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxMjE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxMjY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDEzNTU3NjAwMDgwZmQ1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwMTQ5OTE5MDYxMDI0NjU2NWI5MjUwNTA1MDkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMTYzODE2MTAxNTA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDE3ZTYwMDA4MzAxODQ2MTAxNWE1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDFiODU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDE5ZDU2NWI4MzgxMTExNTYxMDFjNzU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwMWQ4ODI2MTAxODQ1NjViNjEwMWUyODE4NTYxMDE4ZjU2NWI5MzUwNjEwMWYyODE4NTYwMjA4NjAxNjEwMTlhNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAyMGE4Mjg0NjEwMWNkNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwMjIzODE2MTAxNTA1NjViODExNDYxMDIyZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDI0MDgxNjEwMjFhNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI1YzU3NjEwMjViNjEwMjE1NTY1YjViNjAwMDYxMDI2YTg0ODI4NTAxNjEwMjMxNTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYmM4ZjVhZmZmYjNkMzkzNzFkNTAyYzhhZDJiYTJlMGExMmZjNTgzYzdhNjE4NTQzMzI3ODg5M2JjY2JiNDkyZDY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=", + "b64Record": "CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwTLIkeAT/fFt4CDNT9VKhlLzAXDKKo1nZk6rNX85Y76BMMG6KQf6QoFSpooNhSd9lGgwIqeXwqwYQw775ogMiDwoJCO3k8KsGEPIDEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA==" + }, + { + "b64Body": "Cg8KCQju5PCrBhD0AxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOsHGiISIChwcmkyx3XJLTNAq2sIPy3e1DZ66Cg+JpJi/xc1usyzIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==", + "b64Record": "CiUIFiIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBDUvQ/XLTUsGKckJtU/SSTWyyJFegkRBaVFp+9JM3dZBHLzVVJIeyjlaO0ZI9qB74aDAiq5fCrBhD73PDGASIPCgkI7uTwqwYQ9AMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt4HCgMY7AcSqQVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBj2Dv5oRRhADBXW2AAgP1bYQA4YQBOVltgQFFhAEWRkGEBaVZbYEBRgJEDkPNbYACAYABhAWlz//////////////////////////8WY9g7+aFg4BtgQFFgJAFgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAOSRkGEB/lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhASFXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hASZWW2BgkVBbUJFQkVCBYQE1V2AAgP1bgIBgIAGQUYEBkGEBSZGQYQJGVluSUFBQkFZbYACBkFCRkFBWW2EBY4FhAVBWW4JSUFBWW2AAYCCCAZBQYQF+YACDAYRhAVpWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQG4V4CCAVGBhAFSYCCBAZBQYQGdVluDgREVYQHHV2AAhIQBUltQUFBQVltgAGEB2IJhAYRWW2EB4oGFYQGPVluTUGEB8oGFYCCGAWEBmlZbgIQBkVBQkpFQUFZbYABhAgqChGEBzVZbkVCBkFCSkVBQVltgAID9W2ECI4FhAVBWW4EUYQIuV2AAgP1bUFZbYACBUZBQYQJAgWECGlZbkpFQUFZbYABgIIKEAxIVYQJcV2ECW2ECFVZbW2AAYQJqhIKFAWECMVZbkVBQkpFQUFb+omRpcGZzWCISILyPWv/7PTk3HVAsitK6LgoS/Fg8emGFQzJ4iTvMu0ktZHNvbGNDAAgJADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOwHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAPscgcKAxjsBxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN" + }, + { + "b64Body": "ChAKCQju5PCrBhD2AxIDGOoHEgIYAxiAs8UNIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DgoDGOwHEIC1GCIDq6ur", + "b64Record": "CiUIISIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDq2LUa83YvNoTvWLy3Sy5dv+gWkNXvyZSnruQZO+SAMOIKMjF36E+WllB0loaDZhcaDAiq5fCrBhCzgtirAyIQCgkI7uTwqwYQ9gMSAxjqByogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNzqCjoIGgIweCiAxBNSFwoJCgIYYhCAuNUVCgoKAxjqBxD/t9UV" + } + ] + }, + "invalidLargeInputFails": { + "placeholderNum": 1001, + "encodedItems": [ + { + "b64Body": "Cg4KCAjY4PCrBhB/EgIYAhICGAMY+5X2FCICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOWjEKIhIgZTPoMQ2BtnQ8vi4g/ArLHCLGDL+EcgDlo6DW2bc0ySUQgJTr3ANKBQiAztoD", + "b64Record": "CiUIFhIDGOoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAdVffyS5tW8QhzgezmeGhIJr/lYNVY5/tNhIowkB8BS6GAxH9FJmbn4sXhijuovxgaDAiU4fCrBhCDwYbVASIOCggI2ODwqwYQfxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/6fWuQcKCwoDGOoHEICo1rkH" + }, + { + "b64Body": "Cg8KCQjY4PCrBhCBARICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiUr8uvBhC42fTGAxptCiISIFRnSDIyGQ7D4DX/X3toj+SY/eDKm7qTMEDz/GSh84b3CiM6IQJ+b8wu2BWRPBRcwFBAw/pHDBuraQfGCguB6QkhtVjuHwoiEiCxFo7wnYMpnl7rUSuIgBHrE/dHDUwprDIftEpftcoofiIMSGVsbG8gV29ybGQhKgAyAA==", + "b64Record": "CiUIFhoDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBHNo3+Ss4HdbCn8xtzFI4r20Cc5KSffhmFni7fuRcPbqeED/x0yFnfrcHyjHsKhfIaDAiU4fCrBhCL6cTWAyIPCgkI2ODwqwYQgQESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQjZ4PCrBhCFARICGAISAhgDGIflvDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmgsKAxjrByKSCzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDJhOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2Q4M2JmOWExMTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDAzODYxMDA0ZTU2NWI2MDQwNTE2MTAwNDU5MTkwNjEwMTY5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDYwMDA2MTAxNjk3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDgzYmY5YTE2MGUwMWI2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwZTQ5MTkwNjEwMWZlNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxMjE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxMjY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDEzNTU3NjAwMDgwZmQ1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwMTQ5OTE5MDYxMDI0NjU2NWI5MjUwNTA1MDkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMTYzODE2MTAxNTA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDE3ZTYwMDA4MzAxODQ2MTAxNWE1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDFiODU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDE5ZDU2NWI4MzgxMTExNTYxMDFjNzU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwMWQ4ODI2MTAxODQ1NjViNjEwMWUyODE4NTYxMDE4ZjU2NWI5MzUwNjEwMWYyODE4NTYwMjA4NjAxNjEwMTlhNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAyMGE4Mjg0NjEwMWNkNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwMjIzODE2MTAxNTA1NjViODExNDYxMDIyZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDI0MDgxNjEwMjFhNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI1YzU3NjEwMjViNjEwMjE1NTY1YjViNjAwMDYxMDI2YTg0ODI4NTAxNjEwMjMxNTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYmM4ZjVhZmZmYjNkMzkzNzFkNTAyYzhhZDJiYTJlMGExMmZjNTgzYzdhNjE4NTQzMzI3ODg5M2JjY2JiNDkyZDY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=", + "b64Record": "CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwUKfx2gmU4ly4GYu9HHS+DslisdAwaqtGTa3Mh/HtnwAYK4tCcoIMKfNI7lqQi1CHGgwIleHwqwYQu66u9gEiDwoJCNng8KsGEIUBEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA==" + }, + { + "b64Body": "Cg8KCQja4PCrBhCHARICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOsHGiISIKYh8acndXqqjk9lkLmJ9sjgPCqEf1cJDhaY00oo90yBIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==", + "b64Record": "CiUIFiIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB83Znwn8YItt+Acu9VHJA29uDd58fj1z6cpHIdajXbfHwu888XawDlTz2ym8hiGG8aCwiW4fCrBhCL3ZobIg8KCQja4PCrBhCHARICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC3gcKAxjsBxKpBWCAYEBSNIAVYQAQV2AAgP1bUGAENhBhACtXYAA1YOAcgGPYO/mhFGEAMFdbYACA/VthADhhAE5WW2BAUWEARZGQYQFpVltgQFGAkQOQ81tgAIBgAGEBaXP//////////////////////////xZj2Dv5oWDgG2BAUWAkAWBAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEA5JGQYQH+VltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEBIVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEBJlZbYGCRUFtQkVCRUIFhATVXYACA/VuAgGAgAZBRgQGQYQFJkZBhAkZWW5JQUFCQVltgAIGQUJGQUFZbYQFjgWEBUFZbglJQUFZbYABgIIIBkFBhAX5gAIMBhGEBWlZbkpFQUFZbYACBUZBQkZBQVltgAIGQUJKRUFBWW2AAW4OBEBVhAbhXgIIBUYGEAVJgIIEBkFBhAZ1WW4OBERVhAcdXYACEhAFSW1BQUFBWW2AAYQHYgmEBhFZbYQHigYVhAY9WW5NQYQHygYVgIIYBYQGaVluAhAGRUFCSkVBQVltgAGECCoKEYQHNVluRUIGQUJKRUFBWW2AAgP1bYQIjgWEBUFZbgRRhAi5XYACA/VtQVltgAIFRkFBhAkCBYQIaVluSkVBQVltgAGAggoQDEhVhAlxXYQJbYQIVVltbYABhAmqEgoUBYQIxVluRUFCSkVBQVv6iZGlwZnNYIhIgvI9a//s9OTcdUCyK0rouChL8WDx6YYVDMniJO8y7SS1kc29sY0MACAkAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY7AdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAA+xyBwoDGOwHEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0=" + }, + { + "b64Body": "ChAKCQja4PCrBhCJARIDGOoHEgIYAxiAs8UNIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46xQIKAxjsBxCAtRgiuQJ3zdt/1rXTTTTTTTTTTTTTTTTTTTTTR3zdt/1rXTTTR3zdt/1rXTTR3zdt/1rXTTTTTTR3zdt/1rXTzTTR3zdt/1rXTTTTR3zdt/1rXTTTTTTTTTR3zdt/1rXTTTTR3zdt/1rXTTTTTR3zdt/1rXTTff3TTTTTR3zdt/1rXTTTTTTTTTXbfjnrvz3TXbfjnrvz3TXbfjnrvz3TXbfjnrvz3TTTTTTTTTTTTTTTTTTTTTTTTTXbfjnrvz3TXbfjnrvz3TXbfjnrvz3TXbfjnrvz3TTTTTTTTTTTR3zdt/1rXTzdt/1rXTTR3zdt/1rXTTTTTTR3zdt/1rXTTTTTTTTTTTTTTTTTbp7v3TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTR3zdt/1rXTTTTTTTTR3zdt/1rXTT", + "b64Record": "CiUIISIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBQ//SKIT5EaasciLyKJauxQRGzOiuSgR8PT9RuCIWVUYCI14ACwHLZD452rgdvRFQaDAiW4fCrBhDzqJadAiIQCgkI2uDwqwYQiQESAxjqByogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNzqCjoIGgIweCiAxBNSFwoJCgIYYhCAuNUVCgoKAxjqBxD/t9UV" + } + ] + }, + "MultipleCallsHaveIndependentResults": { + "placeholderNum": 1001, + "encodedItems": [ + { + "b64Body": "Cg8KCQjv2vCrBhCUAxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAirqcuvBhDY2NOgAhptCiISIAWLoztHDC4ufjNPJY6zsQRSe2EY/v/+Q8YA9h5pOmjPCiM6IQOJdR6IS6OdNr+7KX3fpTYcLZmar3MaCH0a/51WjdVeBQoiEiD0A9gFGowgE+K8MX8tdzN4ojeRrGigWdCeFyHYQeZbViIMSGVsbG8gV29ybGQhKgAyAA==", + "b64Record": "CiUIFhoDGOoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBa1G83tOhynaBbwJXjOhSp/4hkx6ZjQkzhhuEK0Nc1hG7fuNM7eezdbukoRARcscQaDAir2/CrBhCjk6DEAiIPCgkI79rwqwYQlAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQjw2vCrBhCYAxICGAISAhgDGIflvDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmgsKAxjqByKSCzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDJhOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2Q4M2JmOWExMTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDAzODYxMDA0ZTU2NWI2MDQwNTE2MTAwNDU5MTkwNjEwMTY5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDYwMDA2MTAxNjk3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDgzYmY5YTE2MGUwMWI2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwZTQ5MTkwNjEwMWZlNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxMjE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxMjY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDEzNTU3NjAwMDgwZmQ1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwMTQ5OTE5MDYxMDI0NjU2NWI5MjUwNTA1MDkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMTYzODE2MTAxNTA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDE3ZTYwMDA4MzAxODQ2MTAxNWE1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDFiODU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDE5ZDU2NWI4MzgxMTExNTYxMDFjNzU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwMWQ4ODI2MTAxODQ1NjViNjEwMWUyODE4NTYxMDE4ZjU2NWI5MzUwNjEwMWYyODE4NTYwMjA4NjAxNjEwMTlhNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAyMGE4Mjg0NjEwMWNkNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwMjIzODE2MTAxNTA1NjViODExNDYxMDIyZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDI0MDgxNjEwMjFhNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI1YzU3NjEwMjViNjEwMjE1NTY1YjViNjAwMDYxMDI2YTg0ODI4NTAxNjEwMjMxNTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYmM4ZjVhZmZmYjNkMzkzNzFkNTAyYzhhZDJiYTJlMGExMmZjNTgzYzdhNjE4NTQzMzI3ODg5M2JjY2JiNDkyZDY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=", + "b64Record": "CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwk9wJtQGi6Jt9SoV/VFXcMNl7PUNyvf9LYjVu/cMkcJsElbnZ/bh6UxmqUIo2dhyRGgsIrNvwqwYQ44uEaSIPCgkI8NrwqwYQmAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQjw2vCrBhCaAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOoHGiISICTthv2N9pXrl0vP/mhKMIWnFx1dMaHmykMWqLEF2ohdIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==", + "b64Record": "CiUIFiIDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCn4Jg+PkIf5IRn/mlB/7nZjnHQcioaXhUcaVXhQzerCRSEKAY9HzX1UAApSx6KmuMaDAis2/CrBhCrs7HNAiIPCgkI8NrwqwYQmgMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt4HCgMY6wcSqQVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBj2Dv5oRRhADBXW2AAgP1bYQA4YQBOVltgQFFhAEWRkGEBaVZbYEBRgJEDkPNbYACAYABhAWlz//////////////////////////8WY9g7+aFg4BtgQFFgJAFgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAOSRkGEB/lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhASFXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hASZWW2BgkVBbUJFQkVCBYQE1V2AAgP1bgIBgIAGQUYEBkGEBSZGQYQJGVluSUFBQkFZbYACBkFCRkFBWW2EBY4FhAVBWW4JSUFBWW2AAYCCCAZBQYQF+YACDAYRhAVpWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQG4V4CCAVGBhAFSYCCBAZBQYQGdVluDgREVYQHHV2AAhIQBUltQUFBQVltgAGEB2IJhAYRWW2EB4oGFYQGPVluTUGEB8oGFYCCGAWEBmlZbgIQBkVBQkpFQUFZbYABhAgqChGEBzVZbkVCBkFCSkVBQVltgAID9W2ECI4FhAVBWW4EUYQIuV2AAgP1bUFZbYACBUZBQYQJAgWECGlZbkpFQUFZbYABgIIKEAxIVYQJcV2ECW2ECFVZbW2AAYQJqhIKFAWECMVZbkVBQkpFQUFb+omRpcGZzWCISILyPWv/7PTk3HVAsitK6LgoS/Fg8emGFQzJ4iTvMu0ktZHNvbGNDAAgJADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOsHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAPrcgcKAxjrBxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN" + }, + { + "b64Body": "Cg8KCQjx2vCrBhCcAxICGAISAhgDGICzxQ0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMY6wcQgLUYIgTYO/mh", + "b64Record": "CiUIFiIDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDcAwfe9IQ51MryfgI0dsWtluLOS+T0L2yC+CVNZY7JoWhz2LXuVhKtG618Qonc0fYaCwit2/CrBhDb/oZzIg8KCQjx2vCrBhCcAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDc6go6rgIKAxjrBxIgxxWvhYgzRf5SXF187t0E13T0WB5l3MfjFEslEsB1VogigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDEE1IWCgkKAhgCEP+31RUKCQoCGGIQgLjVFQ==" + }, + { + "b64Body": "ChEKCQjx2vCrBhCcAxICGAIgAaIDAA==", + "b64Record": "CgIIFhIwje+0GOJ7a1cnl3r0hwavceFbDfLPxthjUq+eTgC2YttG2Arg1cQ9N9MEK+QKmXHAGgsIrdvwqwYQ3P6GcyIRCgkI8drwqwYQnAMSAhgCIAE6NgoDGOcCEiDHFa+FiDNF/lJcXXzu3QTXdPRYHmXcx+MUSyUSwHVWiFDgyBZiBNg7+aFqAxjrB1IAegsIrdvwqwYQ2/6Gc5oBIMcVr4WIM0X+UlxdfO7dBNd09FgeZdzH4xRLJRLAdVaI" + }, + { + "b64Body": "Cg8KCQjx2vCrBhCiAxICGAISAhgDGICzxQ0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMY6wcQgLUYIgTYO/mh", + "b64Record": "CiUIFiIDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB+jo+rPPHs9ZQLSMMXnLUQFqI40iKn+/g89IvtvH+8I3RnExExfMrRQVtf3xP5bZAaDAit2/CrBhD7iPnzAiIPCgkI8drwqwYQogMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA3OoKOq4CCgMY6wcSIKZUl18OibuRjWML04OjtBuPfTzW//Ho/nAVG2ZLXxq8IoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAxBNSFgoJCgIYAhD/t9UVCgkKAhhiEIC41RU=" + }, + { + "b64Body": "ChEKCQjx2vCrBhCiAxICGAIgAaIDAA==", + "b64Record": "CgIIFhIwlFGsOHBa1UsEBEOjUKBZQyc4gy3zWNLShCT8dDcYyPIE2FT/BnZfy1Rn8uctJyQzGgwIrdvwqwYQ/Ij58wIiEQoJCPHa8KsGEKIDEgIYAiABOjYKAxjnAhIgplSXXw6Ju5GNYwvTg6O0G499PNb/8ej+cBUbZktfGrxQ4MgWYgTYO/mhagMY6wdSAHoMCK3b8KsGEPuI+fMCmgEgplSXXw6Ju5GNYwvTg6O0G499PNb/8ej+cBUbZktfGrw=" + }, + { + "b64Body": "Cg8KCQjy2vCrBhCoAxICGAISAhgDGICzxQ0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMY6wcQgLUYIgTYO/mh", + "b64Record": "CiUIFiIDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZG6Yh36Zhy+Y6mC2wNtdsngAQYkHfGgE97H0xfnoarhH84/pb4iUyN5ssQXP3bhYaDAiu2/CrBhDj9aSYASIPCgkI8trwqwYQqAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA3OoKOq4CCgMY6wcSIAEooi7xZWM66QtRnPw8qpK/AeftxyrRoxLSbznKBrGqIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAxBNSFgoJCgIYAhD/t9UVCgkKAhhiEIC41RU=" + }, + { + "b64Body": "ChEKCQjy2vCrBhCoAxICGAIgAaIDAA==", + "b64Record": "CgIIFhIwMkB5mPE1Z2rYUrzLwsBWROlkyJC3S1G1WlSLalhAapA9QnCromub8Z7WV4siUlo4GgwIrtvwqwYQ5PWkmAEiEQoJCPLa8KsGEKgDEgIYAiABOjYKAxjnAhIgASiiLvFlYzrpC1Gc/Dyqkr8B5+3HKtGjEtJvOcoGsapQ4MgWYgTYO/mhagMY6wdSAHoMCK7b8KsGEOP1pJgBmgEgASiiLvFlYzrpC1Gc/Dyqkr8B5+3HKtGjEtJvOcoGsao=" + }, + { + "b64Body": "Cg8KCQjy2vCrBhCuAxICGAISAhgDGICzxQ0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMY6wcQgLUYIgTYO/mh", + "b64Record": "CiUIFiIDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBWJdd20tAISdGog6SGSFJCrd+aZ560bJsmm+LjkcrEmwYvKJbeThOAwYtHdv01av8aDAiu2/CrBhDLn6/9AiIPCgkI8trwqwYQrgMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA3OoKOq4CCgMY6wcSIFxoklUoNMHTZtpJyK5qUdONLDIMa9T6hKz9Ik7Zz9/0IoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAxBNSFgoJCgIYAhD/t9UVCgkKAhhiEIC41RU=" + }, + { + "b64Body": "ChEKCQjy2vCrBhCuAxICGAIgAaIDAA==", + "b64Record": "CgIIFhIwWd0XwxkXns3+pEHXJbgE21Licr6Gl4SAOrnbDDunRSzqOuyPCQ6cfpTwBssmXPk4GgwIrtvwqwYQzJ+v/QIiEQoJCPLa8KsGEK4DEgIYAiABOjYKAxjnAhIgXGiSVSg0wdNm2knIrmpR040sMgxr1PqErP0iTtnP3/RQ4MgWYgTYO/mhagMY6wdSAHoMCK7b8KsGEMufr/0CmgEgXGiSVSg0wdNm2knIrmpR040sMgxr1PqErP0iTtnP3/Q=" + }, + { + "b64Body": "Cg8KCQjz2vCrBhC0AxICGAISAhgDGICzxQ0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMY6wcQgLUYIgTYO/mh", + "b64Record": "CiUIFiIDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDcbSFd9rRQALaRARaT1XRizxicsIowwmAtdDUQHUvvv820lb0KjBKGTJ3n6bM2KcIaDAiv2/CrBhDT5ZuiASIPCgkI89rwqwYQtAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA3OoKOq4CCgMY6wcSILlydjgcb+YjyIm5DjtE64ohowE4mzamPF+meVUiqX/fIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAxBNSFgoJCgIYAhD/t9UVCgkKAhhiEIC41RU=" + }, + { + "b64Body": "ChEKCQjz2vCrBhC0AxICGAIgAaIDAA==", + "b64Record": "CgIIFhIwMRnH/d4lfWhOmw/pTiIhkZsyYal4o2cLG80+U9Q58RetUygfbmU3ZwN9Qv0TLtkWGgwIr9vwqwYQ1OWbogEiEQoJCPPa8KsGELQDEgIYAiABOjYKAxjnAhIguXJ2OBxv5iPIibkOO0TriiGjATibNqY8X6Z5VSKpf99Q4MgWYgTYO/mhagMY6wdSAHoMCK/b8KsGENPlm6IBmgEguXJ2OBxv5iPIibkOO0TriiGjATibNqY8X6Z5VSKpf98=" + } + ] + }, + "nonSupportedAbiCallGracefullyFails": { + "placeholderNum": 1001, + "encodedItems": [ + { + "b64Body": "Cg8KCQiG4/CrBhDOBBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIC6Y3rvC3ymzbxICHU5p/pBk4n8/+DP8CUwvOU+BJWLcEICU69wDSgUIgM7aAw==", + "b64Record": "CiUIFhIDGOoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCXBuBJFS07iPvWHV9qOMC+mkJjfdv8ATQumrhkTHbwRbGShKCydOwjvuxWtvgehIEaDAjC4/CrBhCTnq/AAyIPCgkIhuPwqwYQzgQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjqBxCAqNa5Bw==" + }, + { + "b64Body": "Cg8KCQiH4/CrBhDQBBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjDscuvBhDwrcnGARptCiISIO1ksLjqbYZ7EoggfLWnrl5+DT1BJ7Pg7QSWT2W3FbZXCiM6IQOGGLuG0npd04aTQuG0F7vYyJIP41hlN6xRYRpo+v9+UQoiEiCAKsbZEnrcRq/UVgs95skhY58LHh2uPxg+gJAO6FO04CIMSGVsbG8gV29ybGQhKgAyAA==", + "b64Record": "CiUIFhoDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAKAn9wroOtpfWstSE4Q1g9dAdm07xxgTprZoKRq8qL3SGRhVqJqezpgpgQrRkDwXIaDAjD4/CrBhDL5pjkASIPCgkIh+PwqwYQ0AQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQiH4/CrBhDUBBICGAISAhgDGObSpDciAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB6hMKAxjrByLiEzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDRkMTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0MTU3NjAwMDM1NjBlMDFjODA2MzM1ZjNiZmE5MTQ2MTAwNDY1NzgwNjM2NTM4YWUzMTE0NjEwMDUwNTc4MDYzZDgzYmY5YTExNDYxMDA1YTU3NWI2MDAwODBmZDViNjEwMDRlNjEwMDc4NTY1YjAwNWI2MTAwNTg2MTAxMTI1NjViMDA1YjYxMDA2MjYxMDFmOTU2NWI2MDQwNTE2MTAwNmY5MTkwNjEwMzE0NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDYxMDE2OTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjA0MDUxNjAyMDAxNjEwMGEzOTA2MTAzOGM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyNjA0MDUxNjEwMGJmOTE5MDYxMDQyNjU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTVhZjQ5MTUwNTAzZDgwNjAwMDgxMTQ2MTAwZmE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAwZmY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDEwZTU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDgwNjEwMTY5NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2Mzc2MmVjY2Y3NjBlMDFiNjA0MDUxNjAyNDAxNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMWE2OTE5MDYxMDQyNjU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTVhZjQ5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxZTE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxZTY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDFmNTU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDgwNjAwMDYxMDE2OTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNkODNiZjlhMTYwZTAxYjYwNDA1MTYwMjQwMTYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE5MTY2MDIwODIwMTgwNTE3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODM4MTgzMTYxNzgzNTI1MDUwNTA1MDYwNDA1MTYxMDI4ZjkxOTA2MTA0MjY1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg2NWFmMTkxNTA1MDNkODA2MDAwODExNDYxMDJjYzU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDJkMTU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgxNjEwMmUwNTc2MDAwODBmZDViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTAyZjQ5MTkwNjEwNDZlNTY1YjkyNTA1MDUwOTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAzMGU4MTYxMDJmYjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMzI5NjAwMDgzMDE4NDYxMDMwNTU2NWI5MjkxNTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2YzMDc4NjM2NDYzNjQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTAzNzY2MDA2ODM2MTAzMmY1NjViOTE1MDYxMDM4MTgyNjEwMzQwNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDNhNTgxNjEwMzY5NTY1YjkwNTA5MTkwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDNlMDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDNjNTU2NWI4MzgxMTExNTYxMDNlZjU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwNDAwODI2MTAzYWM1NjViNjEwNDBhODE4NTYxMDNiNzU2NWI5MzUwNjEwNDFhODE4NTYwMjA4NjAxNjEwM2MyNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA0MzI4Mjg0NjEwM2Y1NTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwNDRiODE2MTAyZmI1NjViODExNDYxMDQ1NjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDQ2ODgxNjEwNDQyNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDQ4NDU3NjEwNDgzNjEwNDNkNTY1YjViNjAwMDYxMDQ5Mjg0ODI4NTAxNjEwNDU5NTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwZWRmYTYzYTI5ZDk1OGQ1YzE5Mjc2Y2VhOGFhMWVjOTQ4NDdkOTQxMzVlNmYyMGJiYzcwNWYzMTI0MmU1NDI2YTY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=", + "b64Record": "CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwr58+x5PivRHNVH0fzEt1/ow5HYRy79FIkSmIk7JlYwkyxS4Bh9q9YT3V6w1wrbXLGgsIxOPwqwYQu+3yCCIPCgkIh+PwqwYQ1AQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQiI4/CrBhDWBBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOsHGiISIJqH8hoodsBLXUNHYu/Y+rtsR2EmMRajAqFz8BmOpQlkIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==", + "b64Record": "CiUIFiIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD3Iqnt+LqUnuzC7mocLuNVIczqlh9hl4hwrAJk6i+jSvScpJ29LMhOocNclxh99t4aDAjE4/CrBhCrjorvASIPCgkIiOPwqwYQ1gQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQoYMCgMY7AcS0QlggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBBV2AANWDgHIBjNfO/qRRhAEZXgGNlOK4xFGEAUFeAY9g7+aEUYQBaV1tgAID9W2EATmEAeFZbAFthAFhhARJWWwBbYQBiYQH5VltgQFFhAG+RkGEDFFZbYEBRgJEDkPNbYACAYQFpc///////////////////////////FmBAUWAgAWEAo5BhA4xWW2BAUWAggYMDA4FSkGBAUmBAUWEAv5GQYQQmVltgAGBAUYCDA4GFWvSRUFA9gGAAgRRhAPpXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hAP9WW2BgkVBbUJFQkVCBYQEOV2AAgP1bUFBWW2AAgGEBaXP//////////////////////////xZjdi7M92DgG2BAUWAkAWBAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEBppGQYQQmVltgAGBAUYCDA4GFWvSRUFA9gGAAgRRhAeFXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hAeZWW2BgkVBbUJFQkVCBYQH1V2AAgP1bUFBWW2AAgGAAYQFpc///////////////////////////FmPYO/mhYOAbYEBRYCQBYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQKPkZBhBCZWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQLMV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQLRVltgYJFQW1CRUJFQgWEC4FdgAID9W4CAYCABkFGBAZBhAvSRkGEEblZbklBQUJBWW2AAgZBQkZBQVlthAw6BYQL7VluCUlBQVltgAGAgggGQUGEDKWAAgwGEYQMFVluSkVBQVltgAIKCUmAgggGQUJKRUFBWW38weGNkY2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAggFSUFZbYABhA3ZgBoNhAy9WW5FQYQOBgmEDQFZbYCCCAZBQkZBQVltgAGAgggGQUIGBA2AAgwFSYQOlgWEDaVZbkFCRkFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQPgV4CCAVGBhAFSYCCBAZBQYQPFVluDgREVYQPvV2AAhIQBUltQUFBQVltgAGEEAIJhA6xWW2EECoGFYQO3VluTUGEEGoGFYCCGAWEDwlZbgIQBkVBQkpFQUFZbYABhBDKChGED9VZbkVCBkFCSkVBQVltgAID9W2EES4FhAvtWW4EUYQRWV2AAgP1bUFZbYACBUZBQYQRogWEEQlZbkpFQUFZbYABgIIKEAxIVYQSEV2EEg2EEPVZbW2AAYQSShIKFAWEEWVZbkVBQkpFQUFb+omRpcGZzWCISIO36Y6KdlY1cGSds6oqh7JSEfZQTXm8gu8cF8xJC5UJqZHNvbGNDAAgJADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOwHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAPscgcKAxjsBxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN" + }, + { + "b64Body": "ChAKCQiJ4/CrBhDYBBIDGOoHEgIYAxiAs8UNIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGOwHEIC1GCIEZTiuMQ==", + "b64Record": "CiUIISIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBwb/nlHmPpSiFPjmYAuDbnG+k2z4bwiyDm9stI6iT6rLkdeDLJRQgk/USi+HYOfTkaCwjF4/CrBhCbmbwSIhAKCQiJ4/CrBhDYBBIDGOoHKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCup6wNOggaAjB4KOKHGFIXCgkKAhhiENzO2BoKCgoDGOoHENvO2Bo=" + }, + { + "b64Body": "ChIKCQiJ4/CrBhDYBBIDGOoHIAGiAwA=", + "b64Record": "CgMImAISMPLY+OQhg30rA6skQkHDlbyR0XcqD8pCTaXMkoVGVoT2Vj+LpbJX3Lho52EV1MRQchoLCMXj8KsGEJyZvBIiEgoJCInj8KsGENgEEgMY6gcgAToUCgMY5wJQ0cgWYgR2Lsz3agMY6gdSAHoLCMXj8KsGEJuZvBI=" + } + ] + }, + "prngPrecompileHappyPathWorks": { + "placeholderNum": 1001, + "encodedItems": [ + { + "b64Body": "Cg8KCQi25vCrBhCdBBICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIGmBXWUcG/ORUg5tjEOu6eLYoR7xD+rIre4H/2x/WXh1EICU69wDSgUIgM7aAw==", + "b64Record": "CiUIFhIDGOoHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB3M1hTb+4JoqdAGznk1f59jok4cU0o2OTDmVKt7xPPQT2RGKjpq/iUhfOOyRQnxGoaDAjy5vCrBhCLxsfOAiIPCgkItubwqwYQnQQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjqBxCAqNa5Bw==" + }, + { + "b64Body": "Cg8KCQi35vCrBhCfBBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjztMuvBhD4059lGm0KIhIgghhAFBVmtqTomXWAAyQ3cPCax+/SongDrnj4U9RkPZ4KIzohAob+9uemSveCo5ud6ffnnruXp/s7haHnuDJwY+TTxZHSCiISIH880ehQp5zedCC9rCE/rK9/Ua6g2EUqEwtq/nCMF/leIgxIZWxsbyBXb3JsZCEqADIA", + "b64Record": "CiUIFhoDGOsHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDKoE3CbOUE320uVZlptH6aBgYRclPwmwGifvYOXIUOh65bKg1HP4gcNkhidFxQwt4aDAjz5vCrBhCrr6WPASIPCgkIt+bwqwYQnwQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA" + }, + { + "b64Body": "Cg8KCQi35vCrBhCjBBICGAISAhgDGIflvDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBmgsKAxjrByKSCzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDJhOTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2Q4M2JmOWExMTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDAzODYxMDA0ZTU2NWI2MDQwNTE2MTAwNDU5MTkwNjEwMTY5NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDYwMDA2MTAxNjk3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDgzYmY5YTE2MGUwMWI2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwZTQ5MTkwNjEwMWZlNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxMjE1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxMjY1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDEzNTU3NjAwMDgwZmQ1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwMTQ5OTE5MDYxMDI0NjU2NWI5MjUwNTA1MDkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMTYzODE2MTAxNTA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDE3ZTYwMDA4MzAxODQ2MTAxNWE1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDFiODU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDE5ZDU2NWI4MzgxMTExNTYxMDFjNzU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwMWQ4ODI2MTAxODQ1NjViNjEwMWUyODE4NTYxMDE4ZjU2NWI5MzUwNjEwMWYyODE4NTYwMjA4NjAxNjEwMTlhNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAyMGE4Mjg0NjEwMWNkNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjEwMjIzODE2MTAxNTA1NjViODExNDYxMDIyZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDI0MDgxNjEwMjFhNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI1YzU3NjEwMjViNjEwMjE1NTY1YjViNjAwMDYxMDI2YTg0ODI4NTAxNjEwMjMxNTY1YjkxNTA1MDkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYmM4ZjVhZmZmYjNkMzkzNzFkNTAyYzhhZDJiYTJlMGExMmZjNTgzYzdhNjE4NTQzMzI3ODg5M2JjY2JiNDkyZDY0NzM2ZjZjNjM0MzAwMDgwOTAwMzM=", + "b64Record": "CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwGMOW3CzX7aqdpbsvpli8h7ZbzrWT3X5gJf0U0AuEdvgeSFetyxGJIC/0ZwdUFnP8GgwI8+bwqwYQ6/2y9QIiDwoJCLfm8KsGEKMEEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA==" + }, + { + "b64Body": "Cg8KCQi45vCrBhClBBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOsHGiISINnOrioY4n/vF5ZiNGqsgbsmJcE5aE0bM5FNjVM0Uh6aIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==", + "b64Record": "CiUIFiIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAD0CvW0yrdCjAEgXQ3p1VkDYn75zAPFrPolXaeTHWSOMV5jtue/zic6OLE5dOpnjsaDAj05vCrBhDLgKGZASIPCgkIuObwqwYQpQQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt4HCgMY7AcSqQVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBj2Dv5oRRhADBXW2AAgP1bYQA4YQBOVltgQFFhAEWRkGEBaVZbYEBRgJEDkPNbYACAYABhAWlz//////////////////////////8WY9g7+aFg4BtgQFFgJAFgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAOSRkGEB/lZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhASFXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hASZWW2BgkVBbUJFQkVCBYQE1V2AAgP1bgIBgIAGQUYEBkGEBSZGQYQJGVluSUFBQkFZbYACBkFCRkFBWW2EBY4FhAVBWW4JSUFBWW2AAYCCCAZBQYQF+YACDAYRhAVpWW5KRUFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQG4V4CCAVGBhAFSYCCBAZBQYQGdVluDgREVYQHHV2AAhIQBUltQUFBQVltgAGEB2IJhAYRWW2EB4oGFYQGPVluTUGEB8oGFYCCGAWEBmlZbgIQBkVBQkpFQUFZbYABhAgqChGEBzVZbkVCBkFCSkVBQVltgAID9W2ECI4FhAVBWW4EUYQIuV2AAgP1bUFZbYACBUZBQYQJAgWECGlZbkpFQUFZbYABgIIKEAxIVYQJcV2ECW2ECFVZbW2AAYQJqhIKFAWECMVZbkVBQkpFQUFb+omRpcGZzWCISILyPWv/7PTk3HVAsitK6LgoS/Fg8emGFQzJ4iTvMu0ktZHNvbGNDAAgJADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOwHShYKFAAAAAAAAAAAAAAAAAAAAAAAAAPscgcKAxjsBxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN" + }, + { + "b64Body": "ChAKCQi45vCrBhCnBBIDGOoHEgIYAxiAs8UNIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46DwoDGOwHEIC1GCIE2Dv5oQ==", + "b64Record": "CiUIFiIDGOwHKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBM0KY4+P/MQ7qqKivVcdrwikkluiJwAlqIc1aaK/SnbuNHkfpKAN8nBAOTJrnxCBQaDAj05vCrBhDL4pKbAyIQCgkIuObwqwYQpwQSAxjqByogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgNzqCjquAgoDGOwHEiA6nzaa4D5ponLIRRm+4kR8GbaVIj0f1PUoGmxCnaiedSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogMQTUhcKCQoCGGIQgLjVFQoKCgMY6gcQ/7fVFQ==" + }, + { + "b64Body": "ChIKCQi45vCrBhCnBBIDGOoHIAGiAwA=", + "b64Record": "CgIIFhIwOiGnc8f1SI5XvI3GoQQeE7nkBdxY1FueCWsY+hS4kWVqiqHkHT5EthPOqiGgjc6/GgwI9ObwqwYQzOKSmwMiEgoJCLjm8KsGEKcEEgMY6gcgATo2CgMY5wISIDqfNprgPmmicshFGb7iRHwZtpUiPR/U9SgabEKdqJ51UODIFmIE2Dv5oWoDGOwHUgB6DAj05vCrBhDL4pKbA5oBIDqfNprgPmmicshFGb7iRHwZtpUiPR/U9SgabEKdqJ51" + } + ] + } + } +} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java index 0a41b1908482..4ac7508d66e2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java @@ -131,7 +131,8 @@ public class SnapshotModeOp extends UtilOp implements SnapshotOp { // Bloom field in ContractCall result "bloom", // runningHash in SubmitMessageHandler - "topicRunningHash"); + "topicRunningHash", + "prng_bytes"); private static final String PLACEHOLDER_MEMO = ""; private static final String MONO_STREAMS_LOC = "hedera-node/data/recordstreams/record0.0.3"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java index f2803ff04b7d..c333ac31a69f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java @@ -28,9 +28,15 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -91,13 +97,20 @@ List positiveSpecs() { return List.of(prngPrecompileHappyPathWorks(), multipleCallsHaveIndependentResults()); } + @HapiTest final HapiSpec multipleCallsHaveIndependentResults() { final var prng = THE_PRNG_CONTRACT; final var gasToOffer = 400_000; final var numCalls = 5; final List prngSeeds = new ArrayList<>(); return defaultHapiSpec("MultipleCallsHaveIndependentResults") - .given(uploadInitCode(prng), contractCreate(prng)) + .given( + snapshotMode( + FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS), + uploadInitCode(prng), + contractCreate(prng)) .when(withOpContext((spec, opLog) -> { for (int i = 0; i < numCalls; i++) { final var txn = "call" + i; @@ -140,7 +153,11 @@ final HapiSpec emptyInputCallFails() { final var prng = THE_PRNG_CONTRACT; final var emptyInputCall = "emptyInputCall"; return defaultHapiSpec("emptyInputCallFails") - .given(cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), + uploadInitCode(prng), + contractCreate(prng)) .when(sourcing(() -> contractCall(prng, GET_SEED) .withExplicitParams( () -> CommonUtils.hex(Bytes.fromBase64String("").toArray())) @@ -168,7 +185,11 @@ final HapiSpec invalidLargeInputFails() { final var prng = THE_PRNG_CONTRACT; final var largeInputCall = "largeInputCall"; return defaultHapiSpec("invalidLargeInputFails") - .given(cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), + uploadInitCode(prng), + contractCreate(prng)) .when(sourcing(() -> contractCall(prng, GET_SEED) .withExplicitParams(() -> CommonUtils.hex( Bytes.fromBase64String(EXPLICIT_LARGE_PARAMS).toArray())) @@ -191,11 +212,16 @@ final HapiSpec invalidLargeInputFails() { })); } + @HapiTest final HapiSpec nonSupportedAbiCallGracefullyFails() { final var prng = THE_GRACEFULLY_FAILING_PRNG_CONTRACT; final var failedCall = "failedCall"; return defaultHapiSpec("nonSupportedAbiCallGracefullyFails") - .given(cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), + uploadInitCode(prng), + contractCreate(prng)) .when(sourcing(() -> contractCall(prng, "performNonExistingServiceFunctionCall") .gas(GAS_TO_OFFER) .payingWith(BOB) @@ -213,10 +239,15 @@ final HapiSpec nonSupportedAbiCallGracefullyFails() { })); } + @HapiTest final HapiSpec functionCallWithLessThanFourBytesFailsGracefully() { final var lessThan4Bytes = "lessThan4Bytes"; return defaultHapiSpec("functionCallWithLessThanFourBytesFailsGracefully") - .given(cryptoCreate(BOB), uploadInitCode(THE_PRNG_CONTRACT), contractCreate(THE_PRNG_CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), + uploadInitCode(THE_PRNG_CONTRACT), + contractCreate(THE_PRNG_CONTRACT)) .when( sourcing(() -> contractCall(THE_PRNG_CONTRACT, GET_SEED) .withExplicitParams( @@ -240,11 +271,19 @@ final HapiSpec functionCallWithLessThanFourBytesFailsGracefully() { })); } + @HapiTest final HapiSpec prngPrecompileHappyPathWorks() { final var prng = THE_PRNG_CONTRACT; final var randomBits = "randomBits"; return defaultHapiSpec("prngPrecompileHappyPathWorks") - .given(cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) + .given( + snapshotMode( + FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS), + cryptoCreate(BOB), + uploadInitCode(prng), + contractCreate(prng)) .when(sourcing(() -> contractCall(prng, GET_SEED) .gas(GAS_TO_OFFER) .payingWith(BOB) @@ -261,6 +300,25 @@ GET_SEED, prng, isRandomResult(new Object[] {new byte[32]})))) .logged()); } + @HapiTest + private HapiSpec prngPrecompileInsufficientGas() { + final var prng = THE_PRNG_CONTRACT; + final var randomBits = "randomBits"; + return defaultHapiSpec("prngPrecompileInsufficientGas") + .given( + // Will be enabled in https://github.com/hashgraph/hedera-services/issues/10166 + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) + .when(sourcing(() -> contractCall(prng, GET_SEED) + .gas(1L) + .payingWith(BOB) + .via(randomBits) + .hasPrecheckFrom(OK, INSUFFICIENT_GAS) + .hasKnownStatus(INSUFFICIENT_GAS) + .logged())) + .then(); + } + @Override protected Logger getResultsLogger() { return log; From dad92774297cc9507d9ad42adab56ca23c1c125b Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:22:47 -0600 Subject: [PATCH 02/80] chore: Update cgroup and ionice limits for regression workflow (#10519) Co-authored-by: Nathan Klick --- .github/workflows/zxc-jrs-regression.yaml | 40 ++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/workflows/zxc-jrs-regression.yaml b/.github/workflows/zxc-jrs-regression.yaml index fb321eb4e930..798dd0f33215 100644 --- a/.github/workflows/zxc-jrs-regression.yaml +++ b/.github/workflows/zxc-jrs-regression.yaml @@ -160,6 +160,7 @@ env: JAVA_OPTS: -Xmx28g -XX:ActiveProcessorCount=16 GRADLE_CACHE_USERNAME: ${{ secrets.gradle-cache-username }} GRADLE_CACHE_PASSWORD: ${{ secrets.gradle-cache-password }} + CG_EXEC: cgexec -g cpu,memory:gradle-${{ github.run_id }} --sticky ionice -c 2 -n 2 nice -n 19 defaults: run: @@ -305,6 +306,43 @@ jobs: sudo apt install -y ./rclone-v1.58.1-linux-amd64.deb rm -rf rclone-v1.58.1-linux-amd64.deb + - name: Setup Control Groups + run: | + echo "::group::Get System Configuration" + USR_ID="$(id -un)" + GRP_ID="$(id -gn)" + GRADLE_MEM_LIMIT="30064771072" + AGENT_MEM_LIMIT="2147483648" + GRADLE_GROUP_NAME="gradle-${{ github.run_id }}" + AGENT_GROUP_NAME="agent-${{ github.run_id }}" + echo "::endgroup::" + + echo "::group::Install Control Group Tools" + if ! command -v cgcreate >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y cgroup-tools + fi + echo "::endgroup::" + + echo "::group::Create Control Groups" + sudo cgcreate -g cpu,memory:${GRADLE_GROUP_NAME} -a ${USR_ID}:${GRP_ID} -t ${USR_ID}:${GRP_ID} + sudo cgcreate -g cpu,memory:${AGENT_GROUP_NAME} -a ${USR_ID}:${GRP_ID} -t ${USR_ID}:${GRP_ID} + echo "::endgroup::" + + echo "::group::Set Control Group Limits" + cgset -r cpu.shares=768 ${GRADLE_GROUP_NAME} + cgset -r cpu.shares=500 ${AGENT_GROUP_NAME} + cgset -r memory.limit_in_bytes=${GRADLE_MEM_LIMIT} ${GRADLE_GROUP_NAME} + cgset -r memory.limit_in_bytes=${AGENT_MEM_LIMIT} ${AGENT_GROUP_NAME} + cgset -r memory.memsw.limit_in_bytes=${GRADLE_MEM_LIMIT} ${GRADLE_GROUP_NAME} + cgset -r memory.memsw.limit_in_bytes=${AGENT_MEM_LIMIT} ${AGENT_GROUP_NAME} + echo "::endgroup::" + + echo "::group::Move Runner Processes to Control Groups" + sudo cgclassify --sticky -g cpu,memory:${AGENT_GROUP_NAME} $(pgrep 'Runner.Listener' | tr '\n' ' ') + sudo cgclassify -g cpu,memory:${AGENT_GROUP_NAME} $(pgrep 'Runner.Worker' | tr '\n' ' ') + echo "::endgroup::" + - name: Configure RClone Authentication id: rclone-auth env: @@ -441,7 +479,7 @@ jobs: JAVA_OPTS="-Xmx8g" fi - java ${JAVA_OPTS} \ + ${CG_EXEC} java ${JAVA_OPTS} \ -cp "lib/*:regression.jar" \ -Dlog4j.configurationFile="log4j2-fsts-enhanced.xml" \ -Dspring.output.ansi.enabled=ALWAYS \ From 3d18649cbb29d84994daa58ac2488eb8cc8cdd71 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:34:35 -0600 Subject: [PATCH 03/80] fix: removed PTT log that was failing tests. (#10554) Signed-off-by: Cody Littley --- .../demo/merkle/map/internal/ExpectedFCMFamilyImpl.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/merkle/map/internal/ExpectedFCMFamilyImpl.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/merkle/map/internal/ExpectedFCMFamilyImpl.java index de1aca59e07a..e0de04fd18a9 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/merkle/map/internal/ExpectedFCMFamilyImpl.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/merkle/map/internal/ExpectedFCMFamilyImpl.java @@ -761,12 +761,6 @@ private boolean creatingOnExistingEntities( transactionType, expectedValue.getEntityType()); } else { - logger.error( - EXCEPTION.getMarker(), - "ERROR CreateOnExistingEntities, " + "MapKey: {}, TransactionType: {}, EntityType: {}", - mapKey, - transactionType, - expectedValue.getEntityType()); expectedValue.setErrored(true); } return true; From 7a58a17b66c27a526401187fb03a98bfca8db4cd Mon Sep 17 00:00:00 2001 From: Valentin Valkanov Date: Tue, 19 Dec 2023 10:38:39 +0200 Subject: [PATCH 04/80] fix: LeakyContractTestsSuite HAPI tests (#10287) Signed-off-by: Valentin Valkanov Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../hts/create/ClassicCreatesCall.java | 6 +++--- .../hts/create/ClassicCreatesCallTest.java | 6 +++--- .../bdd/spec/queries/meta/HapiGetReceipt.java | 21 ++++++++++++++++++- .../suites/leaky/LeakyContractTestsSuite.java | 9 +++++++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index ed8e613f71a4..507285bb448f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -40,7 +40,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; +import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -95,14 +95,14 @@ public ClassicCreatesCall( } final var recordBuilder = systemContractOperations() - .dispatch(syntheticCreate, verificationStrategy, spenderId, CryptoCreateRecordBuilder.class); + .dispatch(syntheticCreate, verificationStrategy, spenderId, ContractCallRecordBuilder.class); final var customFees = ((TokenCreateTransactionBody) syntheticCreate.data().value()).customFees(); final var tokenType = ((TokenCreateTransactionBody) syntheticCreate.data().value()).tokenType(); final var status = recordBuilder.status(); if (status != ResponseCodeEnum.SUCCESS) { - return gasOnly(revertResult(status, MINIMUM_TINYBAR_PRICE), status, false); + return gasOnly(revertResult(recordBuilder, MINIMUM_TINYBAR_PRICE), status, false); } else { final var isFungible = tokenType == TokenType.FUNGIBLE_COMMON; ByteBuffer encodedOutput; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java index ff16ba97a940..06564ce257a8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java @@ -37,8 +37,8 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.ClassicCreatesCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.CreateTranslator; +import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; -import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; import java.math.BigInteger; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Wei; @@ -59,7 +59,7 @@ public class ClassicCreatesCallTest extends HtsCallTestBase { private SystemContractGasCalculator systemContractGasCalculator; @Mock - private CryptoCreateRecordBuilder recordBuilder; + private ContractCallRecordBuilder recordBuilder; private static final TransactionBody PRETEND_CREATE_TOKEN = TransactionBody.newBuilder() .tokenCreation(TokenCreateTransactionBody.newBuilder() @@ -280,7 +280,7 @@ private void commonGivens() { any(TransactionBody.class), eq(verificationStrategy), eq(A_NEW_ACCOUNT_ID), - eq(CryptoCreateRecordBuilder.class))) + eq(ContractCallRecordBuilder.class))) .willReturn(recordBuilder); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java index d3590bba0d12..3806c921d540 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java @@ -30,7 +30,11 @@ import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionID; import com.hederahashgraph.api.proto.java.TransactionReceipt; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Arrays; +import java.util.EnumSet; import java.util.Optional; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; @@ -48,6 +52,10 @@ public class HapiGetReceipt extends HapiQueryOp { Optional expectedScheduledTxnId = Optional.empty(); Optional explicitTxnId = Optional.empty(); Optional expectedPriorityStatus = Optional.empty(); + + @Nullable + Set expectedPriorityStatuses = null; + Optional expectedDuplicateStatuses = Optional.empty(); Optional hasChildAutoAccountCreations = Optional.empty(); @@ -99,6 +107,14 @@ public HapiGetReceipt hasPriorityStatus(ResponseCodeEnum status) { return this; } + public HapiGetReceipt hasPriorityStatusFrom(ResponseCodeEnum... statuses) { + if (statuses.length == 0) { + throw new IllegalArgumentException("Must specify at least one status"); + } + expectedPriorityStatuses = EnumSet.copyOf(Arrays.asList(statuses)); + return this; + } + public HapiGetReceipt hasDuplicateStatuses(ResponseCodeEnum... statuses) { expectedDuplicateStatuses = Optional.of(statuses); return this; @@ -137,8 +153,11 @@ protected void submitWith(HapiSpec spec, Transaction payment) { @Override protected void assertExpectationsGiven(HapiSpec spec) { var receipt = response.getTransactionGetReceipt().getReceipt(); + ResponseCodeEnum actualStatus = receipt.getStatus(); + if (expectedPriorityStatuses != null && expectedPriorityStatuses.contains(actualStatus)) { + expectedPriorityStatus = Optional.of(actualStatus); + } if (expectedPriorityStatus.isPresent()) { - ResponseCodeEnum actualStatus = receipt.getStatus(); assertEquals(expectedPriorityStatus.get(), actualStatus); } if (expectedDuplicateStatuses.isPresent()) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index ad207166adbc..b999fdd7dfbe 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -167,6 +167,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_DENOMINATION_MUST_BE_FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_MUST_BE_POSITIVE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CONTRACT_ID; @@ -821,6 +822,7 @@ final HapiSpec transferFailsWithIncorrectAmounts() { childRecordsCheck(transferTokenWithNegativeAmountTxn, CONTRACT_REVERT_EXECUTED)); } + @HapiTest HapiSpec payerCannotOverSendValue() { final var payerBalance = 666 * ONE_HBAR; final var overdraftAmount = payerBalance + ONE_HBAR; @@ -846,10 +848,12 @@ HapiSpec payerCannotOverSendValue() { .then( sleepFor(1_000), getReceipt(uncheckedCC) - .hasPriorityStatus(INSUFFICIENT_PAYER_BALANCE) + // Mod-service and mono-service use these mostly interchangeably + .hasPriorityStatusFrom(INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE) .logged()); } + @HapiTest final HapiSpec createTokenWithInvalidFeeCollector() { return propertyPreservingHapiSpec("createTokenWithInvalidFeeCollector") .preserving(CRYPTO_CREATE_WITH_ALIAS_ENABLED, CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS) @@ -897,6 +901,7 @@ final HapiSpec createTokenWithInvalidFeeCollector() { .error(INVALID_CUSTOM_FEE_COLLECTOR.name())))); } + // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys final HapiSpec createTokenWithInvalidFixedFeeWithERC721Denomination() { final String feeCollector = ACCOUNT_2; final String someARAccount = "someARAccount"; @@ -952,6 +957,7 @@ final HapiSpec createTokenWithInvalidFixedFeeWithERC721Denomination() { .error(CUSTOM_FEE_DENOMINATION_MUST_BE_FUNGIBLE_COMMON.name())))); } + // Requires legacy security model, cannot be enabled as @HapiTest without refactoring to use contract keys final HapiSpec createTokenWithInvalidRoyaltyFee() { final String feeCollector = ACCOUNT_2; AtomicReference existingToken = new AtomicReference<>(); @@ -1269,6 +1275,7 @@ final HapiSpec maxRefundIsMaxGasRefundConfiguredWhenTXGasPriceIsSmaller() { resetToDefault(CONTRACTS_MAX_REFUND_PERCENT_OF_GAS_LIMIT)); } + @HapiTest @SuppressWarnings("java:S5960") final HapiSpec contractCreationStoragePriceMatchesFinalExpiry() { final var toyMaker = "ToyMaker"; From e2ef0f6f028d38f7175d5fe7b2544988c311bafe Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 19 Dec 2023 09:18:48 -0600 Subject: [PATCH 05/80] fix: downgrade migration testing tool errors to warnings (#10556) Signed-off-by: Cody Littley --- .../demo/migration/MigrationTestingToolMain.java | 12 +++++------- .../demo/migration/MigrationTestingToolState.java | 15 +++++++-------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java index c6ad6f411445..df7140add3b6 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java @@ -17,6 +17,7 @@ package com.swirlds.demo.migration; import static com.swirlds.base.units.UnitConstants.NANOSECONDS_TO_SECONDS; +import static com.swirlds.logging.legacy.LogMarker.STARTUP; import com.swirlds.common.platform.NodeId; import com.swirlds.fcqueue.FCQueueStatistics; @@ -31,8 +32,6 @@ import java.security.SignatureException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; /** * An application designed for testing migration from version to version. @@ -43,8 +42,6 @@ public class MigrationTestingToolMain implements SwirldMain { private static final Logger logger = LogManager.getLogger(MigrationTestingToolMain.class); - static final Marker MARKER = MarkerManager.getMarker("MIGRATION"); - private long seed; private int maximumTransactionsPerNode; private int transactionsCreated; @@ -71,7 +68,7 @@ public void init(final Platform platform, final NodeId selfId) { this.platform = platform; final String[] parameters = ParameterProvider.getInstance().getParameters(); - logger.info(MARKER, "Parsing arguments {}", (Object) parameters); + logger.info(STARTUP.getMarker(), "Parsing arguments {}", (Object) parameters); seed = Long.parseLong(parameters[0]) + selfId.id(); maximumTransactionsPerNode = Integer.parseInt(parameters[1]); @@ -89,7 +86,7 @@ public void init(final Platform platform, final NodeId selfId) { public void run() { try { logger.info( - MARKER, + STARTUP.getMarker(), "MigrationTestingApp started handling {} transactions with seed {}", maximumTransactionsPerNode, seed); @@ -107,7 +104,8 @@ public void run() { } logger.info( - MARKER, () -> new ApplicationFinishedPayload("MigrationTestingApp finished handling transactions")); + STARTUP.getMarker(), + () -> new ApplicationFinishedPayload("MigrationTestingApp finished handling transactions")); } catch (final Exception ex) { throw new RuntimeException(ex); } diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index 9eadfe613d50..548edd45f9d4 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -16,9 +16,8 @@ package com.swirlds.demo.migration; -import static com.swirlds.demo.migration.MigrationTestingToolMain.MARKER; import static com.swirlds.demo.migration.MigrationTestingToolMain.PREVIOUS_SOFTWARE_VERSION; -import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import static com.swirlds.logging.legacy.LogMarker.STARTUP; import com.swirlds.common.crypto.DigestType; import com.swirlds.common.io.streams.SerializableDataInputStream; @@ -231,28 +230,28 @@ public void init( final MerkleMap merkleMap = getMerkleMap(); if (merkleMap != null) { - logger.info(MARKER, "MerkleMap initialized with {} values", merkleMap.size()); + logger.info(STARTUP.getMarker(), "MerkleMap initialized with {} values", merkleMap.size()); } final VirtualMap virtualMap = getVirtualMap(); if (virtualMap != null) { - logger.info(MARKER, "VirtualMap initialized with {} values", virtualMap.size()); + logger.info(STARTUP.getMarker(), "VirtualMap initialized with {} values", virtualMap.size()); } selfId = platform.getSelfId(); if (trigger == InitTrigger.GENESIS) { - logger.error(EXCEPTION.getMarker(), "InitTrigger was {} when expecting RESTART or RECONNECT", trigger); + logger.warn(STARTUP.getMarker(), "InitTrigger was {} when expecting RESTART or RECONNECT", trigger); } if (previousSoftwareVersion == null || previousSoftwareVersion.compareTo(PREVIOUS_SOFTWARE_VERSION) != 0) { - logger.error( - EXCEPTION.getMarker(), + logger.warn( + STARTUP.getMarker(), "previousSoftwareVersion was {} when expecting it to be {}", previousSoftwareVersion, PREVIOUS_SOFTWARE_VERSION); } if (trigger == InitTrigger.GENESIS) { - logger.info(MARKER, "Doing genesis initialization"); + logger.info(STARTUP.getMarker(), "Doing genesis initialization"); genesisInit(platform); } } From c81d6f30e0ed6ddc26f1c647aba3fef0ae2990c4 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 19 Dec 2023 13:50:37 -0500 Subject: [PATCH 06/80] build: Upgrade PBJ to 0.7.11 (#10426) Signed-off-by: Ivan Malygin --- hedera-dependency-versions/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hedera-dependency-versions/build.gradle.kts b/hedera-dependency-versions/build.gradle.kts index 8689790e2054..8cce0e50e570 100644 --- a/hedera-dependency-versions/build.gradle.kts +++ b/hedera-dependency-versions/build.gradle.kts @@ -55,7 +55,7 @@ moduleInfo { version("com.google.jimfs", "1.2") version("com.google.protobuf", protobufVersion) version("com.google.protobuf.util", protobufVersion) - version("com.hedera.pbj.runtime", "0.7.6") + version("com.hedera.pbj.runtime", "0.7.11") version("com.squareup.javapoet", "1.13.0") version("com.sun.jna", "5.12.1") version("dagger", daggerVersion) diff --git a/settings.gradle.kts b/settings.gradle.kts index 6d9edd335ee8..cd10d2515d62 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -147,6 +147,6 @@ dependencyResolutionManagement { version("grpc-proto", "1.45.1") version("hapi-proto", hapiProtoVersion) - plugin("pbj", "com.hedera.pbj.pbj-compiler").version("0.7.6") + plugin("pbj", "com.hedera.pbj.pbj-compiler").version("0.7.11") } } From 0cdcec365f2dd3b74f8e9c23597532555a1f578e Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 19 Dec 2023 14:58:50 -0600 Subject: [PATCH 07/80] fix: owngrade PTT error to a warning (#10574) Signed-off-by: Cody Littley --- .../VirtualMerkleTransactionHandler.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/transaction/handler/VirtualMerkleTransactionHandler.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/transaction/handler/VirtualMerkleTransactionHandler.java index e9af7207a121..652bb117992c 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/transaction/handler/VirtualMerkleTransactionHandler.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/virtualmerkle/transaction/handler/VirtualMerkleTransactionHandler.java @@ -16,7 +16,7 @@ package com.swirlds.demo.virtualmerkle.transaction.handler; -import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import static com.swirlds.logging.legacy.LogMarker.DEMO_INFO; import static com.swirlds.merkle.map.test.lifecycle.TransactionState.HANDLED; import static com.swirlds.merkle.map.test.lifecycle.TransactionType.Create; import static com.swirlds.merkle.map.test.lifecycle.TransactionType.CreateExistingAccount; @@ -60,7 +60,6 @@ public class VirtualMerkleTransactionHandler { private static final Logger logger = LogManager.getLogger(VirtualMerkleTransactionHandler.class); private static final Marker LOGM_DEMO_INFO = LogMarker.DEMO_INFO.getMarker(); - private static final Marker ERROR = LogMarker.ERROR.getMarker(); /** * This method handles all the transactions that perform changes on the {@link VirtualMap} instances @@ -160,8 +159,8 @@ private static void handleMethodExecutionTransaction( smartContractByteCodeVirtualMap.get(byteCodeKey); if (smartContractByteCodeMapValue == null) { - logger.error( - EXCEPTION.getMarker(), + logger.warn( + DEMO_INFO.getMarker(), "Value for key {} was not found inside smart contract bytecode map.", byteCodeKey); return; @@ -171,8 +170,8 @@ private static void handleMethodExecutionTransaction( final SmartContractMapValue smartContractKeyValuePairsCounter = smartContractVirtualMap.get(contractKey); if (smartContractKeyValuePairsCounter == null) { - logger.error( - EXCEPTION.getMarker(), "Value for key {} was not found inside smart contract map.", contractKey); + logger.warn( + DEMO_INFO.getMarker(), "Value for key {} was not found inside smart contract map.", contractKey); return; } final long totalKeyValuePairs = smartContractKeyValuePairsCounter.getValueAsLong(); @@ -184,8 +183,8 @@ private static void handleMethodExecutionTransaction( new SmartContractMapKey(methodExecution.getContractId(), keyValuePairIdx); final SmartContractMapValue smartContractMapValue = smartContractVirtualMap.get(smartContractMapKey); if (smartContractMapValue == null) { - logger.error( - EXCEPTION.getMarker(), + logger.warn( + DEMO_INFO.getMarker(), "Value for key {} was not found inside smart contract map.", smartContractMapKey); return; @@ -198,8 +197,8 @@ private static void handleMethodExecutionTransaction( new SmartContractMapKey(methodExecution.getContractId(), keyValuePairIdx); final SmartContractMapValue smartContractMapValue = smartContractVirtualMap.get(smartContractMapKey); if (smartContractMapValue == null) { - logger.error( - EXCEPTION.getMarker(), + logger.warn( + DEMO_INFO.getMarker(), "Value for key {} was not found inside smart contract map.", smartContractMapKey); return; @@ -376,8 +375,8 @@ public static void handleExpectedMapValidation( final AccountVirtualMapValue virtualMapValue = virtualMap.get(accountVirtualMapKey); if (virtualMapValue == null) { notMismatching.set(false); - logger.error( - EXCEPTION.getMarker(), "An account from the expected map is not present inside the state."); + logger.warn( + DEMO_INFO.getMarker(), "An account from the expected map is not present inside the state."); return; } final MapValueData mapValueData = new MapValueData( @@ -393,14 +392,14 @@ public static void handleExpectedMapValidation( } else if (lastTransactionType == CreateExistingAccount) { if (!virtualMap.containsKey(accountVirtualMapKey)) { notMismatching.set(false); - logger.error(EXCEPTION.getMarker(), "A created account does not exist inside the state."); + logger.warn(DEMO_INFO.getMarker(), "A created account does not exist inside the state."); } } else if (lastTransactionType == Delete || lastTransactionType == DeleteNotExistentAccount || lastTransactionType == UpdateNotExistentAccount) { if (virtualMap.containsKey(accountVirtualMapKey)) { notMismatching.set(false); - logger.error(EXCEPTION.getMarker(), "A deleted account is still present inside the state."); + logger.warn(DEMO_INFO.getMarker(), "A deleted account is still present inside the state."); } } }); From 4b2a1af78a13bf294e019c4fba1c3a5b6352a570 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 19 Dec 2023 19:01:16 -0500 Subject: [PATCH 08/80] fix: `DataFileCollectionCompactionTest.testMergeUpdateSnapshotRestore` flake (#10577) Signed-off-by: Ivan Malygin --- .../merkledb/files/DataFileCollectionCompactionTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/DataFileCollectionCompactionTest.java b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/DataFileCollectionCompactionTest.java index bc5224479638..e72fd22aa361 100644 --- a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/DataFileCollectionCompactionTest.java +++ b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/files/DataFileCollectionCompactionTest.java @@ -467,7 +467,8 @@ void testMergeUpdateSnapshotRestore(final int testParam) throws Throwable { final Future f = exec.submit(() -> { try { final List> filesToMerge = getFilesToMerge(store); - assertEquals(numFiles, filesToMerge.size()); + // Data file collection may create a new file before the compaction starts + assertTrue(filesToMerge.size() == numFiles || filesToMerge.size() == numFiles + 1); compactor.compactFiles(index, filesToMerge, 1); // Wait for the new file to be available. Without this wait, there // may be 1 or 2 From 9e84dd9cde84b6a28b3fd5c90b2d971c7ff62ba7 Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Tue, 19 Dec 2023 20:03:56 -0600 Subject: [PATCH 09/80] chore: avoid confusing `INSUFFICIENT_PAYER_BALANCE` warnings (#10578) Signed-off-by: Michael Tinker --- .../misc/CannotDeleteSystemEntitiesSuite.java | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java index 0d0e1c50c145..b3b881d47cfc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java @@ -23,6 +23,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileDelete; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingHbar; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ENTITY_NOT_ALLOWED_TO_DELETE; @@ -56,24 +57,22 @@ public boolean canRunConcurrent() { @Override public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - ensureSystemAccountsHaveSomeFunds(), - genesisCannotDeleteSystemAccountsFrom1To100(), - genesisCannotDeleteSystemAccountsFrom700To750(), - systemAdminCannotDeleteSystemAccountsFrom1To100(), - systemAdminCannotDeleteSystemAccountsFrom700To750(), - systemDeleteAdminCannotDeleteSystemAccountsFrom1To100(), - systemDeleteAdminCannotDeleteSystemAccountsFrom700To750(), - normalUserCannotDeleteSystemAccountsFrom1To100(), - normalUserCannotDeleteSystemAccountsFrom700To750(), - genesisCannotDeleteSystemFileIds(), - systemAdminCannotDeleteSystemFileIds(), - systemDeleteAdminCannotDeleteSystemFileIds(), - normalUserCannotDeleteSystemFileIds(), - genesisCannotSystemFileDeleteFileIds(), - systemAdminCannotSystemFileDeleteFileIds(), - systemDeleteAdminCannotSystemFileDeleteFileIds() - }); + return List.of( + genesisCannotDeleteSystemAccountsFrom1To100(), + genesisCannotDeleteSystemAccountsFrom700To750(), + systemAdminCannotDeleteSystemAccountsFrom1To100(), + systemAdminCannotDeleteSystemAccountsFrom700To750(), + systemDeleteAdminCannotDeleteSystemAccountsFrom1To100(), + systemDeleteAdminCannotDeleteSystemAccountsFrom700To750(), + normalUserCannotDeleteSystemAccountsFrom1To100(), + normalUserCannotDeleteSystemAccountsFrom700To750(), + genesisCannotDeleteSystemFileIds(), + systemAdminCannotDeleteSystemFileIds(), + systemDeleteAdminCannotDeleteSystemFileIds(), + normalUserCannotDeleteSystemFileIds(), + genesisCannotSystemFileDeleteFileIds(), + systemAdminCannotSystemFileDeleteFileIds(), + systemDeleteAdminCannotSystemFileDeleteFileIds()); } @HapiTest @@ -82,7 +81,8 @@ final HapiSpec ensureSystemAccountsHaveSomeFunds() { .given() .when() .then( - cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_ADMIN, 10 * ONE_HUNDRED_HBARS)) + cryptoTransfer(movingHbar(100 * ONE_HUNDRED_HBARS) + .distributing(GENESIS, SYSTEM_ADMIN, SYSTEM_DELETE_ADMIN)) .payingWith(GENESIS), cryptoTransfer(tinyBarsFromTo(GENESIS, SYSTEM_DELETE_ADMIN, 10 * ONE_HUNDRED_HBARS)) .payingWith(GENESIS)); @@ -165,14 +165,18 @@ final HapiSpec systemDeleteAdminCannotSystemFileDeleteFileIds() { final HapiSpec systemUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount, String sysUser) { return defaultHapiSpec("systemUserCannotDeleteSystemAccounts") - .given(cryptoCreate("unluckyReceiver").balance(0L)) + .given( + cryptoCreate("unluckyReceiver").balance(0L), + cryptoTransfer(movingHbar(100 * ONE_HUNDRED_HBARS) + .distributing(GENESIS, SYSTEM_ADMIN, SYSTEM_DELETE_ADMIN)) + .payingWith(GENESIS)) .when() .then(inParallel(IntStream.rangeClosed(firstAccount, lastAccount) .mapToObj(id -> cryptoDelete("0.0." + id) .transfer("unluckyReceiver") .payingWith(sysUser) .signedBy(sysUser) - .hasPrecheck(ENTITY_NOT_ALLOWED_TO_DELETE)) + .hasPrecheckFrom(ENTITY_NOT_ALLOWED_TO_DELETE)) .toArray(HapiSpecOperation[]::new))); } From 61484699e769eac19720816008b76cff4d1df878 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Wed, 20 Dec 2023 07:02:37 +0200 Subject: [PATCH 10/80] fix: use `haltResult()` instead of throwing `HandleException` in `HtsSystemContract` (#10534) --- .../contract/impl/exec/FrameRunner.java | 13 +++++++++++ .../systemcontracts/HtsSystemContract.java | 13 +++++++++-- .../impl/test/exec/FrameRunnerTest.java | 23 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java index fcd096f2fe69..1ba2f7d8ece8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java @@ -16,13 +16,16 @@ package com.hedera.node.app.service.contract.impl.exec; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.getAndClearPropagatedCallFailure; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.maybeNext; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.setPropagatedCallFailure; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult.failureFrom; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult.successFrom; import static com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure.NONE; +import static com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure.RESULT_CANNOT_BE_EXTERNALIZED; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZero; @@ -41,6 +44,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; @@ -136,6 +140,8 @@ private void runToCompletion( case CONTRACT_CREATION -> contractCreation; }; executor.process(frame, tracer); + + frame.getExceptionalHaltReason().ifPresent(haltReason -> propagateHaltException(frame, haltReason)); // For mono-service compatibility, we need to also halt the frame on the stack that // executed the CALL operation whose dispatched frame failed due to a missing receiver // signature; since mono-service did that check as part of the CALL operation itself @@ -156,4 +162,11 @@ private long effectiveGasUsed(final long gasLimit, @NonNull final MessageFrame f final var maxRefundPercent = contractsConfigOf(frame).maxRefundPercentOfGasLimit(); return Math.max(nominalUsed, gasLimit - gasLimit * maxRefundPercent / 100); } + + // potentially other cases could be handled here if necessary + private void propagateHaltException(MessageFrame frame, ExceptionalHaltReason haltReason) { + if (haltReason.equals(INSUFFICIENT_CHILD_RECORDS)) { + setPropagatedCallFailure(frame, RESULT_CANNOT_BE_EXTERNALIZED); + } + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java index 838f6f4aaed9..d37b655948fa 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts; +import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; @@ -26,6 +27,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.ContractID; +import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; @@ -135,8 +137,7 @@ private static FullResult resultOfExecuting( } } } catch (final HandleException handleException) { - // TODO - this is almost certainly not the right way to handle this! - throw handleException; + return haltHandleException(handleException, frame.getRemainingGas()); } catch (final Exception internal) { log.error("Unhandled failure for input {} to HTS system contract", input, internal); return haltResult(ExceptionalHaltReason.PRECOMPILE_ERROR, frame.getRemainingGas()); @@ -146,4 +147,12 @@ private static FullResult resultOfExecuting( } return pricedResult.fullResult(); } + + // potentially other cases could be handled here if necessary + private static FullResult haltHandleException(final HandleException handleException, long remainingGas) { + if (handleException.getStatus().equals(MAX_CHILD_RECORDS_EXCEEDED)) { + return haltResult(CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS, remainingGas); + } + throw handleException; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java index 9cf61e8aa496..662f3d9de05b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.contract.impl.test.exec; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.FAILURE_DURING_LAZY_ACCOUNT_CREATION; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_SIGNATURE; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.BESU_LOG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.BESU_MAX_REFUND_QUOTIENT; @@ -205,6 +206,28 @@ void failurePathWorksWithHaltReason() { assertNull(result.revertReason()); } + @Test + void failurePathWorksWithHaltReasonWhenExceedingChildRecords() { + final var inOrder = Mockito.inOrder(frame, childFrame, tracer, messageCallProcessor, contractCreationProcessor); + final Deque messageFrameStack = new ArrayDeque<>(); + messageFrameStack.addFirst(frame); + + givenBaseFailureWith(NON_SYSTEM_LONG_ZERO_ADDRESS); + given(frame.getExceptionalHaltReason()).willReturn(Optional.of(INSUFFICIENT_CHILD_RECORDS)); + + final var result = subject.runToCompletion( + GAS_LIMIT, SENDER_ID, frame, tracer, messageCallProcessor, contractCreationProcessor); + + inOrder.verify(tracer).traceOriginAction(frame); + inOrder.verify(contractCreationProcessor).process(frame, tracer); + inOrder.verify(messageCallProcessor).process(childFrame, tracer); + inOrder.verify(tracer).sanitizeTracedActions(frame); + + assertFailureExpectationsWith(frame, result); + assertEquals(INSUFFICIENT_CHILD_RECORDS, result.haltReason()); + assertNull(result.revertReason()); + } + private void assertSuccessExpectationsWith( @NonNull final ContractID expectedReceiverId, @NonNull final ContractID expectedReceiverAddress, From 32d3ab02bb59f9e317a6b158fb38217944067257 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov Date: Wed, 20 Dec 2023 08:23:55 +0200 Subject: [PATCH 11/80] chore: Follow up - Unconditional, enriched, traced records of UtilPrng system contract calls (#10567) Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../systemcontracts/HtsSystemContract.java | 7 +------ .../systemcontracts/PrngSystemContract.java | 13 ++----------- .../HtsSystemContractTest.java | 19 +++---------------- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java index d37b655948fa..492a084e82ed 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java @@ -32,8 +32,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; -import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; -import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.spi.workflows.HandleException; import edu.umd.cs.findbugs.annotations.NonNull; @@ -112,9 +110,6 @@ private static FullResult resultOfExecuting( if (responseCode == SUCCESS) { final var output = pricedResult.fullResult().result().getOutput(); - var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); - final var senderId = ((ProxyEvmAccount) updater.getAccount(frame.getSenderAddress())).hederaId(); - enhancement .systemOperations() .externalizeResult( @@ -123,7 +118,7 @@ private static FullResult resultOfExecuting( output, frame.getRemainingGas(), frame.getInputData(), - senderId), + attempt.senderId()), responseCode); } else { enhancement diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java index ee56a62e75a1..9d48ce0e20e5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/PrngSystemContract.java @@ -22,17 +22,14 @@ import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.HTS_PRECOMPILE_MIRROR_ID; import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedFor; import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultSuccessFor; -import static com.hedera.node.app.service.evm.utils.ValidationUtils.validateTrue; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FAIL_INVALID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; import static java.util.Objects.requireNonNull; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.hapi.node.util.UtilPrngTransactionBody; -import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; -import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy.Decision; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; @@ -84,7 +81,6 @@ public FullResult computeFully(@NonNull final Bytes input, @NonNull final Messag final ContractID contractID = asEvmContractId(Address.fromHexString(PRNG_PRECOMPILE_ADDRESS)); try { - validateTrue(frame.getRemainingGas() >= gasRequirement, INSUFFICIENT_GAS); // compute the pseudorandom number final var randomNum = generatePseudoRandomData(input, frame); requireNonNull(randomNum); @@ -126,12 +122,7 @@ void createSuccessfulRecord( updater.enhancement() .systemOperations() - .dispatch( - synthBody(), - new ActiveContractVerificationStrategy( - senderId.accountNum(), contractID.evmAddress(), false, UseTopLevelSigs.NO), - senderId, - ContractCallRecordBuilder.class) + .dispatch(synthBody(), key -> Decision.INVALID, senderId, ContractCallRecordBuilder.class) .contractCallResult(data) .entropyBytes(tuweniToPbjBytes(randomNum)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java index 0b5de8a0a930..3214844dcd31 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java @@ -27,10 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.hedera.hapi.node.base.AccountID; import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; @@ -38,11 +36,9 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import java.nio.ByteBuffer; import org.apache.tuweni.bytes.Bytes; -import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -98,7 +94,7 @@ void clear() { @Test void returnsResultFromImpliedCall() { - messageFrameMock(); + commonMocks(); givenValidCallAttempt(); final var pricedResult = gasOnly(successResult(ByteBuffer.allocate(1), 123L), SUCCESS, true); @@ -128,7 +124,7 @@ void internalErrorAttemptHaltsAndConsumesRemainingGas() { @Test void callWithNonGasCostNotImplemented() { - messageFrameMock(); + commonMocks(); givenValidCallAttempt(); final var pricedResult = @@ -147,18 +143,9 @@ private void givenValidCallAttempt() { given(attempt.asExecutableCall()).willReturn(call); } - private void messageFrameMock() { - final var worldUpdater = mock(ProxyWorldUpdater.class); - final var address = Address.fromHexString("0x100"); + private void commonMocks() { final var remainingGas = 10000L; - when(frame.getWorldUpdater()).thenReturn(worldUpdater); - when(frame.getSenderAddress()).thenReturn(address); when(frame.getRemainingGas()).thenReturn(remainingGas); when(frame.getInputData()).thenReturn(org.apache.tuweni.bytes.Bytes.EMPTY); - - final var mutableAccount = mock(ProxyEvmAccount.class); - final var accountID = mock(AccountID.class); - when(worldUpdater.getAccount(address)).thenReturn(mutableAccount); - when(mutableAccount.hederaId()).thenReturn(accountID); } } From 65fab23015d700c6396ea7749bee344ed5fee5fd Mon Sep 17 00:00:00 2001 From: Maxi Tartaglia <152629744+mxtartaglia-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:29:31 -0300 Subject: [PATCH 12/80] chore: Remove throw arg null (#10433) Signed-off-by: Maxi Tartaglia --- .../signing/algorithms/ExtendedSignature.java | 6 ++- .../config/singleton/ConfigurationHolder.java | 5 ++- .../metrics/extensions/CountPerSecond.java | 2 +- .../metrics/platform/DefaultMetrics.java | 41 +++++++++++-------- .../common/metrics/platform/MetricsEvent.java | 14 +++++-- .../metrics/platform/MetricsEventBus.java | 20 +++++---- .../platform/MetricsUpdateService.java | 13 +++--- .../common/metrics/platform/Snapshot.java | 5 ++- .../metrics/platform/SnapshotEvent.java | 8 ++-- .../metrics/platform/SnapshotService.java | 24 +++++++---- .../prometheus/AbstractMetricAdapter.java | 8 ++-- .../platform/prometheus/BooleanAdapter.java | 16 +++++--- .../platform/prometheus/CounterAdapter.java | 16 +++++--- .../prometheus/DistributionAdapter.java | 16 +++++--- .../platform/prometheus/NameConverter.java | 6 +-- .../platform/prometheus/NumberAdapter.java | 16 +++++--- .../prometheus/PrometheusEndpoint.java | 11 ++--- .../platform/prometheus/StringAdapter.java | 15 ++++--- .../metrics/statistics/StatsSpeedometer.java | 6 +-- .../swirlds/common/utility/CommonUtils.java | 33 --------------- .../metrics/platform/DefaultMetricsTest.java | 25 ++++++----- .../metrics/platform/MetricsEventBusTest.java | 9 ++-- .../prometheus/BooleanAdapterTest.java | 18 ++++---- .../prometheus/CounterAdapterTest.java | 18 ++++---- .../prometheus/DistributionAdapterTest.java | 14 +++---- .../platform/prometheus/NameConvertTest.java | 2 +- .../prometheus/NumberAdapterTest.java | 18 ++++---- .../prometheus/PrometheusEndpointTest.java | 6 +-- .../prometheus/StringAdapterTest.java | 18 ++++---- .../swirlds/fcqueue/FCQueueStatistics.java | 5 ++- .../fcqueue/FCQueueStatisticsTest.java | 2 +- .../swirlds/merkledb/MerkleDbStatistics.java | 10 ++--- .../merkledb/MerkleDbStatisticsTest.java | 4 +- .../config/legacy/LegacyConfigProperties.java | 11 +++-- .../legacy/LegacyConfigPropertiesLoader.java | 6 ++- .../platform/crypto/PlatformSigner.java | 4 +- .../platform/dispatch/DispatchBuilder.java | 23 +++++++---- .../dispatch/flowchart/DispatchFlowchart.java | 6 +-- .../platform/metrics/AddedEventMetrics.java | 13 ++++-- .../metrics/ConsensusHandlingMetrics.java | 7 ++-- .../metrics/ConsensusMetricsImpl.java | 11 +++-- .../platform/metrics/RuntimeMetrics.java | 7 +++- .../handshake/VersionCompareHandshake.java | 5 ++- .../MultiFileRunningHashIterator.java | 6 +-- .../state/signed/SignedStateHasher.java | 12 ++++-- .../stats/cycle/PercentageMetric.java | 7 +++- .../platform/util/MetricsDocUtils.java | 18 +++++--- .../util/iterator/SkippingIterator.java | 6 +-- .../platform/util/iterator/TypedIterator.java | 6 +-- .../com/swirlds/platform/DispatchTests.java | 16 ++++---- .../LegacyConfigPropertiesLoaderTest.java | 3 +- .../util/iterator/SkippingIteratorTest.java | 2 +- .../util/iterator/TypedIteratorTest.java | 2 +- .../internal/merkle/VirtualMapStatistics.java | 12 +++--- 54 files changed, 326 insertions(+), 287 deletions(-) diff --git a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/algorithms/ExtendedSignature.java b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/algorithms/ExtendedSignature.java index 9c0e531425fe..6b049358ecc5 100644 --- a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/algorithms/ExtendedSignature.java +++ b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/algorithms/ExtendedSignature.java @@ -16,7 +16,7 @@ package com.swirlds.demo.stats.signing.algorithms; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; +import java.util.Objects; /** * An extended signature which provides both the raw/encoded signature and its coordinate pair (if the signature was @@ -68,9 +68,11 @@ public ExtendedSignature(final byte[] signature) { * the raw or encoded R coordinate. * @param s * the raw of encoded S coordinate. + * + * @throws NullPointerException in case {@code signature} parameter is {@code null} */ public ExtendedSignature(final byte[] signature, final byte[] r, final byte[] s) { - throwArgNull(signature, "signature"); + Objects.requireNonNull(signature, "signature must not be null"); this.signature = signature; this.r = r; diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/config/singleton/ConfigurationHolder.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/config/singleton/ConfigurationHolder.java index d28d9e63b1c5..63395583aeac 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/config/singleton/ConfigurationHolder.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/config/singleton/ConfigurationHolder.java @@ -17,10 +17,10 @@ package com.swirlds.common.config.singleton; import com.swirlds.common.config.ConfigUtils; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.config.api.ConfigData; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; +import java.util.Objects; import java.util.Set; import java.util.function.Supplier; @@ -61,9 +61,10 @@ public void reset() { * Sets the config. This method should only be called in the browser at startup or at unit tests * * @param configuration the new configuration + * @throws NullPointerException in case {@code configuration} parameter is {@code null} */ public void setConfiguration(final Configuration configuration) { - this.configuration = CommonUtils.throwArgNull(configuration, "configuration"); + this.configuration = Objects.requireNonNull(configuration, "configuration must not be null"); } /** diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/extensions/CountPerSecond.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/extensions/CountPerSecond.java index 5ac8c5489458..7f6e4b9b95e7 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/extensions/CountPerSecond.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/extensions/CountPerSecond.java @@ -16,8 +16,8 @@ package com.swirlds.common.metrics.extensions; +import static com.swirlds.base.ArgumentUtils.throwArgBlank; import static com.swirlds.common.metrics.FloatFormats.FORMAT_10_2; -import static com.swirlds.common.utility.CommonUtils.throwArgBlank; import com.swirlds.base.time.Time; import com.swirlds.base.units.UnitConstants; diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/DefaultMetrics.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/DefaultMetrics.java index 640f9139755b..e53828cb581e 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/DefaultMetrics.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/DefaultMetrics.java @@ -18,7 +18,6 @@ import static com.swirlds.common.metrics.platform.MetricsEvent.Type.ADDED; import static com.swirlds.common.metrics.platform.MetricsEvent.Type.REMOVED; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import com.swirlds.common.metrics.Metric; import com.swirlds.common.metrics.MetricConfig; @@ -31,6 +30,7 @@ import java.util.Collections; import java.util.Map; import java.util.NavigableMap; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ScheduledExecutorService; @@ -49,9 +49,6 @@ public class DefaultMetrics implements PlatformMetrics { */ public static final int EXCEPTION_RATE_THRESHOLD = 10; - private static final String CATEGORY = "category"; - private static final String NAME = "name"; - // A reference to the NodeId of the current node private final NodeId selfId; @@ -89,6 +86,14 @@ public class DefaultMetrics implements PlatformMetrics { * the {@link PlatformMetricsFactory} that will be used to create new instances of {@link Metric} * @param metricsConfig * the {@link MetricsConfig} for metrics configuration + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code metricKeyRegistry}
  • + *
  • {@code executor}
  • + *
  • {@code factory}
  • + *
  • {@code metricsConfig}
  • + *
+ * */ public DefaultMetrics( final NodeId selfId, @@ -97,11 +102,11 @@ public DefaultMetrics( final PlatformMetricsFactory factory, final MetricsConfig metricsConfig) { this.selfId = selfId; - this.metricKeyRegistry = throwArgNull(metricKeyRegistry, "metricsKeyRegistry"); - this.factory = throwArgNull(factory, "factory"); - throwArgNull(executor, "executor"); + this.metricKeyRegistry = Objects.requireNonNull(metricKeyRegistry, "metricsKeyRegistry must not be null"); + this.factory = Objects.requireNonNull(factory, "factory must not be null"); + Objects.requireNonNull(executor, "executor must not be null"); this.eventBus = new MetricsEventBus<>(executor); - throwArgNull(metricsConfig, "metricsConfig"); + Objects.requireNonNull(metricsConfig, "metricsConfig must not be null"); this.updateService = metricsConfig.metricsUpdatePeriodMillis() <= 0 ? null : new MetricsUpdateService(executor, metricsConfig.metricsUpdatePeriodMillis(), TimeUnit.MILLISECONDS); @@ -120,8 +125,8 @@ public NodeId getNodeId() { */ @Override public Metric getMetric(final String category, final String name) { - throwArgNull(category, CATEGORY); - throwArgNull(name, NAME); + Objects.requireNonNull(category, "category must not be null"); + Objects.requireNonNull(name, "name must not be null"); return metricMap.get(calculateMetricKey(category, name)); } @@ -130,7 +135,7 @@ public Metric getMetric(final String category, final String name) { */ @Override public Collection findMetricsByCategory(final String category) { - throwArgNull(category, CATEGORY); + Objects.requireNonNull(category, "category must not be null"); final String start = category + "."; // The character '/' is the successor of '.' in Unicode. We use it to define the first metric-key, // which is not part of the result set anymore. @@ -169,7 +174,7 @@ public Runnable subscribe(final Consumer subscriber) { */ @Override public T getOrCreate(final MetricConfig config) { - throwArgNull(config, "config"); + Objects.requireNonNull(config, "config must not be null"); // first we check the happy path, if the metric is already registered final String key = calculateMetricKey(config); @@ -209,8 +214,8 @@ public T getOrCreate(final MetricConfig config) { */ @Override public void remove(final String category, final String name) { - throwArgNull(category, CATEGORY); - throwArgNull(name, NAME); + Objects.requireNonNull(category, "category must not be null"); + Objects.requireNonNull(name, "name must not be null"); final String metricKey = calculateMetricKey(category, name); throwIfGlobal(metricKey); final Metric metric = metricMap.remove(metricKey); @@ -226,7 +231,7 @@ public void remove(final String category, final String name) { */ @Override public void remove(final Metric metric) { - throwArgNull(metric, "metric"); + Objects.requireNonNull(metric, "metric must not be null"); final String metricKey = calculateMetricKey(metric); throwIfGlobal(metricKey); final boolean removed = metricMap.remove(metricKey, metric); @@ -242,7 +247,7 @@ public void remove(final Metric metric) { */ @Override public void remove(final MetricConfig config) { - throwArgNull(config, "config"); + Objects.requireNonNull(config, "config must not be null"); final String metricKey = calculateMetricKey(config); throwIfGlobal(metricKey); metricMap.computeIfPresent(metricKey, (key, oldValue) -> { @@ -268,7 +273,7 @@ private void throwIfGlobal(final String metricKey) { */ @Override public void addUpdater(final Runnable updater) { - throwArgNull(updater, "updater"); + Objects.requireNonNull(updater, "updater must not be null"); if (updateService != null) { updateService.addUpdater(updater); } @@ -279,7 +284,7 @@ public void addUpdater(final Runnable updater) { */ @Override public void removeUpdater(final Runnable updater) { - throwArgNull(updater, "updater"); + Objects.requireNonNull(updater, "updater must not be null"); if (updateService != null) { updateService.removeUpdater(updater); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEvent.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEvent.java index cc1989032ec6..b4a637dc3ef8 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEvent.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEvent.java @@ -16,10 +16,9 @@ package com.swirlds.common.metrics.platform; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import com.swirlds.common.metrics.Metric; import com.swirlds.common.platform.NodeId; +import java.util.Objects; public record MetricsEvent(Type type, NodeId nodeId, Metric metric) { public enum Type { @@ -27,8 +26,15 @@ public enum Type { REMOVED } + /** + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code type}
  • + *
  • {@code metric}
  • + *
+ */ public MetricsEvent { - throwArgNull(type, "type"); - throwArgNull(metric, "metric"); + Objects.requireNonNull(type, "type must not be null"); + Objects.requireNonNull(metric, "metric must not be null"); } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEventBus.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEventBus.java index 830fac700ff6..d5ebc033d7a4 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEventBus.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsEventBus.java @@ -16,8 +16,7 @@ package com.swirlds.common.metrics.platform; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @@ -42,10 +41,10 @@ public class MetricsEventBus { * * @param executor * An {@link Executor} that is used to notify subscribers - * @throws IllegalArgumentException if {@code executor} is {@code null} + * @throws NullPointerException in case {@code executor} parameter is {@code null} */ public MetricsEventBus(final Executor executor) { - this.executor = throwArgNull(executor, "executor"); + this.executor = Objects.requireNonNull(executor, "executor must not be null"); } /** @@ -59,11 +58,15 @@ public MetricsEventBus(final Executor executor) { * A {@link Supplier} of previous events. To ensure, that we do not miss events, this will be evaluated * after the subscriber was added. * @return a {@link Runnable} with which the subscriber can be unsubscribed - * @throws IllegalArgumentException if one of the arguments is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code subscriber}
  • + *
  • {@code previousEvents}
  • + *
*/ public Runnable subscribe(final Consumer subscriber, final Supplier> previousEvents) { - throwArgNull(subscriber, "subscriber"); - throwArgNull(previousEvents, "previousEvents"); + Objects.requireNonNull(subscriber, "subscriber must not be null"); + Objects.requireNonNull(previousEvents, "previousEvents must not be null"); subscribers.add(subscriber); executor.execute(() -> previousEvents.get().forEach(subscriber)); return () -> subscribers.remove(subscriber); @@ -76,9 +79,10 @@ public Runnable subscribe(final Consumer subscriber, final Supplier subscribers.forEach(subscriber -> subscriber.accept(event))); } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsUpdateService.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsUpdateService.java index 8e9efc5c184d..03ee06601962 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsUpdateService.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/MetricsUpdateService.java @@ -20,8 +20,8 @@ import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import com.swirlds.base.state.Startable; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.common.utility.ThresholdLimitingHandler; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentLinkedQueue; @@ -59,11 +59,14 @@ private enum State { private ScheduledFuture future; + /** + * @throws NullPointerException in case {@code executor} parameter is {@code null} + */ MetricsUpdateService(final ScheduledExecutorService executor, final long period, final TimeUnit unit) { - this.executor = CommonUtils.throwArgNull(executor, "executor"); + this.executor = Objects.requireNonNull(executor, "executor must not be null"); this.period = period; this.unit = unit; - state = new AtomicReference<>(State.INIT); + this.state = new AtomicReference<>(State.INIT); } /** @@ -75,7 +78,7 @@ private enum State { * if {@code updater} is {@code null} */ public void addUpdater(final Runnable updater) { - CommonUtils.throwArgNull(updater, "updater"); + Objects.requireNonNull(updater, "updater must not be null"); updaters.add(updater); } @@ -88,7 +91,7 @@ public void addUpdater(final Runnable updater) { * if {@code updater} is {@code null} */ public void removeUpdater(final Runnable updater) { - CommonUtils.throwArgNull(updater, "updater"); + Objects.requireNonNull(updater, "updater must not be null"); updaters.remove(updater); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/Snapshot.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/Snapshot.java index 2399672d5401..ffe3108617f2 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/Snapshot.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/Snapshot.java @@ -19,8 +19,8 @@ import static com.swirlds.common.metrics.Metric.ValueType.VALUE; import com.swirlds.common.metrics.Metric; -import com.swirlds.common.utility.CommonUtils; import java.util.List; +import java.util.Objects; /** * An instance of {@code Snapshot} contains the data of a single snapshot of a {@link Metric}. @@ -33,9 +33,10 @@ public record Snapshot(Metric metric, List entries) { * @param metric * The source metric * @return the {@code Snapshot} + * @throws NullPointerException in case {@code metric} parameter is {@code null} */ public static Snapshot of(final DefaultMetric metric) { - CommonUtils.throwArgNull(metric, "metric"); + Objects.requireNonNull(metric, "metric must not be null"); return new Snapshot(metric, metric.takeSnapshot()); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotEvent.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotEvent.java index 26df5a095c73..7fa7a167af0a 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotEvent.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotEvent.java @@ -16,14 +16,16 @@ package com.swirlds.common.metrics.platform; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import com.swirlds.common.platform.NodeId; import java.util.Collection; +import java.util.Objects; public record SnapshotEvent(NodeId nodeId, Collection snapshots) { + /** + * @throws NullPointerException in case {@code snapshots} parameter is {@code null} + */ public SnapshotEvent { - throwArgNull(snapshots, "snapshots"); + Objects.requireNonNull(snapshots, "snapshots must not be null"); } } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotService.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotService.java index 26f3ff13799a..54e903f0c1fc 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotService.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/SnapshotService.java @@ -17,7 +17,6 @@ package com.swirlds.common.metrics.platform; import static com.swirlds.common.metrics.platform.DefaultMetrics.calculateMetricKey; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import com.swirlds.base.state.Startable; import com.swirlds.base.time.Time; @@ -27,6 +26,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.RejectedExecutionException; @@ -78,8 +78,13 @@ public class SnapshotService implements Startable { * the {@link ScheduledExecutorService} that will be used to schedule the writer-tasks * @param interval * interval between snapshot generations - * @throws IllegalArgumentException - * if any of the arguments is {@code null} or {@code globalMetrics} is not global + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code globalMetrics}
  • + *
  • {@code executor}
  • + *
  • {@code interval}
  • + *
+ * */ public SnapshotService( final DefaultMetrics globalMetrics, final ScheduledExecutorService executor, final Duration interval) { @@ -92,13 +97,14 @@ public SnapshotService( final ScheduledExecutorService executor, final Duration interval, final Time time) { - this.globalMetrics = throwArgNull(globalMetrics, "globalMetrics"); + this.globalMetrics = Objects.requireNonNull(globalMetrics, "globalMetrics must not be null"); if (!globalMetrics.isGlobalMetrics()) { throw new IllegalArgumentException("Trying to create SnapshotService with non-global Metrics"); } - this.executor = throwArgNull(executor, "executor"); - this.delayNanos = throwArgNull(interval, "interval").toNanos(); - this.time = throwArgNull(time, "time"); + this.executor = Objects.requireNonNull(executor, "executor must not be null"); + this.delayNanos = + Objects.requireNonNull(interval, "interval must not be null").toNanos(); + this.time = Objects.requireNonNull(time, "time must not be null"); logger.debug("SnapshotService initialized"); } @@ -112,7 +118,7 @@ public SnapshotService( * if {@code platformMetrics} is {@code null} or not platform-specific */ public void addPlatformMetric(final DefaultMetrics platformMetrics) { - throwArgNull(platformMetrics, "platformMetric"); + Objects.requireNonNull(platformMetrics, "platformMetric must not be null"); if (!platformMetrics.isPlatformMetrics()) { throw new IllegalArgumentException("Trying to add non-platform Metrics"); } @@ -130,7 +136,7 @@ public void addPlatformMetric(final DefaultMetrics platformMetrics) { * if {@code platformMetrics} is {@code null} or not platform-specific */ public void removePlatformMetric(final DefaultMetrics platformMetrics) { - throwArgNull(platformMetrics, "platformMetric"); + Objects.requireNonNull(platformMetrics, "platformMetric must not be null"); if (!platformMetrics.isPlatformMetrics()) { throw new IllegalArgumentException("Trying to remove non-platform Metrics"); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/AbstractMetricAdapter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/AbstractMetricAdapter.java index 065c57fb7c43..5009f17d5a09 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/AbstractMetricAdapter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/AbstractMetricAdapter.java @@ -16,8 +16,7 @@ package com.swirlds.common.metrics.platform.prometheus; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - +import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; public abstract class AbstractMetricAdapter implements MetricAdapter { @@ -25,8 +24,11 @@ public abstract class AbstractMetricAdapter implements MetricAdapter { protected final PrometheusEndpoint.AdapterType adapterType; private final AtomicInteger referenceCount = new AtomicInteger(); + /** + * @throws NullPointerException in case {@code adapterType} parameter is {@code null} + */ protected AbstractMetricAdapter(final PrometheusEndpoint.AdapterType adapterType) { - this.adapterType = throwArgNull(adapterType, "adapterType"); + this.adapterType = Objects.requireNonNull(adapterType, "adapterType must not be null"); } @Override diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapter.java index 3526a5541c57..92277331d715 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapter.java @@ -20,7 +20,6 @@ import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.GLOBAL; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.PLATFORM; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.NODE_LABEL; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import static java.lang.Boolean.TRUE; import com.swirlds.common.metrics.Metric; @@ -30,6 +29,7 @@ import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; +import java.util.Objects; /** * Adapter that synchronizes a {@link Metric} with a single value of {@link Metric#getDataType() type} {@code boolean} @@ -50,12 +50,16 @@ public class BooleanAdapter extends AbstractMetricAdapter { * The {@link Metric} which value should be reported to Prometheus * @param adapterType * Scope of the {@link Metric}, either {@link AdapterType#GLOBAL} or {@link AdapterType#PLATFORM} - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code registry}
  • + *
  • {@code metric}
  • + *
*/ public BooleanAdapter(final CollectorRegistry registry, final Metric metric, final AdapterType adapterType) { super(adapterType); - throwArgNull(registry, "registry"); - throwArgNull(metric, "metric"); + Objects.requireNonNull(registry, "registry must not be null"); + Objects.requireNonNull(metric, "metric must not be null"); final Gauge.Builder builder = new Gauge.Builder() .subsystem(fix(metric.getCategory())) .name(fix(metric.getName())) @@ -71,12 +75,12 @@ public BooleanAdapter(final CollectorRegistry registry, final Metric metric, fin */ @Override public void update(final Snapshot snapshot, final NodeId nodeId) { - throwArgNull(snapshot, "snapshot"); + Objects.requireNonNull(snapshot, "snapshot must not be null"); final double newValue = TRUE.equals(snapshot.getValue()) ? TRUE_VALUE : FALSE_VALUE; if (adapterType == GLOBAL) { gauge.set(newValue); } else { - throwArgNull(nodeId, "nodeId"); + Objects.requireNonNull(nodeId, "nodeId must not be null"); final Gauge.Child child = gauge.labels(nodeId.toString()); child.set(newValue); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapter.java index 7863603783b8..80921af4a63d 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapter.java @@ -20,7 +20,6 @@ import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.GLOBAL; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.PLATFORM; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.NODE_LABEL; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import com.swirlds.common.metrics.Metric; import com.swirlds.common.metrics.platform.Snapshot; @@ -29,6 +28,7 @@ import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; +import java.util.Objects; /** * Adapter that synchronizes a {@link com.swirlds.common.metrics.Counter} @@ -48,12 +48,16 @@ public class CounterAdapter extends AbstractMetricAdapter { * @param adapterType * Scope of the {@link com.swirlds.common.metrics.Counter}, * either {@link AdapterType#GLOBAL} or {@link AdapterType#PLATFORM} - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code registry}
  • + *
  • {@code metric}
  • + *
*/ public CounterAdapter(final CollectorRegistry registry, final Metric metric, final AdapterType adapterType) { super(adapterType); - throwArgNull(registry, "registry"); - throwArgNull(metric, "metric"); + Objects.requireNonNull(registry, "registry must not be null"); + Objects.requireNonNull(metric, "metric must not be null"); final Counter.Builder builder = new Counter.Builder() .subsystem(fix(metric.getCategory())) .name(fix(metric.getName())) @@ -71,13 +75,13 @@ public CounterAdapter(final CollectorRegistry registry, final Metric metric, fin */ @Override public void update(final Snapshot snapshot, final NodeId nodeId) { - throwArgNull(snapshot, "snapshot"); + Objects.requireNonNull(snapshot, "snapshot must not be null"); final double newValue = ((Number) snapshot.getValue()).doubleValue(); if (adapterType == GLOBAL) { final double oldValue = counter.get(); counter.inc(newValue - oldValue); } else { - throwArgNull(nodeId, "nodeId"); + Objects.requireNonNull(nodeId, "nodeId must not be null"); final Counter.Child child = counter.labels(nodeId.toString()); final double oldValue = child.get(); child.inc(newValue - oldValue); diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapter.java index eebf9f4a60db..5d804c5ec992 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapter.java @@ -21,7 +21,6 @@ import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.PLATFORM; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.NODE_LABEL; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.TYPE_LABEL; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import com.swirlds.common.metrics.Metric; import com.swirlds.common.metrics.platform.Snapshot; @@ -30,6 +29,7 @@ import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; +import java.util.Objects; /** * Adapter that synchronizes {@link com.swirlds.common.metrics.RunningAverageMetric} and @@ -48,12 +48,16 @@ public class DistributionAdapter extends AbstractMetricAdapter { * The {@link Metric} which value should be reported to Prometheus * @param adapterType * Scope of the {@link Metric}, either {@link AdapterType#GLOBAL} or {@link AdapterType#PLATFORM} - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code registry}
  • + *
  • {@code metric}
  • + *
*/ public DistributionAdapter(final CollectorRegistry registry, final Metric metric, final AdapterType adapterType) { super(adapterType); - throwArgNull(registry, "registry"); - throwArgNull(metric, "metric"); + Objects.requireNonNull(registry, "registry must not be null"); + Objects.requireNonNull(metric, "metric must not be null"); final Gauge.Builder builder = new Gauge.Builder() .subsystem(fix(metric.getCategory())) .name(fix(metric.getName())) @@ -72,9 +76,9 @@ public DistributionAdapter(final CollectorRegistry registry, final Metric metric */ @Override public void update(final Snapshot snapshot, final NodeId nodeId) { - throwArgNull(snapshot, "snapshot"); + Objects.requireNonNull(snapshot, "snapshot must not be null"); if (adapterType != GLOBAL) { - throwArgNull(nodeId, "nodeId"); + Objects.requireNonNull(nodeId, "nodeId must not be null"); } for (final Snapshot.SnapshotEntry entry : snapshot.entries()) { final String valueType = diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NameConverter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NameConverter.java index 67b7e5a7e7fa..8cbecdad4fa5 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NameConverter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NameConverter.java @@ -16,7 +16,7 @@ package com.swirlds.common.metrics.platform.prometheus; -import com.swirlds.common.utility.CommonUtils; +import java.util.Objects; /** * Converter that ensures a label satisfies Prometheus requirements @@ -32,10 +32,10 @@ private NameConverter() {} * @param label * The input-{@link String} * @return The resulting {@link String} - * @throws IllegalArgumentException if {@code label} is {@code null} + * @throws NullPointerException in case {@code label} parameter is {@code null} */ public static String fix(final String label) { - CommonUtils.throwArgNull(label, "label"); + Objects.requireNonNull(label, "label must not be null"); return label.strip() .replace('.', ':') .replace('-', '_') diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapter.java index 554b0be3bf8c..c0dc8472eb36 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapter.java @@ -20,7 +20,6 @@ import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.GLOBAL; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.PLATFORM; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.NODE_LABEL; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import com.swirlds.common.metrics.Metric; import com.swirlds.common.metrics.platform.Snapshot; @@ -29,6 +28,7 @@ import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; +import java.util.Objects; /** * Adapter that synchronizes a {@link Metric} with a single numeric value @@ -47,12 +47,16 @@ public class NumberAdapter extends AbstractMetricAdapter { * The {@link Metric} which value should be reported to Prometheus * @param adapterType * Scope of the {@link Metric}, either {@link AdapterType#GLOBAL} or {@link AdapterType#PLATFORM} - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code registry}
  • + *
  • {@code metric}
  • + *
*/ public NumberAdapter(final CollectorRegistry registry, final Metric metric, final AdapterType adapterType) { super(adapterType); - throwArgNull(registry, "registry"); - throwArgNull(metric, "metric"); + Objects.requireNonNull(registry, "registry must not be null"); + Objects.requireNonNull(metric, "metric must not be null"); final Gauge.Builder builder = new Gauge.Builder() .subsystem(fix(metric.getCategory())) .name(fix(metric.getName())) @@ -69,12 +73,12 @@ public NumberAdapter(final CollectorRegistry registry, final Metric metric, fina */ @Override public void update(final Snapshot snapshot, final NodeId nodeId) { - throwArgNull(snapshot, "snapshot"); + Objects.requireNonNull(snapshot, "snapshot must not be null"); final double newValue = ((Number) snapshot.getValue()).doubleValue(); if (adapterType == GLOBAL) { gauge.set(newValue); } else { - throwArgNull(nodeId, "nodeId"); + Objects.requireNonNull(nodeId, "nodeId must not be null"); final Gauge.Child child = gauge.labels(nodeId.toString()); child.set(newValue); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpoint.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpoint.java index 6b9386bcde6a..6f37b6695e54 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpoint.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpoint.java @@ -20,7 +20,6 @@ import static com.swirlds.common.metrics.platform.DefaultMetrics.calculateMetricKey; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.GLOBAL; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.PLATFORM; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STARTUP; @@ -44,6 +43,7 @@ import io.prometheus.client.exporter.HTTPServer; import java.io.IOException; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -93,8 +93,8 @@ public PrometheusEndpoint(final HttpServer httpServer) throws IOException { */ @Deprecated(forRemoval = true) public PrometheusEndpoint(final HttpServer httpServer, final CollectorRegistry registry) throws IOException { - throwArgNull(httpServer, "httpServer"); - this.registry = throwArgNull(registry, "registry"); + Objects.requireNonNull(httpServer, "httpServer must not be null"); + this.registry = Objects.requireNonNull(registry, "registry must not be null"); logger.info( STARTUP.getMarker(), @@ -113,9 +113,10 @@ public PrometheusEndpoint(final HttpServer httpServer, final CollectorRegistry r * * @param notification * the {@link MetricsEvent} + * @throws NullPointerException in case {@code notification} parameter is {@code null} */ public void handleMetricsChange(final MetricsEvent notification) { - throwArgNull(notification, "notification"); + Objects.requireNonNull(notification, "notification must not be null"); final Metric metric = notification.metric(); final String metricKey = calculateMetricKey(metric); @@ -145,7 +146,7 @@ public void handleMetricsChange(final MetricsEvent notification) { * the {@link SnapshotEvent} */ public void handleSnapshots(final SnapshotEvent notification) { - throwArgNull(notification, "notification"); + Objects.requireNonNull(notification, "notification must not be null"); for (final Snapshot snapshot : notification.snapshots()) { final String metricKey = calculateMetricKey(snapshot.metric()); diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/StringAdapter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/StringAdapter.java index 9a092ed0c632..69da8ae34df4 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/StringAdapter.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/platform/prometheus/StringAdapter.java @@ -20,7 +20,6 @@ import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.GLOBAL; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.AdapterType.PLATFORM; import static com.swirlds.common.metrics.platform.prometheus.PrometheusEndpoint.NODE_LABEL; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; import com.swirlds.common.metrics.Metric; import com.swirlds.common.metrics.platform.Snapshot; @@ -48,12 +47,16 @@ public class StringAdapter extends AbstractMetricAdapter { * The {@link Metric} which value should be reported to Prometheus * @param adapterType * Scope of the {@link Metric}, either {@link AdapterType#GLOBAL} or {@link AdapterType#PLATFORM} - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code registry}
  • + *
  • {@code metric}
  • + *
*/ public StringAdapter(final CollectorRegistry registry, final Metric metric, final AdapterType adapterType) { super(adapterType); - throwArgNull(registry, "registry"); - throwArgNull(metric, "metric"); + Objects.requireNonNull(registry, "registry must not be null"); + Objects.requireNonNull(metric, "metric must not be null"); final Info.Builder builder = new Info.Builder() .subsystem(fix(metric.getCategory())) .name(fix(metric.getName())) @@ -69,12 +72,12 @@ public StringAdapter(final CollectorRegistry registry, final Metric metric, fina */ @Override public void update(final Snapshot snapshot, final NodeId nodeId) { - throwArgNull(snapshot, "snapshot"); + Objects.requireNonNull(snapshot, "snapshot must not be null"); final String newValue = Objects.toString(snapshot.getValue()); if (adapterType == GLOBAL) { info.info("value", newValue); } else { - throwArgNull(nodeId, "nodeId"); + Objects.requireNonNull(nodeId, "nodeId must not be null"); final Info.Child child = info.labels(nodeId.toString()); child.info("value", newValue); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/statistics/StatsSpeedometer.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/statistics/StatsSpeedometer.java index 1a3b6904df00..2fc58ba25709 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/statistics/StatsSpeedometer.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/metrics/statistics/StatsSpeedometer.java @@ -16,10 +16,9 @@ package com.swirlds.common.metrics.statistics; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import com.swirlds.base.time.Time; import com.swirlds.common.metrics.statistics.internal.StatsBuffer; +import java.util.Objects; /** * This class measures how many times per second the cycle() method is called. It is recalculated every @@ -134,11 +133,12 @@ public StatsSpeedometer(final double halfLife, final Time time) { * half of the exponential weighting comes from the last halfLife seconds * @param time * the {@code Clock} implementation, typically a mock when testing + * @throws NullPointerException in case {@code time} parameter is {@code null} * @deprecated this constructor should only be used internally and will become non-public at some point */ @Deprecated(forRemoval = true) public StatsSpeedometer(final double halfLife, final boolean saveHistory, final Time time) { - this.time = throwArgNull(time, "time"); + this.time = Objects.requireNonNull(time, "time must not be null"); final long now = time.nanoTime(); this.startTime = now; this.lastTime = now; diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/CommonUtils.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/CommonUtils.java index d3426cdf54e9..27a2a410749b 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/CommonUtils.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/utility/CommonUtils.java @@ -30,7 +30,6 @@ import java.util.Collection; import java.util.List; import java.util.Locale; -import java.util.Objects; import java.util.function.Consumer; import javax.sound.midi.MidiChannel; import javax.sound.midi.MidiSystem; @@ -284,38 +283,6 @@ private static int toDigit(final char ch, final int index) throws IllegalArgumen return digit; } - /** - * Throw an {@link IllegalArgumentException} if the supplied argument is {@code null}. - * - * @param arg the argument checked - * @param argName the name of the argument - * @deprecated use {@link java.util.Objects#requireNonNull(Object, String)} instead - */ - @Deprecated(forRemoval = true) - public static T throwArgNull(final T arg, final String argName) { - if (arg == null) { - throw new IllegalArgumentException(String.format("The supplied argument '%s' cannot be null!", argName)); - } - return arg; - } - - /** - * Throw an {@link IllegalArgumentException} if the supplied {@code String} is blank. - * - * @param arg the argument checked - * @param argName the name of the argument - * @see String#isBlank() - * @deprecated use {@link com.swirlds.base.ArgumentUtils#throwArgBlank(String, String)} instead - */ - @Deprecated(forRemoval = true) - public static String throwArgBlank(final String arg, final String argName) { - Objects.requireNonNull(arg, argName); - if (arg.isBlank()) { - throw new IllegalArgumentException(String.format("The supplied argument '%s' cannot be blank!", argName)); - } - return arg; - } - /** * Throws an exception if the value is outside of the specified range * diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/DefaultMetricsTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/DefaultMetricsTest.java index bbd02c27179a..acebe03170b0 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/DefaultMetricsTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/DefaultMetricsTest.java @@ -172,11 +172,11 @@ void testConstructorWithNullParameter() { assertThatCode(() -> new DefaultMetrics(null, registry, executor, factory, metricsConfig)) .doesNotThrowAnyException(); assertThatThrownBy(() -> new DefaultMetrics(NODE_ID, null, executor, factory, metricsConfig)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new DefaultMetrics(NODE_ID, registry, null, factory, metricsConfig)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new DefaultMetrics(NODE_ID, registry, executor, null, metricsConfig)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); } @Test @@ -199,8 +199,8 @@ void testGetSingleMetricThatDoesNotExists() { @Test void testGetSingleMetricWithNullParameter() { - assertThatThrownBy(() -> metrics.getMetric(null, NAME_1)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> metrics.getMetric(CATEGORY_1, null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.getMetric(null, NAME_1)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> metrics.getMetric(CATEGORY_1, null)).isInstanceOf(NullPointerException.class); } @Test @@ -249,7 +249,7 @@ void testGetMetricsOfCategoryAfterMetricWasRemoved() { @Test void testGetMetricsOfCategoryWithNullParameter() { - assertThatThrownBy(() -> metrics.findMetricsByCategory(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.findMetricsByCategory(null)).isInstanceOf(NullPointerException.class); } @Test @@ -420,7 +420,7 @@ void testCreateDuplicateMetricWithWrongType() { @Test void testCreateMetricWithNullParameter() { // then - assertThatThrownBy(() -> metrics.getOrCreate(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.getOrCreate(null)).isInstanceOf(NullPointerException.class); verify(subscriber, never()).accept(any()); } @@ -463,8 +463,8 @@ void testRemoveNonExistingByNameAndCategory() { @Test void testRemoveByNameAndCategoryWithNullParameter() { - assertThatThrownBy(() -> metrics.remove(null, NAME_1)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> metrics.remove(CATEGORY_1, null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.remove(null, NAME_1)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> metrics.remove(CATEGORY_1, null)).isInstanceOf(NullPointerException.class); verify(subscriber, never()).accept(any()); } @@ -513,7 +513,7 @@ void testRemoveNonExistingByMetric(@Mock final Counter counter) { @Test void testRemoveByMetricWithNullParameter() { - assertThatThrownBy(() -> metrics.remove((Metric) null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.remove((Metric) null)).isInstanceOf(NullPointerException.class); verify(subscriber, never()).accept(any()); } @@ -554,8 +554,7 @@ void testRemoveNonExistingByConfig() { @Test void testRemoveByConfigWithNullParameter() { - assertThatThrownBy(() -> metrics.remove((MetricConfig) null)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.remove((MetricConfig) null)).isInstanceOf(NullPointerException.class); verify(subscriber, never()).accept(any()); } @@ -593,7 +592,7 @@ void testDisabledUpdater(@Mock final Runnable updater) { @Test void testUpdaterWithNullParameter() { - assertThatThrownBy(() -> metrics.addUpdater(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> metrics.addUpdater(null)).isInstanceOf(NullPointerException.class); } @Test diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/MetricsEventBusTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/MetricsEventBusTest.java index 2c694f8c79bd..2fd158eb2e12 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/MetricsEventBusTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/MetricsEventBusTest.java @@ -54,7 +54,7 @@ void setup() { @Test void testConstructorWithNull() { - assertThatThrownBy(() -> new MetricsEventBus<>(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new MetricsEventBus<>(null)).isInstanceOf(NullPointerException.class); } @Test @@ -90,9 +90,8 @@ void testSubscribeWithIllegalArguments() { final MetricsEventBus eventBus = new MetricsEventBus<>(executor); // then - assertThatThrownBy(() -> eventBus.subscribe(null, () -> Stream.of(1))) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> eventBus.subscribe(it -> {}, null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> eventBus.subscribe(null, () -> Stream.of(1))).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> eventBus.subscribe(it -> {}, null)).isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> eventBus.subscribe(it -> {}, () -> null)).isInstanceOf(NullPointerException.class); } @@ -125,6 +124,6 @@ void testSubmitWithIllegalArguments() { final MetricsEventBus eventBus = new MetricsEventBus<>(executor); // then - assertThatThrownBy(() -> eventBus.submit(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> eventBus.submit(null)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapterTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapterTest.java index a113299c783e..3295f840320d 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapterTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/BooleanAdapterTest.java @@ -108,15 +108,11 @@ void testConstructorWithNullParameters() { new FunctionGauge.Config<>(CATEGORY, NAME, Boolean.class, () -> true).withDescription(DESCRIPTION)); // then - assertThatThrownBy(() -> new BooleanAdapter(null, metric, GLOBAL)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new BooleanAdapter(null, metric, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new BooleanAdapter(registry, null, GLOBAL)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new BooleanAdapter(registry, null, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new BooleanAdapter(registry, metric, null)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new BooleanAdapter(null, metric, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new BooleanAdapter(null, metric, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new BooleanAdapter(registry, null, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new BooleanAdapter(registry, null, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new BooleanAdapter(registry, metric, null)).isInstanceOf(NullPointerException.class); } @Test @@ -162,7 +158,7 @@ void testUpdateWithNullParameters() { final NodeId nodeId = new NodeId(1L); // then - assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapterTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapterTest.java index 4c02cdee06ae..e5ac0b9448c0 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapterTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/CounterAdapterTest.java @@ -107,15 +107,11 @@ void testConstructorWithNullParameters() { final Metric metric = new DefaultCounter(new Counter.Config(CATEGORY, NAME).withDescription(DESCRIPTION)); // then - assertThatThrownBy(() -> new CounterAdapter(null, metric, GLOBAL)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new CounterAdapter(null, metric, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new CounterAdapter(registry, null, GLOBAL)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new CounterAdapter(registry, null, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new CounterAdapter(registry, metric, null)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new CounterAdapter(null, metric, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new CounterAdapter(null, metric, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new CounterAdapter(registry, null, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new CounterAdapter(registry, null, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new CounterAdapter(registry, metric, null)).isInstanceOf(NullPointerException.class); } @Test @@ -158,7 +154,7 @@ void testUpdateWithNullParameters() { final NodeId nodeId = new NodeId(1L); // then - assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapterTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapterTest.java index b467c6d701ea..c0dfd7fe691d 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapterTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/DistributionAdapterTest.java @@ -111,15 +111,15 @@ void testConstructorWithNullParameters() { // then assertThatThrownBy(() -> new DistributionAdapter(null, metric, GLOBAL)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new DistributionAdapter(null, metric, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new DistributionAdapter(registry, null, GLOBAL)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new DistributionAdapter(registry, null, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new DistributionAdapter(registry, metric, null)) - .isInstanceOf(IllegalArgumentException.class); + .isInstanceOf(NullPointerException.class); } @Test @@ -178,7 +178,7 @@ void testUpdateWithNullParameters() { final NodeId nodeId = new NodeId(1L); // then - assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NameConvertTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NameConvertTest.java index ea63f5ca32a9..936f7846cfd8 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NameConvertTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NameConvertTest.java @@ -28,6 +28,6 @@ void testNameConverter() { assertThat(NameConverter.fix("Hello_World:42")).isEqualTo("Hello_World:42"); assertThat(NameConverter.fix("")).isEmpty(); assertThat(NameConverter.fix(".- /%")).isEqualTo(":___per_Percent"); - assertThatThrownBy(() -> NameConverter.fix(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> NameConverter.fix(null)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapterTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapterTest.java index eb3863bd2265..5c02e59cc70e 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapterTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/NumberAdapterTest.java @@ -110,15 +110,11 @@ void testConstructorWithNullParameters() { new DefaultIntegerGauge(new IntegerGauge.Config(CATEGORY, NAME).withDescription(DESCRIPTION)); // then - assertThatThrownBy(() -> new NumberAdapter(null, metric, GLOBAL)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new NumberAdapter(null, metric, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new NumberAdapter(registry, null, GLOBAL)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new NumberAdapter(registry, null, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new NumberAdapter(registry, metric, null)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new NumberAdapter(null, metric, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new NumberAdapter(null, metric, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new NumberAdapter(registry, null, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new NumberAdapter(registry, null, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new NumberAdapter(registry, metric, null)).isInstanceOf(NullPointerException.class); } @Test @@ -161,7 +157,7 @@ void testUpdateWithNullParameters() { final NodeId nodeId = new NodeId(1L); // then - assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpointTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpointTest.java index de62e390fe04..ec56c6fc9031 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpointTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/PrometheusEndpointTest.java @@ -109,9 +109,9 @@ void testMethodsWithIllegalParameters() throws IOException { final PrometheusEndpoint endpoint = new PrometheusEndpoint(httpServer); // then - assertThatThrownBy(() -> new PrometheusEndpoint(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> endpoint.handleMetricsChange(null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> endpoint.handleSnapshots(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new PrometheusEndpoint(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> endpoint.handleMetricsChange(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> endpoint.handleSnapshots(null)).isInstanceOf(NullPointerException.class); } @Test diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/StringAdapterTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/StringAdapterTest.java index 97ac8d7d6387..62488e2da4d7 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/StringAdapterTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/metrics/platform/prometheus/StringAdapterTest.java @@ -112,15 +112,11 @@ void testConstructorWithNullParameters() { .withDescription(DESCRIPTION)); // then - assertThatThrownBy(() -> new StringAdapter(null, metric, GLOBAL)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new StringAdapter(null, metric, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new StringAdapter(registry, null, GLOBAL)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new StringAdapter(registry, null, PLATFORM)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> new StringAdapter(registry, metric, null)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> new StringAdapter(null, metric, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new StringAdapter(null, metric, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new StringAdapter(registry, null, GLOBAL)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new StringAdapter(registry, null, PLATFORM)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new StringAdapter(registry, metric, null)).isInstanceOf(NullPointerException.class); } @Test @@ -165,7 +161,7 @@ void testUpdateWithNullParameters() { final NodeId nodeId = new NodeId(1L); // then - assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> adapter.update(null, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> adapter.update(null, nodeId)).isInstanceOf(NullPointerException.class); } } diff --git a/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueueStatistics.java b/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueueStatistics.java index ec5d819d9766..a225aafd2a8d 100644 --- a/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueueStatistics.java +++ b/platform-sdk/swirlds-fcqueue/src/main/java/com/swirlds/fcqueue/FCQueueStatistics.java @@ -20,7 +20,7 @@ import com.swirlds.common.metrics.Metrics; import com.swirlds.common.metrics.RunningAverageMetric; -import com.swirlds.common.utility.CommonUtils; +import java.util.Objects; /** * Singleton factory for loading and registering {@link FCQueue} statistics. This is the primary entry point for all @@ -77,9 +77,10 @@ private FCQueueStatistics() {} * * @param metrics * the metrics-system + * @throws NullPointerException in case {@code metrics} parameter is {@code null} */ public static void register(final Metrics metrics) { - CommonUtils.throwArgNull(metrics, "metrics"); + Objects.requireNonNull(metrics, "metrics must not be null"); fcqAddExecutionMicros = metrics.getOrCreate(FCQ_ADD_EXECUTION_MICROS_CONFIG); fcqRemoveExecutionMicros = metrics.getOrCreate(FCQ_REMOVE_EXECUTION_MICROS_CONFIG); fcqHashExecutionMicros = metrics.getOrCreate(FCQ_HASH_EXECUTION_MICROS_CONFIG); diff --git a/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueStatisticsTest.java b/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueStatisticsTest.java index 9874e78c6a2d..ab9dad6a50af 100644 --- a/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueStatisticsTest.java +++ b/platform-sdk/swirlds-fcqueue/src/test/java/com/swirlds/fcqueue/FCQueueStatisticsTest.java @@ -71,6 +71,6 @@ void test() { @Test void testRegisterWithNullParameter() { - assertThrows(IllegalArgumentException.class, () -> FCQueueStatistics.register(null)); + assertThrows(NullPointerException.class, () -> FCQueueStatistics.register(null)); } } diff --git a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatistics.java b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatistics.java index ccf6affede0d..eca0d2308c42 100644 --- a/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatistics.java +++ b/platform-sdk/swirlds-jasperdb/src/main/java/com/swirlds/merkledb/MerkleDbStatistics.java @@ -23,10 +23,10 @@ import com.swirlds.common.metrics.IntegerGauge; import com.swirlds.common.metrics.LongAccumulator; import com.swirlds.common.metrics.Metrics; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.merkledb.config.MerkleDbConfig; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Encapsulates statistics for an instance of a {@link MerkleDbDataSource}. @@ -127,10 +127,10 @@ public class MerkleDbStatistics { * Create a new statistics object for a MerkleDb instances. * * @param label the label for the virtual map - * @throws IllegalArgumentException if {@code label} is {@code null} + * @throws NullPointerException in case {@code label} parameter is {@code null} */ public MerkleDbStatistics(final String label) { - this.label = CommonUtils.throwArgNull(label, "label"); + this.label = Objects.requireNonNull(label, "label must not be null"); hashesStoreCompactionTimeMsList = new ArrayList<>(); hashesStoreCompactionSavedSpaceMbList = new ArrayList<>(); hashesStoreFileSizeByLevelMbList = new ArrayList<>(); @@ -168,10 +168,10 @@ private static DoubleAccumulator buildDoubleAccumulator( * * @param metrics * reference to the metrics system - * @throws IllegalArgumentException if {@code metrics} is {@code null} + * @throws NullPointerException if {@code metrics} is {@code null} */ public void registerMetrics(final Metrics metrics) { - CommonUtils.throwArgNull(metrics, "metrics"); + Objects.requireNonNull(metrics, "metrics must not be null"); // Queries per second hashReads = buildLongAccumulator( diff --git a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbStatisticsTest.java b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbStatisticsTest.java index c2ff18bf45f8..e0227a816188 100644 --- a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbStatisticsTest.java +++ b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/MerkleDbStatisticsTest.java @@ -106,7 +106,7 @@ private void verifyMetricsDoesntThrow(MerkleDbStatistics statistics) { @Test void testConstructorWithNullParameter() { - assertThrows(IllegalArgumentException.class, () -> new MerkleDbStatistics(null)); + assertThrows(NullPointerException.class, () -> new MerkleDbStatistics(null)); } @Test @@ -115,7 +115,7 @@ void testRegisterWithNullParameter() { final MerkleDbStatistics statistics = new MerkleDbStatistics(LABEL); // then - assertThrows(IllegalArgumentException.class, () -> statistics.registerMetrics(null)); + assertThrows(NullPointerException.class, () -> statistics.registerMetrics(null)); } private Metric getMetric(final String section, final String suffix) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigProperties.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigProperties.java index 466a687fa295..007e4c140b4a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigProperties.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigProperties.java @@ -16,7 +16,6 @@ package com.swirlds.platform.config.legacy; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; @@ -61,12 +60,18 @@ public AddressBook getAddressBook() { return addressBook.copy(); } + /** + * @throws NullPointerException in case {@code appConfig} parameter is {@code null} + */ public void setAppConfig(final JarAppConfig appConfig) { - this.appConfig = CommonUtils.throwArgNull(appConfig, "appConfig"); + this.appConfig = Objects.requireNonNull(appConfig, "appConfig must not be null"); } + /** + * @throws NullPointerException in case {@code swirldName} parameter is {@code null} + */ public void setSwirldName(final String swirldName) { - this.swirldName = CommonUtils.throwArgNull(swirldName, "swirldName"); + this.swirldName = Objects.requireNonNull(swirldName, "swirldName must not be null"); } public Optional swirldName() { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoader.java index c6d2c9e5fe0c..7c5154b0c2a9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoader.java @@ -68,8 +68,12 @@ public final class LegacyConfigPropertiesLoader { private LegacyConfigPropertiesLoader() {} + /** + * @throws NullPointerException in case {@code configPath} parameter is {@code null} + * @throws ConfigurationException in case {@code configPath} cannot be found in the system + */ public static LegacyConfigProperties loadConfigFile(@NonNull final Path configPath) throws ConfigurationException { - CommonUtils.throwArgNull(configPath, "configPath"); + Objects.requireNonNull(configPath, "configPath must not be null"); // Load config.txt file, parse application jar file name, main class name, address book, and parameters if (!Files.exists(configPath)) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/PlatformSigner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/PlatformSigner.java index c7b9845d93cc..0e635904ec21 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/PlatformSigner.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/crypto/PlatformSigner.java @@ -21,13 +21,13 @@ import com.swirlds.common.crypto.SignatureType; import com.swirlds.common.stream.HashSigner; import com.swirlds.common.stream.Signer; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.system.PlatformConstructionException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Signature; import java.security.SignatureException; +import java.util.Objects; /** * An instance capable of signing data with the platforms private signing key. This class is not thread safe. @@ -63,7 +63,7 @@ public com.swirlds.common.crypto.Signature sign(final byte[] data) { @Override public com.swirlds.common.crypto.Signature sign(final Hash hash) { - CommonUtils.throwArgNull(hash, "hash"); + Objects.requireNonNull(hash, "hash must not be null"); return sign(hash.getValue()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/DispatchBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/DispatchBuilder.java index b6e3e86039a0..45ebc73970ec 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/DispatchBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/DispatchBuilder.java @@ -16,8 +16,6 @@ package com.swirlds.platform.dispatch; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import com.swirlds.base.state.MutabilityException; import com.swirlds.base.state.Mutable; import com.swirlds.base.state.Startable; @@ -45,6 +43,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Manages the construction of dispatch methods. Useful for linking together various platform @@ -71,9 +70,10 @@ public class DispatchBuilder implements Mutable, Startable { * * @param configuration * dispatch configuration + * @throws NullPointerException in case {@code configuration} parameter is {@code null} */ public DispatchBuilder(final DispatchConfiguration configuration) { - throwArgNull(configuration, "configuration"); + Objects.requireNonNull(configuration, "configuration must not be null"); if (configuration.flowchartEnabled()) { flowchart = new DispatchFlowchart(configuration); } else { @@ -152,6 +152,12 @@ DispatchBuilder registerObserver( * @return this object * @throws MutabilityException * if called after {@link #start()} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code triggerClass}
  • + *
  • {@code observer}
  • + *
+ * */ public , TRIGGER_CLASS extends BASE_INTERFACE> DispatchBuilder registerObserver( @@ -161,9 +167,8 @@ DispatchBuilder registerObserver( final String comment) { throwIfImmutable("observer can only be registered while this object is mutable"); - throwArgNull(triggerClass, "triggerClass"); - throwArgNull(triggerClass, "dispatchType"); - throwArgNull(observer, "observer"); + Objects.requireNonNull(triggerClass, "triggerClass must not be null"); + Objects.requireNonNull(observer, "observer must not be null"); if (flowchart != null && isMutable()) { flowchart.registerObserver(owner, triggerClass, comment); @@ -185,7 +190,7 @@ DispatchBuilder registerObserver( */ public DispatchBuilder registerObservers(final Object object) { throwIfImmutable("observers can only be registered while this object is mutable"); - throwArgNull(object, "object"); + Objects.requireNonNull(object, "object must not be null"); for (final Method method : object.getClass().getDeclaredMethods()) { @@ -301,8 +306,8 @@ BASE_INTERFACE getDispatcher(final Object owner, final Class tr BASE_INTERFACE getDispatcher( final Object owner, final Class triggerClass, final String comment) { - throwArgNull(owner, "owner"); - throwArgNull(triggerClass, "dispatchType"); + Objects.requireNonNull(owner, "owner must not be null"); + Objects.requireNonNull(triggerClass, "dispatchType must not be null"); if (flowchart != null && isMutable()) { flowchart.registerDispatcher(owner, triggerClass, comment); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/flowchart/DispatchFlowchart.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/flowchart/DispatchFlowchart.java index 5c074f4af44d..f4249eddd48f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/flowchart/DispatchFlowchart.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/dispatch/flowchart/DispatchFlowchart.java @@ -16,8 +16,6 @@ package com.swirlds.platform.dispatch.flowchart; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import com.swirlds.platform.dispatch.DispatchConfiguration; import java.io.IOException; import java.nio.file.Files; @@ -26,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -135,6 +134,7 @@ public void registerObserver(final Object owner, final Class triggerClass, fi * an optional comment on the linkage * @param map * a map containing linkages for observers or dispatchers + * @throws NullPointerException in case {@code owner} parameter is {@code null} */ private void registerTriggerLinkage( final Object owner, @@ -142,7 +142,7 @@ private void registerTriggerLinkage( final String comment, final Map, Set> map) { - throwArgNull(owner, "owner"); + Objects.requireNonNull(owner, "owner must not be null"); final Class ownerClass; if (owner instanceof final Class cls) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/AddedEventMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/AddedEventMetrics.java index 4cd44a1130de..d4112ef68556 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/AddedEventMetrics.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/AddedEventMetrics.java @@ -31,12 +31,12 @@ import com.swirlds.common.metrics.RunningAverageMetric; import com.swirlds.common.metrics.SpeedometerMetric; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.observers.EventAddedObserver; import com.swirlds.platform.stats.AverageStat; import com.swirlds.platform.system.transaction.ConsensusTransaction; import java.time.temporal.ChronoUnit; +import java.util.Objects; /** * An {@link EventAddedObserver} that maintains all metrics which need to be updated on a new event @@ -126,11 +126,16 @@ public class AddedEventMetrics implements EventAddedObserver { * the {@link NodeId} of this node * @param metrics * a reference to the metrics-system - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code selfId}
  • + *
  • {@code metrics}
  • + *
+ * */ public AddedEventMetrics(final NodeId selfId, final Metrics metrics) { - this.selfId = CommonUtils.throwArgNull(selfId, "selfId"); - CommonUtils.throwArgNull(metrics, "metrics"); + this.selfId = Objects.requireNonNull(selfId, "selfId must not be null"); + Objects.requireNonNull(metrics, "metrics must not be null"); eventsCreatedPerSecond = metrics.getOrCreate(EVENTS_CREATED_PER_SECOND_CONFIG); averageOtherParentAgeDiff = new AverageStat( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusHandlingMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusHandlingMetrics.java index 0734f85942af..cb1c83f813a9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusHandlingMetrics.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusHandlingMetrics.java @@ -23,7 +23,6 @@ import com.swirlds.base.utility.Pair; import com.swirlds.common.metrics.LongGauge; import com.swirlds.common.metrics.Metrics; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.stats.AverageAndMax; @@ -33,6 +32,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Objects; /** * Provides access to statistics relevant to {@link ConsensusRoundHandler} @@ -66,11 +66,10 @@ public class ConsensusHandlingMetrics { * @param metrics * a reference to the metrics-system * @param time provides wall clock time - * @throws IllegalArgumentException - * if {@code metrics} is {@code null} + * @throws NullPointerException in case {@code metrics} parameter is {@code null} */ public ConsensusHandlingMetrics(final Metrics metrics, final Time time) { - CommonUtils.throwArgNull(metrics, "metrics"); + Objects.requireNonNull(metrics, "metrics must not be null"); this.time = time; consensusCycleTiming = new CycleTimingStat( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusMetricsImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusMetricsImpl.java index 13e42d79fed9..005eb63e243d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusMetricsImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/ConsensusMetricsImpl.java @@ -29,7 +29,6 @@ import com.swirlds.common.metrics.RunningAverageMetric; import com.swirlds.common.metrics.SpeedometerMetric; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.stats.AverageAndMax; import java.time.Instant; @@ -128,11 +127,15 @@ public class ConsensusMetricsImpl implements ConsensusMetrics { * * @param selfId the {@link NodeId} of this node * @param metrics a reference to the metrics-system - * @throws IllegalArgumentException if one of the parameters is {@code null} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code selfId}
  • + *
  • {@code metrics}
  • + *
*/ public ConsensusMetricsImpl(final NodeId selfId, final Metrics metrics) { - this.selfId = CommonUtils.throwArgNull(selfId, "selfId"); - CommonUtils.throwArgNull(metrics, "metrics"); + this.selfId = Objects.requireNonNull(selfId, "selfId must not be null"); + Objects.requireNonNull(metrics, "metrics must not be null"); avgFirstEventInRoundReceivedTime = metrics.getOrCreate(AVG_FIRST_EVENT_IN_ROUND_RECEIVED_TIME_CONFIG); numCoinRounds = metrics.getOrCreate(NUM_COIN_ROUNDS_CONFIG); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/RuntimeMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/RuntimeMetrics.java index f9d163211956..b7282346b0a6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/RuntimeMetrics.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/RuntimeMetrics.java @@ -31,7 +31,6 @@ import com.swirlds.common.metrics.FunctionGauge; import com.swirlds.common.metrics.Metrics; import com.swirlds.common.metrics.RunningAverageMetric; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.common.utility.RuntimeObjectRegistry; import com.swirlds.platform.state.signed.SignedState; import java.io.File; @@ -42,6 +41,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -151,8 +151,11 @@ public static void setup(final Metrics metrics) { } } + /** + * @throws NullPointerException in case {@code metrics} parameter is {@code null} + */ private RuntimeMetrics(final Metrics metrics) { - CommonUtils.throwArgNull(metrics, "metrics"); + Objects.requireNonNull(metrics, "metrics must not be null"); this.osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); this.thbean = ManagementFactory.getThreadMXBean(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/communication/handshake/VersionCompareHandshake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/communication/handshake/VersionCompareHandshake.java index 482e152ce7cf..4cb61352bf55 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/communication/handshake/VersionCompareHandshake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/communication/handshake/VersionCompareHandshake.java @@ -19,12 +19,12 @@ import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import com.swirlds.common.io.SelfSerializable; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.network.Connection; import com.swirlds.platform.network.NetworkProtocolException; import com.swirlds.platform.network.protocol.ProtocolRunnable; import com.swirlds.platform.system.SoftwareVersion; import java.io.IOException; +import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -50,9 +50,10 @@ public VersionCompareHandshake(final SoftwareVersion version) { * @param throwOnMismatch * if set to true, the protocol will throw an exception on a version mismatch. if set to false, it will log an * error and continue + * @throws NullPointerException in case {@code version} parameter is {@code null} */ public VersionCompareHandshake(final SoftwareVersion version, final boolean throwOnMismatch) { - CommonUtils.throwArgNull(version, "version"); + Objects.requireNonNull(version, "version must not be null"); this.version = version; this.throwOnMismatch = throwOnMismatch; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java index 69f0b51d55ce..2d8bc63a12e1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/MultiFileRunningHashIterator.java @@ -18,9 +18,9 @@ import com.swirlds.common.io.IOIterator; import com.swirlds.common.stream.RunningHashCalculatorForStream; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.system.events.DetailedConsensusEvent; import java.io.IOException; +import java.util.Objects; /** * A wrapper around {@link EventStreamMultiFileIterator} @@ -33,10 +33,10 @@ public class MultiFileRunningHashIterator implements IOIterator(); runningHashCalculator.setRunningHash(iterator.getStartHash()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java index ade67501346a..375b9c7a9e02 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateHasher.java @@ -21,11 +21,11 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.platform.components.common.output.FatalErrorConsumer; import com.swirlds.platform.dispatch.triggers.flow.StateHashedTrigger; import java.time.Duration; import java.time.Instant; +import java.util.Objects; import java.util.concurrent.ExecutionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -61,13 +61,19 @@ public class SignedStateHasher { * @param signedStateMetrics the SignedStateMetrics instance to record time spent hashing. * @param stateHashedTrigger the StateHashedTrigger dispatcher to notify with hash. * @param fatalErrorConsumer the FatalErrorConsumer to consume any fatal errors during hashing. + * + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code stateHashedTrigger}
  • + *
  • {@code fatalErrorConsumer}
  • + *
*/ public SignedStateHasher( SignedStateMetrics signedStateMetrics, StateHashedTrigger stateHashedTrigger, FatalErrorConsumer fatalErrorConsumer) { - this.stateHashedTrigger = CommonUtils.throwArgNull(stateHashedTrigger, "stateHashedTrigger"); - this.fatalErrorConsumer = CommonUtils.throwArgNull(fatalErrorConsumer, "fatalErrorConsumer"); + this.stateHashedTrigger = Objects.requireNonNull(stateHashedTrigger, "stateHashedTrigger must not be null"); + this.fatalErrorConsumer = Objects.requireNonNull(fatalErrorConsumer, "fatalErrorConsumer must not be null"); this.signedStateMetrics = signedStateMetrics; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/stats/cycle/PercentageMetric.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/stats/cycle/PercentageMetric.java index 66f6bc92f37d..47bb69135725 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/stats/cycle/PercentageMetric.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/stats/cycle/PercentageMetric.java @@ -19,7 +19,7 @@ import com.swirlds.common.metrics.FloatFormats; import com.swirlds.common.metrics.IntegerPairAccumulator; import com.swirlds.common.metrics.Metrics; -import com.swirlds.common.utility.CommonUtils; +import java.util.Objects; import java.util.function.BiFunction; /** @@ -31,6 +31,9 @@ public class PercentageMetric { private final IntegerPairAccumulator container; + /** + * @throws NullPointerException in case {@code name} parameter is {@code null} + */ protected PercentageMetric( final Metrics metrics, final String category, @@ -38,7 +41,7 @@ protected PercentageMetric( final String description, final BiFunction resultFunction) { - CommonUtils.throwArgNull(name, "name"); + Objects.requireNonNull(name, "name must not be null"); container = metrics.getOrCreate( new IntegerPairAccumulator.Config<>(category, name + APPENDIX, Double.class, resultFunction) .withDescription(description) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/MetricsDocUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/MetricsDocUtils.java index e0b646c732c7..2cf34b45432d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/MetricsDocUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/MetricsDocUtils.java @@ -22,7 +22,6 @@ import com.swirlds.common.metrics.Metric; import com.swirlds.common.metrics.Metrics; import com.swirlds.common.metrics.config.MetricsConfig; -import com.swirlds.common.utility.CommonUtils; import com.swirlds.config.api.Configuration; import com.swirlds.platform.SwirldsPlatform; import java.io.File; @@ -32,6 +31,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -58,14 +58,21 @@ private MetricsDocUtils() {} * @param globalMetrics the global {@code Metrics} * @param platforms the collection of {@code SwirldsPlatform}s * @param configuration the {@code Configuration} + * @throws NullPointerException if any of the following parameters are {@code null}. + *
    + *
  • {@code globalMetrics}
  • + *
  • {@code platforms}
  • + *
  • {@code configuration}
  • + *
+ * */ public static void writeMetricsDocumentToFile( final Metrics globalMetrics, final Collection platforms, final Configuration configuration) { - CommonUtils.throwArgNull(globalMetrics, "globalMetrics"); - CommonUtils.throwArgNull(platforms, "platforms"); - CommonUtils.throwArgNull(configuration, "configuration"); + Objects.requireNonNull(globalMetrics, "globalMetrics must not be null"); + Objects.requireNonNull(platforms, "platforms must not be null"); + Objects.requireNonNull(configuration, "configuration must not be null"); // Combine global metrics and platform metrics without duplicates final Set combinedMetrics = new HashSet<>(globalMetrics.getAll()); @@ -92,9 +99,10 @@ public static void writeMetricsDocumentToFile( * * @param metrics a collection of {@code Metric}s * @return the metrics document contents + * @throws NullPointerException in case {@code metrics} parameter is {@code null} */ private static String generateMetricsDocContentsInTSV(final Collection metrics) { - CommonUtils.throwArgNull(metrics, "metrics"); + Objects.requireNonNull(metrics, "metrics must not be null"); final List sortedMetrics = metrics.stream() .sorted((x, y) -> x.getIdentifier().compareToIgnoreCase(y.getIdentifier())) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/SkippingIterator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/SkippingIterator.java index 7c0263561a86..e76fa344f80a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/SkippingIterator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/SkippingIterator.java @@ -16,11 +16,10 @@ package com.swirlds.platform.util.iterator; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; /** @@ -43,9 +42,10 @@ public class SkippingIterator implements Iterator { * the array to iterate over * @param skipIndices * the zero based indices to skip over + * @throws NullPointerException in case {@code array} parameter is {@code null} */ public SkippingIterator(final T[] array, final Set skipIndices) { - throwArgNull(array, "array must not be null"); + Objects.requireNonNull(array, "array must not be null"); this.array = array; this.skipIndices = skipIndices == null ? Collections.emptySet() : Set.copyOf(skipIndices); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/TypedIterator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/TypedIterator.java index 4f6be39f32ce..2481639fd40b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/TypedIterator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/iterator/TypedIterator.java @@ -16,9 +16,8 @@ package com.swirlds.platform.util.iterator; -import static com.swirlds.common.utility.CommonUtils.throwArgNull; - import java.util.Iterator; +import java.util.Objects; /** * A wrapper for an iterator that allows the exposed type to be a super type of the iterator provided. @@ -37,9 +36,10 @@ public class TypedIterator implements Iterator { * * @param itr * the iterator to wrap + * @throws NullPointerException in case {@code itr} parameter is {@code null} */ public TypedIterator(final Iterator itr) { - throwArgNull(itr, "itr must not be null"); + Objects.requireNonNull(itr, "itr must not be null"); this.itr = itr; } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DispatchTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DispatchTests.java index ac1950ddecaf..895449c5fdcc 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DispatchTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/DispatchTests.java @@ -290,32 +290,30 @@ void nullArgumentTest() { final DispatchBuilder builder = new DispatchBuilder(config); assertThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> builder.registerObserver(null, null, null), "null arguments not allowed"); assertThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> builder.registerObserver(DispatchTests.class, TestDispatchOne.class, null), "null arguments not allowed"); assertThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> builder.registerObserver(TestDispatchOne.class, null, (TestDispatchOne) x -> {}), "null arguments not allowed"); assertThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> builder.registerObserver(DispatchTests.class, null, (TestDispatchOne) x -> {}), "null arguments not allowed"); - assertThrows( - IllegalArgumentException.class, () -> builder.registerObservers(null), "null arguments not allowed"); + assertThrows(NullPointerException.class, () -> builder.registerObservers(null), "null arguments not allowed"); builder.start(); assertThrows( - IllegalArgumentException.class, + NullPointerException.class, () -> builder.getDispatcher(null, TestDispatchOne.class), "null arguments not allowed"); - assertThrows( - IllegalArgumentException.class, () -> builder.getDispatcher(this, null), "null arguments not allowed"); + assertThrows(NullPointerException.class, () -> builder.getDispatcher(this, null), "null arguments not allowed"); } @Test diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoaderTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoaderTest.java index e45e8d16040c..1a8abb3074bf 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoaderTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/config/legacy/LegacyConfigPropertiesLoaderTest.java @@ -32,8 +32,7 @@ class LegacyConfigPropertiesLoaderTest { @Test void testNullValue() { - Assertions.assertThrows( - IllegalArgumentException.class, () -> LegacyConfigPropertiesLoader.loadConfigFile(null)); + Assertions.assertThrows(NullPointerException.class, () -> LegacyConfigPropertiesLoader.loadConfigFile(null)); } @Test diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/SkippingIteratorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/SkippingIteratorTest.java index 8d56f5b21df0..6f53c9fcf6d4 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/SkippingIteratorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/SkippingIteratorTest.java @@ -177,7 +177,7 @@ void testAllSkippedIndices() { @Test @DisplayName("SkippingIterator - null array") void testNullArray() { - assertThrows(IllegalArgumentException.class, () -> new SkippingIterator<>(null, Collections.emptySet())); + assertThrows(NullPointerException.class, () -> new SkippingIterator<>(null, Collections.emptySet())); } @Tag(TestTypeTags.FUNCTIONAL) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/TypedIteratorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/TypedIteratorTest.java index 40b093e10c4c..b757f02bc12f 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/TypedIteratorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/iterator/TypedIteratorTest.java @@ -48,7 +48,7 @@ public class TypedIteratorTest { @Test @DisplayName("TypedListIterator - null list") void testNullArray() { - assertThrows(IllegalArgumentException.class, () -> new TypedIterator<>(null)); + assertThrows(NullPointerException.class, () -> new TypedIterator<>(null)); } @Tag(TestTypeTags.FUNCTIONAL) diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java index 0b6a8379d46e..1b024612f668 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java @@ -22,7 +22,7 @@ import com.swirlds.common.metrics.LongAccumulator; import com.swirlds.common.metrics.LongGauge; import com.swirlds.common.metrics.Metrics; -import com.swirlds.common.utility.CommonUtils; +import java.util.Objects; /** * Encapsulates statistics for a virtual map. @@ -93,11 +93,10 @@ private static IntegerAccumulator buildIntegerAccumulator( * * @param label * the label for the virtual map - * @throws IllegalArgumentException - * if {@code label} is {@code null} + * @throws NullPointerException in case {@code label} parameter is {@code null} */ public VirtualMapStatistics(final String label) { - CommonUtils.throwArgNull(label, "label"); + Objects.requireNonNull(label, "label must not be null"); this.label = label; } @@ -106,11 +105,10 @@ public VirtualMapStatistics(final String label) { * * @param metrics * reference to the metrics system - * @throws IllegalArgumentException - * if {@code metrics} is {@code null} + * @throws NullPointerException in case {@code metrics} parameter is {@code null} */ public void registerMetrics(final Metrics metrics) { - CommonUtils.throwArgNull(metrics, "metrics"); + Objects.requireNonNull(metrics, "metrics must not be null"); // Generic size = metrics.getOrCreate(new LongGauge.Config(STAT_CATEGORY, VMAP_PREFIX + "size_" + label) From ec2c2bdea4edb27ea22286f00b6146ddd3d270be Mon Sep 17 00:00:00 2001 From: Austin Littley <102969658+alittley@users.noreply.github.com> Date: Wed, 20 Dec 2023 09:08:10 -0500 Subject: [PATCH 13/80] feat: Create hasher classes (#10572) Signed-off-by: Austin Littley --- .../platform/event/hashing/EventHasher.java | 50 ++++++++++++++++ .../wiring/components/EventHasherWiring.java | 57 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/hashing/EventHasher.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventHasherWiring.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/hashing/EventHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/hashing/EventHasher.java new file mode 100644 index 000000000000..a651b0a66853 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/hashing/EventHasher.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.hashing; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.Cryptography; +import com.swirlds.platform.event.GossipEvent; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Hashes events. + */ +public class EventHasher { + private final Cryptography cryptography; + + /** + * Constructs a new event hasher. + * + * @param platformContext the platform context + */ + public EventHasher(@NonNull final PlatformContext platformContext) { + this.cryptography = platformContext.getCryptography(); + } + + /** + * Hashes the event and builds the event descriptor. + * + * @param event the event to hash + * @return the hashed event + */ + public GossipEvent hashEvent(@NonNull final GossipEvent event) { + cryptography.digestSync(event.getHashedData()); + event.buildDescriptor(); + return event; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventHasherWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventHasherWiring.java new file mode 100644 index 000000000000..30ada78247f6 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventHasherWiring.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.wiring.components; + +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.input.InputWire; +import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.event.hashing.EventHasher; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Wiring for the {@link EventHasher}. + * + * @param eventInput the input wire for events to be hashed + * @param eventOutput the output wire for hashed events + * @param flushRunnable the runnable to flush the hasher + */ +public record EventHasherWiring( + @NonNull InputWire eventInput, + @NonNull OutputWire eventOutput, + @NonNull Runnable flushRunnable) { + /** + * Create a new instance of this wiring. + * + * @param taskScheduler the task scheduler for this wiring + * @return the new wiring instance + */ + public static EventHasherWiring create(@NonNull final TaskScheduler taskScheduler) { + return new EventHasherWiring( + taskScheduler.buildInputWire("events to hash"), taskScheduler.getOutputWire(), taskScheduler::flush); + } + + /** + * Bind an event hasher to this wiring. + * + * @param hasher the event hasher to bind + */ + public void bind(@NonNull final EventHasher hasher) { + ((BindableInputWire) eventInput).bind(hasher::hashEvent); + } +} From 41653fdeea67638b376c32e8da5c79bc5f978d93 Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Wed, 20 Dec 2023 08:36:31 -0600 Subject: [PATCH 14/80] chore: try to stabilize concurrentEthSpecs() (#10522) Signed-off-by: Michael Tinker --- .../src/itest/java/AllIntegrationTests.java | 3 +- .../services/bdd/suites/block/BlockSuite.java | 31 ++++++++- .../precompile/ERCPrecompileSuite.java | 64 +----------------- .../suites/leaky/LeakyContractTestsSuite.java | 65 ++++++++++++++++++- 4 files changed, 97 insertions(+), 66 deletions(-) diff --git a/hedera-node/test-clients/src/itest/java/AllIntegrationTests.java b/hedera-node/test-clients/src/itest/java/AllIntegrationTests.java index 69f6049c3495..58ee88b4bb25 100644 --- a/hedera-node/test-clients/src/itest/java/AllIntegrationTests.java +++ b/hedera-node/test-clients/src/itest/java/AllIntegrationTests.java @@ -65,7 +65,8 @@ Collection sequentialSpecsBySuite() { @Order(2) @TestFactory List concurrentSpecs() { - return List.of(concurrentSpecsFrom(ConcurrentSuites.all())); + return List.of( + concurrentSpecsFrom(ConcurrentSuites.all()), concurrentEthSpecsFrom(ConcurrentSuites.ethereumSuites())); } @Tag("integration") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java index f111cca19659..90a634719283 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/block/BlockSuite.java @@ -31,16 +31,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.common.primitives.Longs; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import com.hederahashgraph.api.proto.java.Timestamp; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import java.util.List; +import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; @@ -121,7 +126,20 @@ final HapiSpec blck003ReturnsTimestampOfTheBlock() { final var secondCallTimestamp = Longs.fromByteArray(Arrays.copyOfRange(secondCallTimeLogData, 24, 32)); - assertEquals(firstCallTimestamp, secondCallTimestamp, "Block timestamps should be equal"); + final var firstBlockPeriod = canonicalBlockPeriod(firstCallRecord.getConsensusTimestamp()); + final var secondBlockPeriod = canonicalBlockPeriod(secondCallRecord.getConsensusTimestamp()); + + // In general both calls will be handled in the same block period, and should hence have the + // same Ethereum block timestamp; but timing fluctuations in CI _can_ cause them to be handled + // in different block periods, so we allow for that here as well + if (firstBlockPeriod < secondBlockPeriod) { + assertTrue( + firstCallTimestamp < secondCallTimestamp, + "Block timestamps should change from period " + firstBlockPeriod + " to " + + secondBlockPeriod); + } else { + assertEquals(firstCallTimestamp, secondCallTimestamp, "Block timestamps should be equal"); + } })); } @@ -212,4 +230,15 @@ final HapiSpec blck001And002And003And004ReturnsCorrectBlockProperties() { protected Logger getResultsLogger() { return LOG; } + + /** + * Returns the canonical block period for the given consensus timestamp. + * + * @param consensusTimestamp the consensus timestamp + * @return the canonical block period + */ + private long canonicalBlockPeriod(@NonNull final Timestamp consensusTimestamp) { + return Objects.requireNonNull(consensusTimestamp).getSeconds() + / Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("hedera.recordStream.logPeriod")); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java index 970c8220fd6d..bb2bb3e81e55 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java @@ -49,10 +49,8 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUniqueWithAllowance; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; @@ -71,7 +69,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALLOWANCE_SPENDER_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SPENDER_ACCOUNT_SAME_AS_OWNER; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SPENDER_DOES_NOT_HAVE_ALLOWANCE; @@ -90,7 +87,6 @@ import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; -import com.hederahashgraph.api.proto.java.HederaFunctionality; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; @@ -100,7 +96,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Tag; @HapiTestSuite @@ -123,7 +118,7 @@ public class ERCPrecompileSuite extends HapiSuite { public static final String TRANSFER_SIG_NAME = "transferSig"; public static final String ERC_20_CONTRACT = "ERC20Contract"; private static final String ERC_721_CONTRACT = "ERC721Contract"; - private static final String NAME_TXN = "nameTxn"; + public static final String NAME_TXN = "nameTxn"; private static final String SYMBOL_TXN = "symbolTxn"; private static final String TOTAL_SUPPLY_TXN = "totalSupplyTxn"; private static final String BALANCE_OF_TXN = "balanceOfTxn"; @@ -204,7 +199,6 @@ List erc20() { directCallsWorkForErc20(), erc20TransferFromAllowance(), erc20TransferFromSelf(), - getErc20TokenNameExceedingLimits(), transferErc20TokenFromContractWithNoApproval()); } @@ -933,62 +927,6 @@ final HapiSpec getErc721TokenName() { .withName(TOKEN_NAME))))); } - final HapiSpec getErc20TokenNameExceedingLimits() { - final var REDUCED_NETWORK_FEE = 1L; - final var REDUCED_NODE_FEE = 1L; - final var REDUCED_SERVICE_FEE = 1L; - final var INIT_ACCOUNT_BALANCE = 100 * ONE_HUNDRED_HBARS; - return defaultHapiSpec("getErc20TokenNameExceedingLimits") - .given( - newKeyNamed(MULTI_KEY), - cryptoCreate(ACCOUNT).balance(INIT_ACCOUNT_BALANCE), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(FUNGIBLE_TOKEN) - .tokenType(TokenType.FUNGIBLE_COMMON) - .supplyType(TokenSupplyType.INFINITE) - .initialSupply(5) - .name(TOKEN_NAME) - .treasury(TOKEN_TREASURY) - .adminKey(MULTI_KEY) - .supplyKey(MULTI_KEY), - uploadInitCode(ERC_20_CONTRACT), - contractCreate(ERC_20_CONTRACT)) - .when( - balanceSnapshot("accountSnapshot", ACCOUNT), - reduceFeeFor( - HederaFunctionality.ContractCall, - REDUCED_NODE_FEE, - REDUCED_NETWORK_FEE, - REDUCED_SERVICE_FEE), - withOpContext((spec, opLog) -> allRunFor( - spec, - contractCall( - ERC_20_CONTRACT, - "nameNTimes", - asHeadlongAddress(asHexedAddress( - spec.registry().getTokenID(FUNGIBLE_TOKEN))), - BigInteger.valueOf(51)) - .payingWith(ACCOUNT) - .via(NAME_TXN) - .gas(4_000_000) - .hasKnownStatus(MAX_CHILD_RECORDS_EXCEEDED)))) - .then( - getTxnRecord(NAME_TXN) - .andAllChildRecords() - .logged() - .hasPriority(recordWith() - .contractCallResult(resultWith() - .error(Bytes.of(MAX_CHILD_RECORDS_EXCEEDED - .name() - .getBytes()) - .toHexString()) - .gasUsed(4_000_000))), - getAccountDetails(ACCOUNT) - .has(accountDetailsWith() - .balanceLessThan( - INIT_ACCOUNT_BALANCE - REDUCED_NETWORK_FEE - REDUCED_NODE_FEE))); - } - @HapiTest final HapiSpec getErc721Symbol() { final var tokenSymbol = "N"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index b999fdd7dfbe..3683261a8e02 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -78,6 +78,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.accountAmount; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.accountAmountAlias; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; @@ -85,6 +86,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingThree; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.resetToDefault; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; @@ -94,6 +96,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; +import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -141,6 +144,7 @@ import static com.hedera.services.bdd.suites.contract.precompile.CryptoTransferHTSSuite.TRANSFER_MULTIPLE_TOKENS; import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.APPROVE; import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.ERC_20_CONTRACT; +import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.NAME_TXN; import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.RECIPIENT; import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.TRANSFER; import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.TRANSFER_FROM; @@ -212,6 +216,7 @@ import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; +import com.hederahashgraph.api.proto.java.HederaFunctionality; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenPauseStatus; import com.hederahashgraph.api.proto.java.TokenSupplyType; @@ -229,6 +234,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; @HapiTestSuite @@ -295,7 +301,8 @@ public List getSpecsInSuite() { transferErc20TokenFromErc721TokenFails(), contractCreateNoncesExternalizationHappyPath(), contractCreateFollowedByContractCallNoncesExternalization(), - shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled()); + shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled(), + getErc20TokenNameExceedingLimits()); } @HapiTest @@ -822,6 +829,62 @@ final HapiSpec transferFailsWithIncorrectAmounts() { childRecordsCheck(transferTokenWithNegativeAmountTxn, CONTRACT_REVERT_EXECUTED)); } + final HapiSpec getErc20TokenNameExceedingLimits() { + final var REDUCED_NETWORK_FEE = 1L; + final var REDUCED_NODE_FEE = 1L; + final var REDUCED_SERVICE_FEE = 1L; + final var INIT_ACCOUNT_BALANCE = 100 * ONE_HUNDRED_HBARS; + return defaultHapiSpec("getErc20TokenNameExceedingLimits") + .given( + newKeyNamed(MULTI_KEY), + cryptoCreate(ACCOUNT).balance(INIT_ACCOUNT_BALANCE), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(FUNGIBLE_TOKEN) + .tokenType(TokenType.FUNGIBLE_COMMON) + .supplyType(TokenSupplyType.INFINITE) + .initialSupply(5) + .name(TOKEN_NAME) + .treasury(TOKEN_TREASURY) + .adminKey(MULTI_KEY) + .supplyKey(MULTI_KEY), + uploadInitCode(ERC_20_CONTRACT), + contractCreate(ERC_20_CONTRACT)) + .when( + balanceSnapshot("accountSnapshot", ACCOUNT), + reduceFeeFor( + HederaFunctionality.ContractCall, + REDUCED_NODE_FEE, + REDUCED_NETWORK_FEE, + REDUCED_SERVICE_FEE), + withOpContext((spec, opLog) -> allRunFor( + spec, + contractCall( + ERC_20_CONTRACT, + "nameNTimes", + asHeadlongAddress(asHexedAddress( + spec.registry().getTokenID(FUNGIBLE_TOKEN))), + BigInteger.valueOf(51)) + .payingWith(ACCOUNT) + .via(NAME_TXN) + .gas(4_000_000) + .hasKnownStatus(MAX_CHILD_RECORDS_EXCEEDED)))) + .then( + getTxnRecord(NAME_TXN) + .andAllChildRecords() + .logged() + .hasPriority(recordWith() + .contractCallResult(resultWith() + .error(Bytes.of(MAX_CHILD_RECORDS_EXCEEDED + .name() + .getBytes()) + .toHexString()) + .gasUsed(4_000_000))), + getAccountDetails(ACCOUNT) + .has(accountDetailsWith() + .balanceLessThan( + INIT_ACCOUNT_BALANCE - REDUCED_NETWORK_FEE - REDUCED_NODE_FEE))); + } + @HapiTest HapiSpec payerCannotOverSendValue() { final var payerBalance = 666 * ONE_HBAR; From eb3429d64d03e37a5765698d8244a14f86071a9d Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Wed, 20 Dec 2023 16:55:04 +0200 Subject: [PATCH 15/80] test: fix submittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness (#10587) Signed-off-by: Mustafa Uzun --- .../services/bdd/suites/records/RecordCreationSuite.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java index 3a2e1e985e4b..70a94726705c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java @@ -17,6 +17,7 @@ package com.hedera.services.bdd.suites.records; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; @@ -33,6 +34,7 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; @@ -190,12 +192,17 @@ final HapiSpec submittingNodeChargedNetworkFeeForLackOfDueDiligence() { .logged())); } + @HapiTest final HapiSpec submittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final AtomicReference feeObs = new AtomicReference<>(); - return defaultHapiSpec("SubmittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness") + return propertyPreservingHapiSpec("SubmittingNodeChargedNetworkFeeForIgnoringPayerUnwillingness") + .preserving(STAKING_FEES_NODE_REWARD_PERCENTAGE, STAKING_FEES_STAKING_REWARD_PERCENTAGE) .given( + overridingTwo( + STAKING_FEES_NODE_REWARD_PERCENTAGE, "10", + STAKING_FEES_STAKING_REWARD_PERCENTAGE, "10"), cryptoTransfer(tinyBarsFromTo(GENESIS, TO_ACCOUNT, ONE_HBAR)) .payingWith(GENESIS), cryptoCreate(PAYER), From 78653bbf448feadaef0b7e73a84fae1eddf73989 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Wed, 20 Dec 2023 10:53:45 -0500 Subject: [PATCH 16/80] fix: Added asserts for threads in all modifying methods (#10584) Signed-off-by: Ivan Malygin --- .../internal/merkle/VirtualRootNode.java | 180 ++++++++++-------- 1 file changed, 102 insertions(+), 78 deletions(-) diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java index 8c19c3f69603..a96e6a161c54 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java @@ -316,6 +316,12 @@ public static class ClassVersion { private VirtualMapStatistics statistics; + /** + * This reference is used to assert that there is only one thread modifying the VM at a time. + * NOTE: This field is used *only* if assertions are enabled, otherwise it always has null value. + */ + private final AtomicReference currentModifyingThreadRef = new AtomicReference<>(null); + /** * Creates a new empty root node. This constructor is used for deserialization and * reconnects, not for normal use. @@ -791,9 +797,14 @@ public boolean containsKey(final K key) { public V getForModify(final K key) { throwIfImmutable(); Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); - final VirtualLeafRecord rec = records.findLeafRecord(key, true); - statistics.countUpdatedEntities(); - return rec == null ? null : rec.getValue(); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + final VirtualLeafRecord rec = records.findLeafRecord(key, true); + statistics.countUpdatedEntities(); + return rec == null ? null : rec.getValue(); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); + } } /** @@ -826,20 +837,25 @@ public V get(final K key) { public void put(final K key, final V value) { throwIfImmutable(); assert !isHashed() : "Cannot modify already hashed node"; - Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); + + final long path = records.findKey(key); + if (path == INVALID_PATH) { + // The key is not stored. So add a new entry and return. + add(key, value); + statistics.countAddedEntities(); + statistics.setSize(state.size()); + return; + } - final long path = records.findKey(key); - if (path == INVALID_PATH) { - // The key is not stored. So add a new entry and return. - add(key, value); - statistics.countAddedEntities(); - statistics.setSize(state.size()); - return; + final VirtualLeafRecord leaf = new VirtualLeafRecord<>(path, key, value); + cache.putLeaf(leaf); + statistics.countUpdatedEntities(); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); } - - final VirtualLeafRecord leaf = new VirtualLeafRecord<>(path, key, value); - cache.putLeaf(leaf); - statistics.countUpdatedEntities(); } /** @@ -859,16 +875,20 @@ public void put(final K key, final V value) { public V replace(final K key, final V value) { throwIfImmutable(); Objects.requireNonNull(key, NO_NULL_KEYS_ALLOWED_MESSAGE); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + // Attempt to replace the existing leaf + final boolean success = replaceImpl(key, value); + statistics.countUpdatedEntities(); + if (success) { + return value; + } - // Attempt to replace the existing leaf - final boolean success = replaceImpl(key, value); - statistics.countUpdatedEntities(); - if (success) { - return value; + // We failed to find an existing leaf (dirty or clean). So throw an ISE. + throw new IllegalStateException("Can not replace value that is not in the map"); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); } - - // We failed to find an existing leaf (dirty or clean). So throw an ISE. - throw new IllegalStateException("Can not replace value that is not in the map"); } /** @@ -882,67 +902,71 @@ public V replace(final K key, final V value) { public V remove(final K key) { throwIfImmutable(); Objects.requireNonNull(key); + assert currentModifyingThreadRef.compareAndSet(null, Thread.currentThread()); + try { + // Verify whether the current leaf exists. If not, we can just return null. + VirtualLeafRecord leafToDelete = records.findLeafRecord(key, true); + if (leafToDelete == null) { + return null; + } - // Verify whether the current leaf exists. If not, we can just return null. - VirtualLeafRecord leafToDelete = records.findLeafRecord(key, true); - if (leafToDelete == null) { - return null; - } - - // Mark the leaf as being deleted. - cache.deleteLeaf(leafToDelete); - statistics.countRemovedEntities(); + // Mark the leaf as being deleted. + cache.deleteLeaf(leafToDelete); + statistics.countRemovedEntities(); - // We're going to need these - final long lastLeafPath = state.getLastLeafPath(); - final long firstLeafPath = state.getFirstLeafPath(); - final long leafToDeletePath = leafToDelete.getPath(); - - // If the leaf was not the last leaf, then move the last leaf to take this spot - if (leafToDeletePath != lastLeafPath) { - final VirtualLeafRecord lastLeaf = records.findLeafRecord(lastLeafPath, true); - assert lastLeaf != null; - cache.clearLeafPath(lastLeafPath); - lastLeaf.setPath(leafToDeletePath); - cache.putLeaf(lastLeaf); - // NOTE: at this point, if leafToDelete was in the cache at some "path" index, it isn't anymore! - // The lastLeaf has taken its place in the path index. - } + // We're going to need these + final long lastLeafPath = state.getLastLeafPath(); + final long firstLeafPath = state.getFirstLeafPath(); + final long leafToDeletePath = leafToDelete.getPath(); + + // If the leaf was not the last leaf, then move the last leaf to take this spot + if (leafToDeletePath != lastLeafPath) { + final VirtualLeafRecord lastLeaf = records.findLeafRecord(lastLeafPath, true); + assert lastLeaf != null; + cache.clearLeafPath(lastLeafPath); + lastLeaf.setPath(leafToDeletePath); + cache.putLeaf(lastLeaf); + // NOTE: at this point, if leafToDelete was in the cache at some "path" index, it isn't anymore! + // The lastLeaf has taken its place in the path index. + } - // If the parent of the last leaf is root, then we can simply do some bookkeeping. - // Otherwise, we replace the parent of the last leaf with the sibling of the last leaf, - // and mark it dirty. This covers all cases. - final long lastLeafParent = getParentPath(lastLeafPath); - if (lastLeafParent == ROOT_PATH) { - if (firstLeafPath == lastLeafPath) { - // We just removed the very last leaf, so set these paths to be invalid - state.setFirstLeafPath(INVALID_PATH); - state.setLastLeafPath(INVALID_PATH); + // If the parent of the last leaf is root, then we can simply do some bookkeeping. + // Otherwise, we replace the parent of the last leaf with the sibling of the last leaf, + // and mark it dirty. This covers all cases. + final long lastLeafParent = getParentPath(lastLeafPath); + if (lastLeafParent == ROOT_PATH) { + if (firstLeafPath == lastLeafPath) { + // We just removed the very last leaf, so set these paths to be invalid + state.setFirstLeafPath(INVALID_PATH); + state.setLastLeafPath(INVALID_PATH); + } else { + // We removed the second to last leaf, so the first & last leaf paths are now the same. + state.setLastLeafPath(FIRST_LEFT_PATH); + } } else { - // We removed the second to last leaf, so the first & last leaf paths are now the same. - state.setLastLeafPath(FIRST_LEFT_PATH); + final long lastLeafSibling = getSiblingPath(lastLeafPath); + final VirtualLeafRecord sibling = records.findLeafRecord(lastLeafSibling, true); + assert sibling != null; + cache.clearLeafPath(lastLeafSibling); + cache.deleteHash(lastLeafParent); + sibling.setPath(lastLeafParent); + cache.putLeaf(sibling); + + // Update the first & last leaf paths + state.setFirstLeafPath(lastLeafParent); // replaced by the sibling, it is now first + state.setLastLeafPath(lastLeafSibling - 1); // One left of the last leaf sibling + } + if (statistics != null) { + statistics.setSize(state.size()); } - } else { - final long lastLeafSibling = getSiblingPath(lastLeafPath); - final VirtualLeafRecord sibling = records.findLeafRecord(lastLeafSibling, true); - assert sibling != null; - cache.clearLeafPath(lastLeafSibling); - cache.deleteHash(lastLeafParent); - sibling.setPath(lastLeafParent); - cache.putLeaf(sibling); - - // Update the first & last leaf paths - state.setFirstLeafPath(lastLeafParent); // replaced by the sibling, it is now first - state.setLastLeafPath(lastLeafSibling - 1); // One left of the last leaf sibling - } - if (statistics != null) { - statistics.setSize(state.size()); - } - // Get the value and return it (as read only). - final V value = leafToDelete.getValue(); - //noinspection unchecked - return value == null ? null : (V) value.asReadOnly(); + // Get the value and return it (as read only). + final V value = leafToDelete.getValue(); + //noinspection unchecked + return value == null ? null : (V) value.asReadOnly(); + } finally { + assert currentModifyingThreadRef.compareAndSet(Thread.currentThread(), null); + } } /* From be962a04717dcc88266a79402286290d1e921cc2 Mon Sep 17 00:00:00 2001 From: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:09:54 -0600 Subject: [PATCH 17/80] feat: design roster interfaces (#10428) Signed-off-by: Edward Wertz --- .../roster/roster-datastructures-and-apis.md | 31 ++++ .../com/swirlds/platform/roster/Roster.java | 69 ++++++++ .../swirlds/platform/roster/RosterEntry.java | 73 ++++++++ .../roster/legacy/AddressBookRoster.java | 162 ++++++++++++++++++ .../roster/legacy/AddressRosterEntry.java | 162 ++++++++++++++++++ .../roster/legacy/AddressBookRosterTests.java | 84 +++++++++ .../system/address/AddressBookTests.java | 5 +- 7 files changed, 582 insertions(+), 4 deletions(-) create mode 100644 platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java create mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java diff --git a/platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md b/platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md new file mode 100644 index 000000000000..d3eceaaafb7a --- /dev/null +++ b/platform-sdk/docs/core/dynamic-address-book/roster/roster-datastructures-and-apis.md @@ -0,0 +1,31 @@ +# Roster APIs + +The following roster api is reduced from the address book to just the fields that are needed by the platform to establish mutual TLS connections, gossip, validate events and state, come to consensus, and detect an ISS. + +The data for each node is contained in the node's `RosterEntry`. + +## Roster Interfaces + +### RosterEntry + +```java +public interface RosterEntry extends SelfSerializable { + NodeId getNodeId(); + long getWeight(); + String getHostname(); + int getPort(); + PublicKey getSigningPublicKey(); + X509Certificate getSigningCertificate(); + boolean isZeroWeight(); +} +``` +### Roster + +```java +public interface Roster extends Iterable, SelfSerializable{ + int size(); + boolean contains(NodeId nodeId); + RosterEntry getEntry(NodeId nodeId); + long getTotalWeight(); +} +``` \ No newline at end of file diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java new file mode 100644 index 000000000000..5e14a90e2e9a --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/Roster.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.roster; + +import com.swirlds.common.io.SelfSerializable; +import com.swirlds.common.platform.NodeId; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Collection; + +/** + * A roster is the set of nodes that are creating events and contributing to consensus. The data in a Roster object is + * immutable must not change over time. + */ +public interface Roster extends Iterable, SelfSerializable { + + /** + * @return a collection of all unique nodeIds in the roster. + */ + @NonNull + Collection getNodeIds(); + + /** + * @param nodeId the nodeId of the {@link RosterEntry} to get + * @return the RosterEntry with the given nodeId + * @throws java.util.NoSuchElementException if the nodeId is not in the roster + */ + @NonNull + RosterEntry getEntry(@NonNull NodeId nodeId); + + /** + * @param nodeId the nodeId to check for membership in the roster + * @return true if there is a rosterEntry with the given nodeId, false otherwise + */ + default boolean contains(@NonNull NodeId nodeId) { + return getNodeIds().contains(nodeId); + } + + /** + * @return the total number of nodes in the roster + */ + default int getSize() { + return getNodeIds().size(); + } + + /** + * @return the total weight of all nodes in the roster + */ + default long getTotalWeight() { + long totalWeight = 0; + for (final RosterEntry entry : this) { + totalWeight += entry.getWeight(); + } + return totalWeight; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java new file mode 100644 index 000000000000..0f15497f6d71 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterEntry.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.roster; + +import com.swirlds.common.io.SelfSerializable; +import com.swirlds.common.platform.NodeId; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.security.PublicKey; +import java.security.cert.X509Certificate; + +/** + * A RosterEntry is a single node in the roster. It contains the node's ID, weight, network address, and public signing + * key in the form of an X509Certificate. The data in a RosterEntry object is immutable and must not change over time. + */ +public interface RosterEntry extends SelfSerializable { + + /** + * @return the ID of the node + */ + @NonNull + NodeId getNodeId(); + + /** + * @return the non-negative consensus weight of the node + */ + long getWeight(); + + /** + * @return the hostname portion of a node's gossip endpoint. + */ + @NonNull + String getHostname(); + + /** + * @return the port portion of a node's gossip endpoint. + */ + int getPort(); + + /** + * @return the X509Certificate containing the public signing key of the node + */ + @NonNull + X509Certificate getSigningCertificate(); + + /** + * @return the public signing key of the node + */ + @NonNull + default PublicKey getSigningPublicKey() { + return getSigningCertificate().getPublicKey(); + } + + /** + * @return true if the weight is zero, false otherwise + */ + default boolean isZeroWeight() { + return getWeight() == 0; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java new file mode 100644 index 000000000000..d7833dd708f7 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressBookRoster.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.roster.legacy; + +import com.swirlds.base.utility.ToStringBuilder; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.roster.Roster; +import com.swirlds.platform.roster.RosterEntry; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * A {@link Roster} implementation that uses an {@link AddressBook} as its backing data structure. + */ +public class AddressBookRoster implements Roster { + private static final long CLASS_ID = 0x7104f97d4e298619L; + + private static final class ClassVersion { + public static final int ORIGINAL = 1; + } + + private final Map entries = new HashMap<>(); + private List nodeOrder; + + /** + * Constructs a new {@link AddressBookRoster} from the given {@link AddressBook} and {@link KeysAndCerts} map. + * + * @param addressBook the address book + * @param keysAndCertsMap the keys and certs map + */ + public AddressBookRoster( + @NonNull final AddressBook addressBook, @NonNull final Map keysAndCertsMap) { + Objects.requireNonNull(addressBook); + Objects.requireNonNull(keysAndCertsMap); + + for (final Address address : addressBook) { + entries.put(address.getNodeId(), new AddressRosterEntry(address, keysAndCertsMap.get(address.getNodeId()))); + } + + nodeOrder = entries.keySet().stream().sorted().toList(); + } + + /** + * Empty constructor for deserialization. + */ + public AddressBookRoster() { + nodeOrder = new ArrayList<>(); + } + + @Override + public long getClassId() { + return CLASS_ID; + } + + @Override + public int getVersion() { + return ClassVersion.ORIGINAL; + } + + @Override + public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { + out.writeInt(entries.size()); + for (final RosterEntry entry : this) { + out.writeSerializable(entry, true); + } + } + + @Override + public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final RosterEntry entry = in.readSerializable(); + entries.put(entry.getNodeId(), entry); + } + nodeOrder = entries.keySet().stream().sorted().toList(); + } + + @Override + @NonNull + public Collection getNodeIds() { + return nodeOrder; + } + + @Override + @NonNull + public RosterEntry getEntry(@NonNull final NodeId nodeId) { + Objects.requireNonNull(nodeId); + final RosterEntry entry = entries.get(nodeId); + if (entry == null) { + throw new NoSuchElementException("No entry found for nodeId " + nodeId); + } + return entry; + } + + @Override + @NonNull + public Iterator iterator() { + return new Iterator<>() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < nodeOrder.size(); + } + + @Override + public RosterEntry next() { + return entries.get(nodeOrder.get(index++)); + } + }; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final AddressBookRoster that = (AddressBookRoster) o; + return Objects.equals(entries, that.entries); + } + + @Override + public int hashCode() { + return Objects.hash(entries); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append("entries", entries).toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java new file mode 100644 index 000000000000..24eb86a7cd45 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/legacy/AddressRosterEntry.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.roster.legacy; + +import com.swirlds.base.utility.ToStringBuilder; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.roster.RosterEntry; +import com.swirlds.platform.system.address.Address; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Objects; + +/** + * An {@link Address} wrapper that implements the {@link RosterEntry} interface. + */ +public class AddressRosterEntry implements RosterEntry { + + private static final long CLASS_ID = 0x4e700e352be188aaL; + + private static final class ClassVersion { + public static final int ORIGINAL = 1; + } + + private static final int ENCODED_CERT_MAX_SIZE = 8192; + + private Address address; + private X509Certificate sigCert; + + /** + * Constructs a new {@link AddressRosterEntry} from the given {@link Address} and {@link KeysAndCerts}. + * + * @param address the address + * @param keysAndCerts the keys and certs containing the signing certificate + */ + public AddressRosterEntry(@NonNull final Address address, @NonNull final KeysAndCerts keysAndCerts) { + Objects.requireNonNull(address); + Objects.requireNonNull(keysAndCerts); + + this.address = address; + this.sigCert = keysAndCerts.sigCert(); + } + + /** + * Empty constructor for deserialization. + */ + public AddressRosterEntry() {} + + /** + * {@inheritDoc} + */ + @Override + public long getClassId() { + return CLASS_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public int getVersion() { + return ClassVersion.ORIGINAL; + } + + @Override + public void serialize(@NonNull final SerializableDataOutputStream out) throws IOException { + out.writeSerializable(address, false); + try { + out.writeByteArray(sigCert.getEncoded()); + } catch (final CertificateEncodingException e) { + throw new IOException("Could not encode certificate", e); + } + } + + @Override + public void deserialize(@NonNull final SerializableDataInputStream in, final int version) throws IOException { + address = in.readSerializable(false, Address::new); + final byte[] encodedCert = in.readByteArray(ENCODED_CERT_MAX_SIZE); + try { + sigCert = (X509Certificate) + CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(encodedCert)); + } catch (final CertificateException e) { + throw new IOException("Could not decode certificate", e); + } + } + + @Override + @NonNull + public NodeId getNodeId() { + return address.getNodeId(); + } + + @Override + public long getWeight() { + return address.getWeight(); + } + + @NonNull + @Override + public String getHostname() { + return Objects.requireNonNullElse(address.getHostnameExternal(), ""); + } + + @Override + public int getPort() { + return address.getPortExternal(); + } + + @NonNull + @Override + public X509Certificate getSigningCertificate() { + return sigCert; + } + + @Override + public boolean equals(@Nullable final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final AddressRosterEntry that = (AddressRosterEntry) o; + return Objects.equals(address, that.address) && Objects.equals(sigCert, that.sigCert); + } + + @Override + public int hashCode() { + return Objects.hash(address, sigCert); + } + + @Override + public String toString() { + + return new ToStringBuilder(this) + .append("address", address) + .append("sigCert", sigCert) + .toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java new file mode 100644 index 000000000000..26a06b406d81 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/legacy/AddressBookRosterTests.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.roster.legacy; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.crypto.KeysAndCerts; +import com.swirlds.platform.roster.Roster; +import com.swirlds.platform.roster.RosterEntry; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class AddressBookRosterTests { + + @DisplayName("Serialize and deserialize AddressBook derived Roster") + @ParameterizedTest + @MethodSource({"com.swirlds.platform.crypto.CryptoArgsProvider#basicTestArgs"}) + void serializeDeserializeTest( + @NonNull final AddressBook addressBook, @NonNull final Map keysAndCerts) + throws IOException, ConstructableRegistryException { + ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); + final Roster roster = new AddressBookRoster(addressBook, keysAndCerts); + + final ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + final SerializableDataOutputStream out = new SerializableDataOutputStream(byteOut); + + out.writeSerializable(roster, true); + + final SerializableDataInputStream in = + new SerializableDataInputStream(new ByteArrayInputStream(byteOut.toByteArray())); + + Roster roster2 = in.readSerializable(); + assertEquals(roster, roster2); + } + + @DisplayName("Roster derived from AddressBook") + @ParameterizedTest + @MethodSource({"com.swirlds.platform.crypto.CryptoArgsProvider#basicTestArgs"}) + void addressBookRosterTest( + @NonNull final AddressBook addressBook, @NonNull final Map keysAndCerts) { + final Roster roster = new AddressBookRoster(addressBook, keysAndCerts); + final Iterator entries = roster.iterator(); + for (int i = 0; i < addressBook.getSize(); i++) { + final NodeId nodeId = addressBook.getNodeId(i); + final Address address = addressBook.getAddress(nodeId); + final RosterEntry rosterEntry = entries.next(); + assertEquals(address.getHostnameExternal(), rosterEntry.getHostname()); + assertEquals(address.getPortExternal(), rosterEntry.getPort()); + assertEquals(address.getNodeId(), rosterEntry.getNodeId()); + assertEquals(address.getWeight(), rosterEntry.getWeight()); + assertEquals(address.getSigPublicKey(), rosterEntry.getSigningPublicKey()); + } + assertFalse(entries.hasNext()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java index 5a6510d78b95..db64b8ec7845 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/address/AddressBookTests.java @@ -18,7 +18,6 @@ import static com.swirlds.common.test.fixtures.RandomUtils.getRandomPrintSeed; import static com.swirlds.platform.system.address.AddressBookUtils.parseAddressBookText; -import static com.swirlds.test.framework.TestQualifierTags.TIME_CONSUMING; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -48,7 +47,6 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @DisplayName("AddressBook Tests") @@ -329,9 +327,8 @@ void atoStringSanityTest() { @Test @DisplayName("Serialization Test") - @Tag(TIME_CONSUMING) void serializationTest() throws IOException, ConstructableRegistryException { - ConstructableRegistry.getInstance().registerConstructables("com.swirlds.common.system"); + ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); final AddressBook original = new RandomAddressBookGenerator(getRandomPrintSeed()) .setSize(100) From 272983d0c2b09972e0e1470d8b5177564e4c88c7 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Wed, 20 Dec 2023 10:32:44 -0600 Subject: [PATCH 18/80] chore: Fix remaining `Crypto` tests (#10500) Signed-off-by: Neeharika-Sompalli --- .../app/workflows/ingest/IngestChecker.java | 40 ++++++++--- .../scope/HandleHederaNativeOperations.java | 8 ++- .../src/main/java/module-info.java | 3 +- .../HandleHederaNativeOperationsTest.java | 5 +- .../impl/validators/AllowanceValidator.java | 9 +-- .../crypto/AutoAccountCreationSuite.java | 1 + .../suites/leaky/LeakyCryptoTestsSuite.java | 69 +++++++++++-------- .../src/main/resource/bootstrap.properties | 2 +- 8 files changed, 88 insertions(+), 49 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java index 0146342d40b3..d3a8f362646a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/ingest/IngestChecker.java @@ -29,22 +29,26 @@ import static com.hedera.node.app.spi.HapiUtils.isHollow; import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static com.swirlds.platform.system.status.PlatformStatus.ACTIVE; +import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.SignaturePair; import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.annotations.NodeSelfId; import com.hedera.node.app.fees.FeeContextImpl; import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.info.CurrentPlatformStatus; +import com.hedera.node.app.service.evm.utils.EthSigsUtils; import com.hedera.node.app.signature.DefaultKeyVerifier; import com.hedera.node.app.signature.ExpandedSignaturePair; import com.hedera.node.app.signature.SignatureExpander; import com.hedera.node.app.signature.SignatureVerifier; import com.hedera.node.app.spi.authorization.Authorizer; import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.HederaState; @@ -55,6 +59,8 @@ import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher; import com.hedera.node.config.data.HederaConfig; +import com.hedera.node.config.data.LazyCreationConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -229,16 +235,32 @@ private void verifyPayerSignature( signatureExpander.expand(sigPairs, expandedSigs); if (!isHollow(payer)) { signatureExpander.expand(payerKey, sigPairs, expandedSigs); + } else { + // If the payer is hollow, then we need to expand the signature for the payer + final var originals = txInfo.signatureMap().sigPairOrElse(emptyList()).stream() + .filter(SignaturePair::hasEcdsaSecp256k1) + .filter(pair -> Bytes.wrap(EthSigsUtils.recoverAddressFromPubKey( + pair.pubKeyPrefix().toByteArray())) + .equals(payer.alias())) + .findFirst(); + validateTruePreCheck(originals.isPresent(), INVALID_SIGNATURE); + validateTruePreCheck( + configuration.getConfigData(LazyCreationConfig.class).enabled(), INVALID_SIGNATURE); + signatureExpander.expand(List.of(originals.get()), expandedSigs); + } - // Verify the signatures - final var results = signatureVerifier.verify(txInfo.signedBytes(), expandedSigs); - final var verifier = new DefaultKeyVerifier(sigPairs.size(), hederaConfig, results); - final var payerKeyVerification = verifier.verificationFor(payerKey); - - // This can happen if the signature map was missing a signature for the payer account. - if (payerKeyVerification.failed()) { - throw new PreCheckException(INVALID_SIGNATURE); - } + // Verify the signatures + final var results = signatureVerifier.verify(txInfo.signedBytes(), expandedSigs); + final var verifier = new DefaultKeyVerifier(sigPairs.size(), hederaConfig, results); + final SignatureVerification payerKeyVerification; + if (!isHollow(payer)) { + payerKeyVerification = verifier.verificationFor(payerKey); + } else { + payerKeyVerification = verifier.verificationFor(payer.alias()); + } + // This can happen if the signature map was missing a signature for the payer account. + if (payerKeyVerification.failed()) { + throw new PreCheckException(INVALID_SIGNATURE); } } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index b2496737a86e..5bba8ec06890 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.LAZY_CREATION_MEMO; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthHollowAccountCreation; import static java.util.Objects.requireNonNull; @@ -103,9 +104,10 @@ public void setNonce(final long contractNumber, final long nonce) { // Note the use of the null "verification assistant" callback; we don't want any // signing requirements enforced for this synthetic transaction try { - return context.dispatchRemovablePrecedingTransaction( - synthTxn, CryptoCreateRecordBuilder.class, null, context.payer()) - .status(); + final var childRecordBuilder = context.dispatchRemovablePrecedingTransaction( + synthTxn, CryptoCreateRecordBuilder.class, null, context.payer()); + childRecordBuilder.memo(LAZY_CREATION_MEMO); + return childRecordBuilder.status(); } catch (final HandleException e) { // It is critically important we don't let HandleExceptions propagate to the workflow because // it doesn't rollback for contract operations so we can commit gas charges; that is, the diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java index fef550881971..b71a00b773e8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java @@ -51,7 +51,8 @@ com.hedera.node.app.service.contract.impl.test; exports com.hedera.node.app.service.contract.impl.exec.failure to - com.hedera.node.app.service.contract.impl.test; + com.hedera.node.app.service.contract.impl.test, + com.hedera.node.app.service.contract.impl.test.exec.scope; exports com.hedera.node.app.service.contract.impl.exec; exports com.hedera.node.app.service.contract.impl.exec.operations to com.hedera.node.app.service.contract.impl.test; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index 40f30c3f6f7e..f8871dfa84c8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -45,6 +45,7 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.Key; @@ -150,9 +151,9 @@ void createsHollowAccountByDispatching() { .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); - given(context.dispatchRemovablePrecedingTransaction( + when(context.dispatchRemovablePrecedingTransaction( eq(synthTxn), eq(CryptoCreateRecordBuilder.class), eq(null), eq(A_NEW_ACCOUNT_ID))) - .willReturn(cryptoCreateRecordBuilder); + .thenReturn(cryptoCreateRecordBuilder); given(cryptoCreateRecordBuilder.status()).willReturn(OK); final var status = subject.createHollowAccount(CANONICAL_ALIAS); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java index 1152dcccd1ad..7834a7bb0e28 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/AllowanceValidator.java @@ -70,13 +70,10 @@ protected void validateSerialNums( */ public static int aggregateApproveNftAllowances(final List nftAllowances) { int nftAllowancesTotal = 0; - final var setOfSerials = new HashSet(); - for (final var allowances : nftAllowances) { - setOfSerials.addAll(allowances.serialNumbers()); - if (!setOfSerials.isEmpty()) { - nftAllowancesTotal += setOfSerials.size(); - setOfSerials.clear(); + // each serial is counted as an allowance + if (!allowances.serialNumbers().isEmpty()) { + nftAllowancesTotal += allowances.serialNumbers().size(); } else { nftAllowancesTotal++; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java index d1b43a6094dd..0cf5a7608c01 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java @@ -506,6 +506,7 @@ final HapiSpec canAutoCreateWithNftTransferToEvmAddress() { recordWith().status(SUCCESS).consensusTimeImpliedByNonce(parentConsTime.get(), -1)))); } + @HapiTest final HapiSpec multipleTokenTransfersSucceed() { final var initialTokenSupply = 1000; final var multiTokenXfer = "multiTokenXfer"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 004700de17b1..838ebbed246c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -70,11 +70,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOfDeferred; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingThree; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.remembering; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; @@ -121,6 +119,7 @@ import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoTransfer; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoUpdate; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ALIAS_ALREADY_ASSIGNED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_ACCOUNT_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE; @@ -208,7 +207,7 @@ public List getSpecsInSuite() { hollowAccountCompletionWithEthereumTransaction(), hollowAccountCreationChargesExpectedFees(), lazyCreateViaEthereumCryptoTransfer(), - hollowAccountCompletionWithSimultaniousPropertiesUpdate(), + hollowAccountCompletionWithSimultaneousPropertiesUpdate(), contractDeployAfterEthereumTransferLazyCreate(), contractCallAfterEthereumTransferLazyCreate(), autoAssociationPropertiesWorkAsExpected(), @@ -217,6 +216,7 @@ public List getSpecsInSuite() { customFeesHaveExpectedAutoCreateInteractions()); } + @HapiTest final HapiSpec autoAssociationPropertiesWorkAsExpected() { final var minAutoRenewPeriodPropertyName = "ledger.autoRenewPeriod.minDuration"; final var maxAssociationsPropertyName = "ledger.maxAutoAssociations"; @@ -252,6 +252,7 @@ final HapiSpec autoAssociationPropertiesWorkAsExpected() { validateChargedUsd(updateWithExpiredAccount, plusTenSlotsFee)); } + @HapiTest final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButServiceFee() { final var civilian = "civilian"; final var creation = "creation"; @@ -306,10 +307,11 @@ final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButSe // because this fails depending on the previous operation reaching // consensus before the current operation or after, since we have added // deferStatusResolution - .hasPrecheckFrom(OK, INSUFFICIENT_PAYER_BALANCE) - .hasKnownStatus(INSUFFICIENT_PAYER_BALANCE))); + .hasPrecheckFrom(OK, INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE) + .hasKnownStatusFrom(INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE))); } + // Cannot be enabled as HapiTest because long term schedule transactions is not implemented in mod service final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { return defaultHapiSpec("ScheduledCryptoApproveAllowanceWaitForExpiryTrue") .given( @@ -388,11 +390,11 @@ final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { getTokenNftInfo(NON_FUNGIBLE_TOKEN, 2L).hasSpenderID(SPENDER)); } + // @HapiTest // will be enabled in next PR final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled() { - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled") + return propertyPreservingHapiSpec("txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled") + .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( - remembering(startingProps, LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED), overridingTwo( LAZY_CREATION_ENABLED, FALSE_VALUE, CRYPTO_CREATE_WITH_ALIAS_ENABLED, FALSE_VALUE), @@ -434,9 +436,10 @@ final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled( .has(accountWith().key(SECP_256K1_SOURCE_KEY).noAlias()); allRunFor(spec, op, op2, op3, op4, op5, op6, op7, hapiGetAccountInfo); })) - .then(overridingAllOfDeferred(() -> startingProps)); + .then(); } + // Cannot be enabled as HapiTest because tokens.maxPerAccount is not being used in mod service final HapiSpec maxAutoAssociationSpec() { final int MONOGAMOUS_NETWORK = 1; final int maxAutoAssociations = 100; @@ -460,6 +463,7 @@ final HapiSpec maxAutoAssociationSpec() { overriding("tokens.maxPerAccount", "" + ADVENTUROUS_NETWORK)); } + // Cannot be enabled as HapiTest because expiration is not implemented in mod service public HapiSpec canDissociateFromMultipleExpiredTokens() { final var civilian = "civilian"; final long initialSupply = 100L; @@ -496,6 +500,7 @@ public HapiSpec canDissociateFromMultipleExpiredTokens() { overriding(LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION, DEFAULT_MIN_AUTO_RENEW_PERIOD)); } + @HapiTest final HapiSpec cannotExceedAccountAllowanceLimit() { return defaultHapiSpec("CannotExceedAccountAllowanceLimit") .given( @@ -563,11 +568,11 @@ final HapiSpec cannotExceedAccountAllowanceLimit() { HEDERA_ALLOWANCES_MAX_ACCOUNT_LIMIT, "100")); } + @HapiTest final HapiSpec createAnAccountWithEVMAddressAliasAndECKey() { - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("CreateAnAccountWithEVMAddressAliasAndECKey") + return propertyPreservingHapiSpec("CreateAnAccountWithEVMAddressAliasAndECKey") + .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) .given( - remembering(startingProps, LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED), overridingTwo(LAZY_CREATION_ENABLED, TRUE_VALUE, CRYPTO_CREATE_WITH_ALIAS_ENABLED, TRUE_VALUE), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE)) .when(withOpContext((spec, opLog) -> { @@ -619,9 +624,10 @@ final HapiSpec createAnAccountWithEVMAddressAliasAndECKey() { getTxnRecord("createTxn").hasPriority(recordWith().hasNoAlias()); allRunFor(spec, hapiGetAccountInfo, hapiGetAnotherAccountInfo, getTxnRecord); })) - .then(overridingAllOfDeferred(() -> startingProps)); + .then(); } + @HapiTest final HapiSpec createAnAccountWithEVMAddress() { return propertyPreservingHapiSpec("CreateAnAccountWithEVMAddress") .preserving(LAZY_CREATION_ENABLED, CRYPTO_CREATE_WITH_ALIAS_ENABLED) @@ -643,6 +649,7 @@ final HapiSpec createAnAccountWithEVMAddress() { .then(); } + @HapiTest final HapiSpec cannotExceedAllowancesTransactionLimit() { return defaultHapiSpec("CannotExceedAllowancesTransactionLimit") .given( @@ -691,11 +698,13 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { .addCryptoAllowance(OWNER, SECOND_SPENDER, 100L) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SPENDER, false, List.of(1L)) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED), + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED), cryptoApproveAllowance() .payingWith(OWNER) .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SPENDER, false, List.of(1L, 1L, 1L, 1L, 1L)) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED), + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED), cryptoApproveAllowance() .payingWith(OWNER) .addCryptoAllowance(OWNER, SPENDER, 100L) @@ -703,7 +712,8 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { .addCryptoAllowance(OWNER, SPENDER, 100L) .addCryptoAllowance(OWNER, SPENDER, 200L) .addCryptoAllowance(OWNER, SPENDER, 200L) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED), + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED), cryptoApproveAllowance() .payingWith(OWNER) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) @@ -711,7 +721,8 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) - .hasPrecheck(MAX_ALLOWANCES_EXCEEDED)) + .hasPrecheckFrom(OK, MAX_ALLOWANCES_EXCEEDED) + .hasKnownStatus(MAX_ALLOWANCES_EXCEEDED)) .then( // reset overridingTwo( @@ -719,11 +730,11 @@ final HapiSpec cannotExceedAllowancesTransactionLimit() { HEDERA_ALLOWANCES_MAX_ACCOUNT_LIMIT, "100")); } + @HapiTest final HapiSpec hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { - final Map startingProps = new HashMap<>(); - return defaultHapiSpec("HollowAccountCompletionNotAcceptedWhenFlagIsDisabled") + return propertyPreservingHapiSpec("HollowAccountCompletionNotAcceptedWhenFlagIsDisabled") + .preserving(LAZY_CREATION_ENABLED) .given( - remembering(startingProps, LAZY_CREATION_ENABLED), overriding(LAZY_CREATION_ENABLED, TRUE), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(LAZY_CREATE_SPONSOR).balance(INITIAL_BALANCE * ONE_HBAR), @@ -765,6 +776,7 @@ final HapiSpec hollowAccountCompletionNotAcceptedWhenFlagIsDisabled() { })); } + @HapiTest final HapiSpec hollowAccountCreationChargesExpectedFees() { final long REDUCED_NODE_FEE = 2L; final long REDUCED_NETWORK_FEE = 3L; @@ -831,13 +843,13 @@ final HapiSpec hollowAccountCreationChargesExpectedFees() { })) .then(uploadDefaultFeeSchedules(GENESIS)); } - + // @HapiTest /// will be enabled after EthereumTransaction hollow account finalization is implemented final HapiSpec hollowAccountCompletionWithEthereumTransaction() { final Map startingProps = new HashMap<>(); final String CONTRACT = "Fuse"; - return defaultHapiSpec("HollowAccountCompletionWithEthereumTransaction") + return propertyPreservingHapiSpec("HollowAccountCompletionWithEthereumTransaction") + .preserving(LAZY_CREATION_ENABLED, CHAIN_ID_PROP) .given( - remembering(startingProps, LAZY_CREATION_ENABLED, CHAIN_ID_PROP), overridingTwo(LAZY_CREATION_ENABLED, TRUE, CHAIN_ID_PROP, "298"), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), @@ -881,6 +893,7 @@ final HapiSpec hollowAccountCompletionWithEthereumTransaction() { })); } + @HapiTest final HapiSpec contractDeployAfterEthereumTransferLazyCreate() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; @@ -930,6 +943,7 @@ final HapiSpec contractDeployAfterEthereumTransferLazyCreate() { })); } + @HapiTest final HapiSpec contractCallAfterEthereumTransferLazyCreate() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; @@ -979,6 +993,7 @@ final HapiSpec contractCallAfterEthereumTransferLazyCreate() { })); } + @HapiTest final HapiSpec lazyCreateViaEthereumCryptoTransfer() { final var RECIPIENT_KEY = LAZY_ACCOUNT_RECIPIENT; final var lazyCreateTxn = PAY_TXN; @@ -1062,7 +1077,8 @@ final HapiSpec lazyCreateViaEthereumCryptoTransfer() { })); } - final HapiSpec hollowAccountCompletionWithSimultaniousPropertiesUpdate() { + @HapiTest + final HapiSpec hollowAccountCompletionWithSimultaneousPropertiesUpdate() { return propertyPreservingHapiSpec("hollowAccountCompletionWithSimultaniousPropertiesUpdate") .preserving(LAZY_CREATION_ENABLED) .given( @@ -1092,14 +1108,13 @@ final HapiSpec hollowAccountCompletionWithSimultaniousPropertiesUpdate() { .then(withOpContext((spec, opLog) -> { final var op2 = fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(LAZY_CREATION_ENABLED, "" + FALSE)) - .deferStatusResolution(); + .overridingProps(Map.of(LAZY_CREATION_ENABLED, "" + FALSE)); final var op3 = cryptoTransfer( tinyBarsFromTo(LAZY_CREATE_SPONSOR, CRYPTO_TRANSFER_RECEIVER, ONE_HUNDRED_HBARS)) .payingWith(SECP_256K1_SOURCE_KEY) .sigMapPrefixes(uniqueWithFullPrefixesFor(SECP_256K1_SOURCE_KEY)) - .hasPrecheck(OK) + .hasPrecheckFrom(OK, INVALID_SIGNATURE) .hasKnownStatus(INVALID_PAYER_SIGNATURE) .via(TRANSFER_TXN_2); diff --git a/hedera-node/test-clients/src/main/resource/bootstrap.properties b/hedera-node/test-clients/src/main/resource/bootstrap.properties index 0650df1c2b59..83ef79f71414 100644 --- a/hedera-node/test-clients/src/main/resource/bootstrap.properties +++ b/hedera-node/test-clients/src/main/resource/bootstrap.properties @@ -52,7 +52,7 @@ hedera.workflows.enabled= accounts.maxNumber=20000000 autoCreation.enabled=true lazyCreation.enabled=true -cryptoCreateWithAlias.enabled=false +cryptoCreateWithAlias.enabled=true entities.limitTokenAssociations=false balances.exportDir.path=/opt/hgcapp/accountBalances/ balances.exportEnabled=false From 862912b656bd5439f0ae8387e2b7978bfe2c237f Mon Sep 17 00:00:00 2001 From: Valentin Valkanov Date: Wed, 20 Dec 2023 20:58:59 +0200 Subject: [PATCH 19/80] fix: getErc20TokenNameExceedingLimits and getErc721TokenURIFromErc20TokenFails tests (#10568) Signed-off-by: Valentin Valkanov --- .../failure/CustomExceptionalHaltReason.java | 6 ++++++ .../hts/tokenuri/TokenUriCall.java | 8 ++++++++ .../hts/tokenuri/TokenUriCallTest.java | 17 +++++++++++++++++ .../contract/precompile/ERCPrecompileSuite.java | 1 + .../suites/leaky/LeakyContractTestsSuite.java | 1 + 5 files changed, 33 insertions(+) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java index 29483b6ab390..e28f2957ef59 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/CustomExceptionalHaltReason.java @@ -20,6 +20,7 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import edu.umd.cs.findbugs.annotations.NonNull; +import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; public enum CustomExceptionalHaltReason implements ExceptionalHaltReason { @@ -76,6 +77,11 @@ public static ResponseCodeEnum statusFor(@NonNull final ExceptionalHaltReason re public static String errorMessageFor(@NonNull final ExceptionalHaltReason reason) { requireNonNull(reason); + // #10568 - We add this check to match mono behavior + if (reason == CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS) { + return Bytes.of(ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED.name().getBytes()) + .toHexString(); + } return reason.toString(); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java index 108e515cd91f..beb01ec40a45 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/tokenuri/TokenUriCall.java @@ -16,9 +16,12 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -49,6 +52,11 @@ public TokenUriCall( @Override protected @NonNull FullResult resultOfViewingNft(@NonNull final Token token, final Nft nft) { requireNonNull(token); + // #10568 - We add this check to match mono behavior + if (token.tokenType() == TokenType.FUNGIBLE_COMMON) { + return revertResult(ResponseCodeEnum.INVALID_TOKEN_ID, gasCalculator.viewGasRequirement()); + } + String metadata; if (nft != null) { metadata = new String(nft.metadata().toByteArray()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java index af567762bcac..0eb4917aed5b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokenuri/TokenUriCallTest.java @@ -17,12 +17,14 @@ package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.tokenuri; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CIVILIAN_OWNED_NFT; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NFT_SERIAL_NO; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN_ID; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; +import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenuri.TokenUriTranslator; import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; @@ -50,4 +52,19 @@ void returnsUnaliasedOwnerLongZeroForPresentTokenAndNonTreasuryNft() { .array()), result.getOutput()); } + + @Test + void revertsWhenTokenIsNotERC721() { + // given + subject = new TokenUriCall(gasCalculator, mockEnhancement(), FUNGIBLE_TOKEN, NFT_SERIAL_NO); + given(nativeOperations.getNft(FUNGIBLE_TOKEN.tokenId().tokenNum(), NFT_SERIAL_NO)) + .willReturn(null); + + // when + final var result = subject.execute().fullResult().result(); + + // then + assertEquals(MessageFrame.State.REVERT, result.getState()); + assertEquals(Bytes.wrap(ResponseCodeEnum.INVALID_TOKEN_ID.name().getBytes()), result.getOutput()); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java index bb2bb3e81e55..13901687365f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java @@ -1193,6 +1193,7 @@ ERC_721_CONTRACT, OWNER_OF, asHeadlongAddress(tokenAddr.get()), BigInteger.ONE) } // Expects revert + @HapiTest final HapiSpec getErc721TokenURIFromErc20TokenFails() { final var invalidTokenURITxn = "tokenURITxnFromErc20"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 3683261a8e02..77022b60e0c3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -829,6 +829,7 @@ final HapiSpec transferFailsWithIncorrectAmounts() { childRecordsCheck(transferTokenWithNegativeAmountTxn, CONTRACT_REVERT_EXECUTED)); } + @HapiTest final HapiSpec getErc20TokenNameExceedingLimits() { final var REDUCED_NETWORK_FEE = 1L; final var REDUCED_NODE_FEE = 1L; From 740b25b0c71e77ba1b3133c39683299d281d4465 Mon Sep 17 00:00:00 2001 From: Maxi Tartaglia <152629744+mxtartaglia-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 16:10:17 -0300 Subject: [PATCH 20/80] fix: 10535 add enum converter (#10557) Signed-off-by: Maxi Tartaglia --- .../config/impl/converters/EnumConverter.java | 57 ++++++++ .../impl/internal/ConverterService.java | 44 +++--- .../impl/converters/EnumConverterTest.java | 90 ++++++++++++ .../impl/internal/ConverterServiceTest.java | 130 ++++++++++++++++++ 4 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java create mode 100644 platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java create mode 100644 platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java new file mode 100644 index 000000000000..f41f5d8fc1ae --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/converters/EnumConverter.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.config.impl.converters; + +import com.swirlds.config.api.converter.ConfigConverter; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; + +/** + * Implementation of {@code ConfigConverter} specifically for converting enums + */ +public class EnumConverter> implements ConfigConverter { + + private final Class valueType; + + /** + * @param valueType enum type this converter is associated to + * @throws NullPointerException if {@code valueType} is {@code null} + */ + public EnumConverter(@NonNull Class valueType) { + this.valueType = Objects.requireNonNull(valueType, "valueType must not be null"); + } + + /** + * @param value String value to be converted to a valid enum instance + * @return the enum instance associated with {@code value} + * @throws IllegalArgumentException if value is not a valid enum value for {@code valueType} + * @throws NullPointerException if {@code value} is {@code null} + */ + @Nullable + @Override + public T convert(@NonNull String value) throws IllegalArgumentException, NullPointerException { + try { + return Enum.valueOf(this.valueType, Objects.requireNonNull(value, "value must not be null")); + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException( + "Can not convert value '%s' of Enum '%s' by default. Please add a custom config converter." + .formatted(value, this.valueType), + e); + } + } +} diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java index dbadb477e607..9fe15e26e24e 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConverterService.java @@ -24,6 +24,7 @@ import com.swirlds.config.impl.converters.ChronoUnitConverter; import com.swirlds.config.impl.converters.DoubleConverter; import com.swirlds.config.impl.converters.DurationConverter; +import com.swirlds.config.impl.converters.EnumConverter; import com.swirlds.config.impl.converters.FileConverter; import com.swirlds.config.impl.converters.FloatConverter; import com.swirlds.config.impl.converters.IntegerConverter; @@ -47,9 +48,9 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; class ConverterService implements ConfigLifecycle { private final Map, ConfigConverter> converters; @@ -94,7 +95,7 @@ class ConverterService implements ConfigLifecycle { private static final ConfigConverter CHRONO_UNIT_CONVERTER = new ChronoUnitConverter(); ConverterService() { - this.converters = new HashMap<>(); + this.converters = new ConcurrentHashMap<>(); } @NonNull @@ -115,7 +116,6 @@ private > Class getConverterType(@NonNull fin .orElseGet(() -> getConverterType((Class) converterClass.getSuperclass())); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Nullable T convert(@Nullable final String value, @NonNull final Class targetClass) { throwIfNotInitialized(); @@ -126,20 +126,7 @@ T convert(@Nullable final String value, @NonNull final Class targetClass) if (Objects.equals(targetClass, String.class)) { return (T) value; } - final ConfigConverter converter = (ConfigConverter) converters.get(targetClass); - - if (converter == null && targetClass.isEnum()) { - // FUTURE WORK: once logging is added to this module, log a warning here - // ("No converter defined for type '" + targetClass + "'. Converting using backup enum converter."); - try { - return (T) Enum.valueOf((Class) targetClass, value); - } catch (final IllegalArgumentException e) { - throw new IllegalArgumentException( - "Can not convert value '%s' of Enum '%s' by default. Please add a custom config converter." - .formatted(value, targetClass), - e); - } - } + final ConfigConverter converter = getOrAdConverter(targetClass); if (converter == null) { throw new IllegalArgumentException("No converter defined for type '" + targetClass + "'"); @@ -216,11 +203,30 @@ public boolean isInitialized() { return initialized; } - @SuppressWarnings("unchecked") @Nullable ConfigConverter getConverterForType(@NonNull final Class valueType) { throwIfNotInitialized(); Objects.requireNonNull(valueType, "valueType must not be null"); - return (ConfigConverter) converters.get(valueType); + return getOrAdConverter(valueType); + } + + /** + * @param valueType type to convert to + * @return + *
    + *
  • the previously configured {@code ConfigConverter} if exist for {@code valueType}
  • + *
  • a new instance of {@code EnumConverter} if {@code valueType} is an enum + * and no {@code ConfigConverter} was found
  • + *
  • {@code null} otherwise
  • + *
+ */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private ConfigConverter getOrAdConverter(@NonNull Class valueType) { + ConfigConverter converter = (ConfigConverter) converters.get(valueType); + + if (converter == null && valueType.isEnum()) { + return (ConfigConverter) converters.computeIfAbsent(valueType, c -> new EnumConverter(c)); + } + return converter; } } diff --git a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java new file mode 100644 index 000000000000..5cc3ab549585 --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/converters/EnumConverterTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.config.impl.converters; + +import java.util.function.Supplier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class EnumConverterTest { + + @Test + public void itFailsWhenConstructingWithNull() { + // given + Supplier> supplier = () -> new EnumConverter<>(null); + // then + Assertions.assertThrows(NullPointerException.class, supplier::get); + } + + @Test + public void itFailsWhenConvertingNull() { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + // then + Assertions.assertThrows(NullPointerException.class, () -> converter.convert(null)); + } + + @Test + public void itSucceedsWhenConvertingAValidEnumValue() { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + + // when + final NumberEnum value = converter.convert("ONE"); + + // then + Assertions.assertEquals(NumberEnum.ONE, value); + } + + @Test + public void itFailsWhenConvertingInvalidEnumValue() { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + + // then + Assertions.assertThrows(IllegalArgumentException.class, () -> converter.convert("")); + } + + @Test + void itSuccessfullyConvertsValidEnumValueWithSpecialChar() { + // given + final EnumConverter converter = new EnumConverter<>(SpecialCharacterEnum.class); + + // then: + Assertions.assertEquals(SpecialCharacterEnum.Ñ, converter.convert("Ñ")); + } + + @ParameterizedTest + @ValueSource(strings = {"One", "one", "onE", "oNe", " ONE", "ONE ", "DOS", "OnE", "null"}) + void itFailsConvertingInvalidEnumValues(final String param) { + // given + final EnumConverter converter = new EnumConverter<>(NumberEnum.class); + // then + Assertions.assertThrows(IllegalArgumentException.class, () -> converter.convert(param)); + } + + private enum NumberEnum { + ONE, + TWO + } + + private enum SpecialCharacterEnum { + Ñ, + } +} diff --git a/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java new file mode 100644 index 000000000000..453e23dc95f4 --- /dev/null +++ b/platform-sdk/swirlds-config-impl/src/test/java/com/swirlds/config/impl/internal/ConverterServiceTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.config.impl.internal; + +import com.swirlds.config.api.Configuration; +import com.swirlds.config.api.ConfigurationBuilder; +import com.swirlds.config.api.converter.ConfigConverter; +import com.swirlds.config.extensions.sources.SimpleConfigSource; +import com.swirlds.config.impl.converters.EnumConverter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConverterServiceTest { + + private ConverterService converterService; + + @BeforeEach + public void setUp() { + converterService = new ConverterService(); + converterService.init(); + } + + @Test + void itSuccessfullyCreatesAndReturnsAConverter() { + // given + final ConverterService cs = converterService; + + ConfigConverter converterForType = cs.getConverterForType(NumberEnum.class); + // then: + Assertions.assertNotNull(converterForType); + Assertions.assertInstanceOf(EnumConverter.class, converterForType); + } + + @Test + void itDoesNotCreateANewInstanceForAlreadyCachedConverters() { + // given + final ConverterService cs = converterService; + ConfigConverter converter = cs.getConverterForType(NumberEnum.class); + + // then: + Assertions.assertSame(converter, cs.getConverterForType(NumberEnum.class)); + } + + @Test + void itSuccessfullyUsesConfiguredEnumConverterAboveDefaultConverter() { + // given + ConverterService cs = new ConverterService(); + FakeEnumConverter converter = new FakeEnumConverter(); + cs.addConverter(converter); // creates a new enumConverter for NumberAndValueEnum + cs.init(); + + // then: + // NumberValueEnum gets converted with FakeEnumConverter + Assertions.assertSame(converter, cs.getConverterForType(NumberAndValueEnum.class)); + Assertions.assertEquals(NumberAndValueEnum.ONE, cs.convert("ONE", NumberAndValueEnum.class)); + Assertions.assertEquals(NumberAndValueEnum.ONE, cs.convert("", NumberAndValueEnum.class)); + Assertions.assertEquals(NumberAndValueEnum.ONE, cs.convert("DOS", NumberAndValueEnum.class)); + Assertions.assertInstanceOf(FakeEnumConverter.class, cs.getConverterForType(NumberAndValueEnum.class)); + // and: + // NumberEnum stills gets converted with defaultEnumConverter + Assertions.assertEquals(NumberEnum.ONE, cs.convert("ONE", NumberEnum.class)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cs.convert("", NumberEnum.class)); + Assertions.assertThrows(IllegalArgumentException.class, () -> cs.convert("DOS", NumberEnum.class)); + Assertions.assertInstanceOf(EnumConverter.class, cs.getConverterForType(NumberEnum.class)); + } + + @Test + void testIntegration() { + + // given + final Configuration configuration = ConfigurationBuilder.create() + .withSource(new SimpleConfigSource("plain-value", "ONE")) + .withSource(new SimpleConfigSource("complex-value-one", "ONE")) + .withSource(new SimpleConfigSource("complex-value-two", "DOS")) + .withSource(new SimpleConfigSource("complex-value-three", "THREE")) + .build(); + + // when + final NumberEnum uno = configuration.getValue("plain-value", NumberEnum.class); + final NumberAndValueEnum one = configuration.getValue("complex-value-one", NumberAndValueEnum.class); + + // then + Assertions.assertEquals(uno, NumberEnum.ONE); + Assertions.assertEquals(one, NumberAndValueEnum.ONE); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> configuration.getValue("complex-value-two", NumberAndValueEnum.class)); + Assertions.assertThrows( + IllegalArgumentException.class, + () -> configuration.getValue("complex-value-three", NumberAndValueEnum.class)); + } + + /** + * An enum converter that always returns the same constant + */ + private static class FakeEnumConverter implements ConfigConverter { + @Override + public NumberAndValueEnum convert(String value) { + return NumberAndValueEnum.ONE; + } + } + + private enum NumberEnum { + ONE + } + + private enum NumberAndValueEnum { + ONE(1); + final int value; + + NumberAndValueEnum(int value) { + this.value = value; + } + } +} From 73c6397778fcd767b8a30abcd7a0d1b74412520c Mon Sep 17 00:00:00 2001 From: Ivo Yankov Date: Wed, 20 Dec 2023 22:21:55 +0200 Subject: [PATCH 21/80] fix: Resolve EthereumSuite Errors (#10230) Signed-off-by: Ivo Yankov --- .../app/throttle/ThrottleAccumulator.java | 9 +- .../SingleTransactionRecordBuilderImpl.java | 5 ++ .../exec/ContextTransactionProcessor.java | 2 +- .../impl/exec/TransactionProcessor.java | 31 ++++++- .../operations/BasicCustomCallOperation.java | 1 + .../handlers/EthereumTransactionHandler.java | 44 +++++++--- .../impl/hevm/HederaEvmTransactionResult.java | 4 + .../EthereumTransactionRecordBuilder.java | 3 + .../test/exec/TransactionProcessorTest.java | 18 ++++ .../EthereumTransactionHandlerTest.java | 85 ++++++++++++++++--- .../bdd/suites/ethereum/EthereumSuite.java | 69 ++++++++++++++- .../src/main/resource/spec-default.properties | 2 +- 12 files changed, 243 insertions(+), 30 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java index 506790873505..5a4f2a443a87 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java @@ -80,6 +80,7 @@ import java.util.EnumSet; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.IntSupplier; import org.apache.commons.lang3.tuple.Pair; @@ -512,9 +513,11 @@ private long getGasLimitForContractTx( return switch (function) { case CONTRACT_CREATE -> txn.contractCreateInstance().gas(); case CONTRACT_CALL -> txn.contractCall().gas(); - case ETHEREUM_TRANSACTION -> EthTxData.populateEthTxData( - txn.ethereumTransaction().ethereumData().toByteArray()) - .gasLimit(); + case ETHEREUM_TRANSACTION -> Optional.of( + txn.ethereumTransactionOrThrow().ethereumData().toByteArray()) + .map(EthTxData::populateEthTxData) + .map(EthTxData::gasLimit) + .orElse(0L); default -> 0L; }; } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index ca2fc4c9f52a..ab17a05b9cc4 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -1128,4 +1128,9 @@ private TransactionBody inProgressBody() { throw new IllegalStateException("Record being built for unparseable transaction", e); } } + + public EthereumTransactionRecordBuilder feeChargedToPayer(@NonNull long amount) { + transactionRecordBuilder.transactionFee(transactionFee + amount); + return this; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index 2254640bb1f8..9f003f98fa94 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -58,7 +58,7 @@ public class ContextTransactionProcessor implements Callable { private final ActionSidecarContentTracer tracer; private final RootProxyWorldUpdater rootProxyWorldUpdater; - private final HevmTransactionFactory hevmTransactionFactory; + public final HevmTransactionFactory hevmTransactionFactory; private final Supplier feesOnlyUpdater; private final Map processors; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index 2434363ca9f4..b1c3f1130d6e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -26,8 +26,10 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Duration; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; +import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; @@ -37,6 +39,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; @@ -205,10 +208,34 @@ private InvolvedParties computeInvolvedParties( if (transaction.isCreate()) { final Address to; if (transaction.isEthereumTransaction()) { + final ReadableAccountStore accountStore = + updater.enhancement().nativeOperations().readableAccountStore(); + var createBody = requireNonNull(transaction.hapiCreation()); + final var modifiedBodyBuilder = createBody.copyBuilder(); + + final Account sponsor = requireNonNull(accountStore.getAccountById(transaction.senderId())); + if (sponsor.memo() != null) { + modifiedBodyBuilder.memo(sponsor.memo()); + } + if (sponsor.autoRenewAccountId() != null) { + modifiedBodyBuilder.autoRenewAccountId(sponsor.autoRenewAccountId()); + } + if (sponsor.stakedAccountId() != null) { + modifiedBodyBuilder.stakedAccountId(sponsor.stakedAccountId()); + } + if (sponsor.autoRenewSeconds() > 0) { + modifiedBodyBuilder.autoRenewPeriod(Duration.newBuilder() + .seconds(sponsor.autoRenewSeconds()) + .build()); + } + modifiedBodyBuilder.maxAutomaticTokenAssociations(sponsor.maxAutoAssociations()); + modifiedBodyBuilder.declineReward(sponsor.declineReward()); + + final var modifiedBody = modifiedBodyBuilder.build(); to = Address.contractAddress(sender.getAddress(), sender.getNonce()); - updater.setupAliasedTopLevelCreate(requireNonNull(transaction.hapiCreation()), to); + updater.setupAliasedTopLevelCreate(modifiedBody, to); } else { - to = updater.setupTopLevelCreate(requireNonNull(transaction.hapiCreation())); + to = updater.setupTopLevelCreate(transaction.hapiCreation()); } parties = new InvolvedParties(sender, relayer, to); } else { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java index f7c392fd8a0a..ad4f64659954 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/BasicCustomCallOperation.java @@ -83,6 +83,7 @@ default Operation.OperationResult executeChecked(@NonNull final MessageFrame fra requireNonNull(frame); try { final var address = to(frame); + if (addressChecks().isNeitherSystemNorPresent(address, frame)) { return new Operation.OperationResult(cost(frame), INVALID_SOLIDITY_ADDRESS); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index 68dc3828be59..34e7f698da84 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -17,8 +17,13 @@ package com.hedera.node.app.service.contract.impl.handlers; import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.throwIfUnsuccessful; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; +import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.HederaFunctionality; @@ -30,15 +35,18 @@ import com.hedera.node.app.service.contract.impl.records.EthereumTransactionRecordBuilder; import com.hedera.node.app.service.file.ReadableFileStore; import com.hedera.node.app.service.mono.fees.calculation.ethereum.txns.EthereumTransactionResourceUsage; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; +import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.TransactionHandler; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; @@ -64,18 +72,20 @@ public EthereumTransactionHandler( } @Override - public void preHandle(@NonNull final PreHandleContext context) { + public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { requireNonNull(context); final var body = context.body().ethereumTransactionOrThrow(); final var fileStore = context.createStore(ReadableFileStore.class); final var hederaConfig = context.configuration().getConfigData(HederaConfig.class); - final var ethTxData = callDataHydration - .tryToHydrate(body, fileStore, hederaConfig.firstUserEntity()) - .ethTxData(); - if (ethTxData != null) { - // Ignore the return value; we just want to cache the signature for use in handle() - ethereumSignatures.computeIfAbsent(ethTxData); - } + final var hydratedTx = callDataHydration.tryToHydrate(body, fileStore, hederaConfig.firstUserEntity()); + + validateTruePreCheck(hydratedTx.status() == OK, hydratedTx.status()); + + final var ethTxData = hydratedTx.ethTxData(); + validateTruePreCheck(ethTxData != null, INVALID_ETHEREUM_TRANSACTION); + + // Ignore the return value; we just want to cache the signature for use in handle() + ethereumSignatures.computeIfAbsent(ethTxData); } @Override @@ -83,15 +93,24 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Create the transaction-scoped component final var component = provider.get().create(context, ETHEREUM_TRANSACTION); - // Run its in-scope transaction and get the outcome - final var outcome = component.contextTransactionProcessor().call(); + final var hevmTransactionFactory = component.contextTransactionProcessor().hevmTransactionFactory; + final var hevmTransaction = hevmTransactionFactory.fromHapiTransaction(context.body()); + + final var accountStore = context.readableStore(ReadableAccountStore.class); + final var sender = accountStore.getAccountById(Objects.requireNonNull(hevmTransaction.senderId())); // Assemble the appropriate top-level record for the result final var ethTxData = requireNonNull(requireNonNull(component.hydratedEthTxData()).ethTxData()); + + // Run its in-scope transaction and get the outcome + final var outcome = component.contextTransactionProcessor().call(); + final var recordBuilder = context.recordBuilder(EthereumTransactionRecordBuilder.class) .ethereumHash(Bytes.wrap(ethTxData.getEthereumHash())) - .status(outcome.status()); + .status(outcome.status()) + .feeChargedToPayer(outcome.tinybarGasCost()); + if (ethTxData.hasToAddress()) { // The Ethereum transaction was a top-level MESSAGE_CALL recordBuilder.contractID(outcome.recipientId()).contractCallResult(outcome.result()); @@ -99,6 +118,9 @@ public void handle(@NonNull final HandleContext context) throws HandleException // The Ethereum transaction was a top-level CONTRACT_CREATION recordBuilder.contractID(outcome.recipientIdIfCreated()).contractCreateResult(outcome.result()); } + + validateTrue(sender.ethereumNonce() == ethTxData.nonce(), WRONG_NONCE); + throwIfUnsuccessful(outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java index 669234fe96a6..f98177de6754 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CONTRACT_STORAGE_EXCEEDED; @@ -75,6 +76,7 @@ public record HederaEvmTransactionResult( private static final Bytes INSUFFICIENT_GAS_REASON = Bytes.wrap(INSUFFICIENT_GAS.name()); private static final Bytes INVALID_CONTRACT_REASON = Bytes.wrap(INVALID_CONTRACT_ID.name()); private static final Bytes MAX_CHILD_RECORDS_EXCEEDED_REASON = Bytes.wrap(MAX_CHILD_RECORDS_EXCEEDED.name()); + private static final Bytes INSUFFICIENT_TX_FEE_REASON = Bytes.wrap(INSUFFICIENT_TX_FEE.name()); /** * Converts this result to a {@link ContractFunctionResult} for a transaction based on the given @@ -141,6 +143,8 @@ public ResponseCodeEnum finalStatus() { return INVALID_CONTRACT_ID; } else if (revertReason.equals(MAX_CHILD_RECORDS_EXCEEDED_REASON)) { return MAX_CHILD_RECORDS_EXCEEDED; + } else if (revertReason.equals(INSUFFICIENT_TX_FEE_REASON)) { + return INSUFFICIENT_TX_FEE; } else { return CONTRACT_REVERT_EXECUTED; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java index b7c61de2fe66..d9d28278e77e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java @@ -68,4 +68,7 @@ public interface EthereumTransactionRecordBuilder { */ @NonNull EthereumTransactionRecordBuilder ethereumHash(@NonNull Bytes ethereumHash); + + @NonNull + EthereumTransactionRecordBuilder feeChargedToPayer(@NonNull long amount); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java index 4069f4e4fc95..0a9f1a8ed186 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java @@ -58,12 +58,14 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; +import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.contract.impl.exec.FrameRunner; import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; +import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; @@ -72,6 +74,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; @@ -136,6 +139,15 @@ class TransactionProcessorTest { @Mock private CustomGasCharging gasCharging; + @Mock + private HederaWorldUpdater.Enhancement enhancement; + + @Mock + private HederaNativeOperations nativeOperations; + + @Mock + private ReadableAccountStore readableAccountStore; + private TransactionProcessor subject; @BeforeEach @@ -272,6 +284,12 @@ void ethCreateHappyPathAsExpected() { messageCallProcessor, contractCreationProcessor)) .willReturn(SUCCESS_RESULT); + given(worldUpdater.enhancement()).willReturn(enhancement); + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(nativeOperations.readableAccountStore()).willReturn(readableAccountStore); + final var parsedAccount = + Account.newBuilder().accountId(senderAccount.hederaId()).build(); + given(readableAccountStore.getAccountById(SENDER_ID)).willReturn(parsedAccount); given(initialFrame.getSelfDestructs()).willReturn(Set.of(NON_SYSTEM_LONG_ZERO_ADDRESS)); final var result = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index 59785bc0ade3..aab524374339 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -19,33 +19,48 @@ import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_038; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITHOUT_TO_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_TO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.HEVM_CREATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SUCCESS_RESULT; +import static com.hedera.node.app.spi.fixtures.Assertions.assertThrowsPreCheck; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.contract.EthereumTransactionBody; +import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.CallOutcome; import com.hedera.node.app.service.contract.impl.exec.ContextTransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.TransactionComponent; +import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; import com.hedera.node.app.service.contract.impl.handlers.EthereumTransactionHandler; +import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; +import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; +import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; +import com.hedera.node.app.service.contract.impl.infra.HevmTransactionFactory; import com.hedera.node.app.service.contract.impl.records.EthereumTransactionRecordBuilder; import com.hedera.node.app.service.contract.impl.state.RootProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.test.TestHelpers; import com.hedera.node.app.service.file.ReadableFileStore; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; +import com.hedera.node.config.data.ContractsConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -75,15 +90,30 @@ class EthereumTransactionHandlerTest { @Mock private TransactionComponent.Factory factory; - @Mock - private ContextTransactionProcessor processor; - @Mock private EthereumTransactionRecordBuilder recordBuilder; @Mock private RootProxyWorldUpdater baseProxyWorldUpdater; + @Mock + private HevmTransactionFactory hevmTransactionFactory; + + @Mock + private HederaEvmContext hederaEvmContext; + + @Mock + private ActionSidecarContentTracer tracer; + + @Mock + private Supplier feesOnlyUpdater; + + @Mock + private TransactionProcessor transactionProcessor; + + @Mock + private ReadableAccountStore readableAccountStore; + private EthereumTransactionHandler subject; @BeforeEach @@ -91,23 +121,56 @@ void setUp() { subject = new EthereumTransactionHandler(ethereumSignatures, callDataHydration, () -> factory); } + void setUpTransactionProcessing() { + final var contractsConfig = DEFAULT_CONFIG.getConfigData(ContractsConfig.class); + final var processors = Map.of(VERSION_038, transactionProcessor); + + final var contextTransactionProcessor = new ContextTransactionProcessor( + HydratedEthTxData.successFrom(ETH_DATA_WITH_TO_ADDRESS), + handleContext, + contractsConfig, + DEFAULT_CONFIG, + hederaEvmContext, + tracer, + baseProxyWorldUpdater, + hevmTransactionFactory, + feesOnlyUpdater, + processors); + + given(component.contextTransactionProcessor()).willReturn(contextTransactionProcessor); + given(hevmTransactionFactory.fromHapiTransaction(handleContext.body())).willReturn(HEVM_CREATION); + + final AccountID senderId = HEVM_CREATION.senderId(); + final var parsedAccount = Account.newBuilder().accountId(senderId).build(); + + given(handleContext.readableStore(ReadableAccountStore.class)).willReturn(readableAccountStore); + given(readableAccountStore.getAccountById(HEVM_CREATION.senderId())).willReturn(parsedAccount); + given(transactionProcessor.processTransaction( + HEVM_CREATION, + baseProxyWorldUpdater, + feesOnlyUpdater, + hederaEvmContext, + tracer, + DEFAULT_CONFIG)) + .willReturn(SUCCESS_RESULT); + } + @Test void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { given(factory.create(handleContext, ETHEREUM_TRANSACTION)).willReturn(component); given(component.hydratedEthTxData()).willReturn(HydratedEthTxData.successFrom(ETH_DATA_WITH_TO_ADDRESS)); - given(component.contextTransactionProcessor()).willReturn(processor); + setUpTransactionProcessing(); given(handleContext.recordBuilder(EthereumTransactionRecordBuilder.class)) .willReturn(recordBuilder); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome( expectedResult, SUCCESS_RESULT.finalStatus(), CALLED_CONTRACT_ID, SUCCESS_RESULT.gasPrice()); - given(processor.call()).willReturn(expectedOutcome); - given(recordBuilder.status(SUCCESS)).willReturn(recordBuilder); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITH_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); + given(recordBuilder.feeChargedToPayer(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @@ -116,26 +179,26 @@ void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { void delegatesToCreatedComponentAndExposesEthTxDataCreateWithoutToAddress() { given(factory.create(handleContext, ETHEREUM_TRANSACTION)).willReturn(component); given(component.hydratedEthTxData()).willReturn(HydratedEthTxData.successFrom(ETH_DATA_WITHOUT_TO_ADDRESS)); - given(component.contextTransactionProcessor()).willReturn(processor); + setUpTransactionProcessing(); given(handleContext.recordBuilder(EthereumTransactionRecordBuilder.class)) .willReturn(recordBuilder); given(baseProxyWorldUpdater.getCreatedContractIds()).willReturn(List.of(CALLED_CONTRACT_ID)); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITHOUT_TO_ADDRESS, baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome(expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice()); - given(processor.call()).willReturn(expectedOutcome); given(recordBuilder.status(SUCCESS)).willReturn(recordBuilder); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITHOUT_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); + given(recordBuilder.feeChargedToPayer(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @Test - void preHandleCachesTheSignaturesIfDataCanBeHydrated() { + void preHandleCachesTheSignaturesIfDataCanBeHydrated() throws PreCheckException { final var ethTxn = EthereumTransactionBody.newBuilder() .ethereumData(TestHelpers.ETH_WITH_TO_ADDRESS) .build(); @@ -151,7 +214,7 @@ void preHandleCachesTheSignaturesIfDataCanBeHydrated() { } @Test - void preHandleIgnoresFailureToHydrate() { + void preHandleDoesNotIgnoreFailureToHydrate() throws PreCheckException { final var ethTxn = EthereumTransactionBody.newBuilder().ethereumData(Bytes.EMPTY).build(); final var body = @@ -161,7 +224,7 @@ void preHandleIgnoresFailureToHydrate() { given(preHandleContext.configuration()).willReturn(DEFAULT_CONFIG); given(callDataHydration.tryToHydrate(ethTxn, fileStore, 1001L)) .willReturn(HydratedEthTxData.failureFrom(INVALID_ETHEREUM_TRANSACTION)); - subject.preHandle(preHandleContext); + assertThrowsPreCheck(() -> subject.preHandle(preHandleContext), INVALID_ETHEREUM_TRANSACTION); verifyNoInteractions(ethereumSignatures); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java index 75952f7974de..d81948d85c45 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java @@ -165,7 +165,6 @@ public List getSpecsInSuite() { .toList(); } - @HapiTest @Disabled("Failing or intermittently failing HAPI Test") HapiSpec sendingLargerBalanceThanAvailableFailsGracefully() { final AtomicReference
tokenCreateContractAddress = new AtomicReference<>(); @@ -296,6 +295,66 @@ List feePaymentMatrix() { .toList(); } + @HapiTest + HapiSpec matrixedPayerRelayerTest1() { + return feePaymentMatrix().get(0); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest2() { + return feePaymentMatrix().get(1); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest3() { + return feePaymentMatrix().get(2); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest4() { + return feePaymentMatrix().get(3); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest5() { + return feePaymentMatrix().get(4); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest6() { + return feePaymentMatrix().get(5); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest7() { + return feePaymentMatrix().get(6); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest8() { + return feePaymentMatrix().get(7); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest9() { + return feePaymentMatrix().get(8); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest10() { + return feePaymentMatrix().get(9); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest11() { + return feePaymentMatrix().get(10); + } + + @HapiTest + HapiSpec matrixedPayerRelayerTest12() { + return feePaymentMatrix().get(11); + } + @BddTestNameDoesNotMatchMethodName HapiSpec matrixedPayerRelayerTest( final boolean success, final long senderGasPrice, final long relayerOffered, final long senderCharged) { @@ -349,6 +408,7 @@ HapiSpec matrixedPayerRelayerTest( })); } + @HapiTest HapiSpec invalidTxData() { return defaultHapiSpec("InvalidTxData") .given( @@ -423,6 +483,7 @@ HapiSpec etx014ContractCreateInheritsSignerProperties() { .memo(MEMO)))); } + @HapiTest HapiSpec etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { final var relayerSnapshot = "relayer"; final var senderSnapshot = "sender"; @@ -464,6 +525,7 @@ HapiSpec etx031InvalidNonceEthereumTxFailsAndChargesRelayer() { .has(accountWith().nonce(0L))); } + @HapiTest HapiSpec etx013PrecompileCallFailsWhenSignatureMissingFromBothEthereumAndHederaTxn() { final AtomicReference fungible = new AtomicReference<>(); final String fungibleToken = TOKEN; @@ -514,6 +576,7 @@ HELLO_WORLD_MINT_CONTRACT, asHeadlongAddress(asAddress(fungible.get())))), recordWith().status(INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE))); } + @HapiTest HapiSpec etx009CallsToTokenAddresses() { final AtomicReference tokenNum = new AtomicReference<>(); final var totalSupply = 50; @@ -711,6 +774,7 @@ HapiSpec etxSvc003ContractGetBytecodeQueryReturnsDeployedCode() { })); } + @HapiTest HapiSpec directTransferWorksForERC20() { final var tokenSymbol = "FDFGF"; final var tokenTotalSupply = 5; @@ -812,6 +876,7 @@ HapiSpec transferHbarsViaEip2930TxSuccessfully() { .hasTinyBars(changeFromSnapshot(aliasBalanceSnapshot, -FIVE_HBARS))); } + @HapiTest HapiSpec callToTokenAddressViaEip2930TxSuccessfully() { final AtomicReference tokenNum = new AtomicReference<>(); final var totalSupply = 50; @@ -856,6 +921,7 @@ HapiSpec callToTokenAddressViaEip2930TxSuccessfully() { .withTotalSupply(totalSupply))))); } + @HapiTest HapiSpec transferTokensViaEip2930TxSuccessfully() { final var tokenSymbol = "FDFGF"; final var tokenTotalSupply = 5; @@ -908,6 +974,7 @@ HapiSpec transferTokensViaEip2930TxSuccessfully() { .withErcFungibleTransferStatus(true))))))); } + @HapiTest HapiSpec callToNonExistingContractFailsGracefully() { return defaultHapiSpec("callToNonExistingContractFailsGracefully") diff --git a/hedera-node/test-clients/src/main/resource/spec-default.properties b/hedera-node/test-clients/src/main/resource/spec-default.properties index a9960be5c818..35b75f2e88dc 100644 --- a/hedera-node/test-clients/src/main/resource/spec-default.properties +++ b/hedera-node/test-clients/src/main/resource/spec-default.properties @@ -110,7 +110,7 @@ num.opFinisher.threads=8 persistentEntities.dir.path= persistentEntities.updateCreatedManifests=true spec.autoScheduledTxns= -spec.streamlinedIngestChecks=INVALID_FILE_ID,ENTITY_NOT_ALLOWED_TO_DELETE,AUTHORIZATION_FAILED,INVALID_PRNG_RANGE,INVALID_STAKING_ID,NOT_SUPPORTED,TOKEN_ID_REPEATED_IN_TOKEN_LIST,ALIAS_ALREADY_ASSIGNED,INVALID_ALIAS_KEY,KEY_REQUIRED,BAD_ENCODING,AUTORENEW_DURATION_NOT_IN_RANGE,INVALID_ZERO_BYTE_IN_STRING,INVALID_ADMIN_KEY,ACCOUNT_DELETED,BUSY,INSUFFICIENT_PAYER_BALANCE,INSUFFICIENT_TX_FEE,INVALID_ACCOUNT_ID,INVALID_NODE_ACCOUNT,INVALID_SIGNATURE,INVALID_TRANSACTION,INVALID_TRANSACTION_BODY,INVALID_TRANSACTION_DURATION,INVALID_TRANSACTION_ID,INVALID_TRANSACTION_START,KEY_PREFIX_MISMATCH,MEMO_TOO_LONG,PAYER_ACCOUNT_NOT_FOUND,PLATFORM_NOT_ACTIVE,TRANSACTION_EXPIRED,TRANSACTION_HAS_UNKNOWN_FIELDS,TRANSACTION_ID_FIELD_NOT_ALLOWED,TRANSACTION_OVERSIZE,TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT,EMPTY_ALLOWANCES,REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT,TOKEN_HAS_NO_FREEZE_KEY,TOKEN_HAS_NO_SUPPLY_KEY,INVALID_TOKEN_INITIAL_SUPPLY,INVALID_TOKEN_DECIMALS,INVALID_TOKEN_MAX_SUPPLY,ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS,TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN,INVALID_ACCOUNT_AMOUNTS,TOKEN_NAME_TOO_LONG,TOKEN_SYMBOL_TOO_LONG,INVALID_TOKEN_NFT_SERIAL_NUMBER,PERMANENT_REMOVAL_REQUIRES_SYSTEM_INITIATION,MISSING_TOKEN_SYMBOL,MISSING_TOKEN_NAME,INVALID_EXPIRATION_TIME,EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS,INVALID_ALLOWANCE_OWNER_ID,FUNGIBLE_TOKEN_IN_NFT_ALLOWANCES,TOKEN_NOT_ASSOCIATED_TO_ACCOUNT,MAX_ALLOWANCES_EXCEEDED,INVALID_ALLOWANCE_SPENDER_ID,AMOUNT_EXCEEDS_TOKEN_MAX_SUPPLY,NFT_IN_FUNGIBLE_TOKEN_ALLOWANCES,NEGATIVE_ALLOWANCE_AMOUNT,DELEGATING_SPENDER_DOES_NOT_HAVE_APPROVE_FOR_ALL,DELEGATING_SPENDER_CANNOT_GRANT_APPROVE_FOR_ALL,INVALID_TOKEN_MINT_AMOUNT,INVALID_TOKEN_BURN_AMOUNT,INVALID_WIPING_AMOUNT,INVALID_NFT_ID,BATCH_SIZE_LIMIT_EXCEEDED,METADATA_TOO_LONG,INVALID_RENEWAL_PERIOD,INVALID_CUSTOM_FEE_SCHEDULE_KEY,MAX_GAS_LIMIT_EXCEEDED,CONTRACT_DELETED +spec.streamlinedIngestChecks=INVALID_FILE_ID,ENTITY_NOT_ALLOWED_TO_DELETE,AUTHORIZATION_FAILED,INVALID_PRNG_RANGE,INVALID_STAKING_ID,NOT_SUPPORTED,TOKEN_ID_REPEATED_IN_TOKEN_LIST,ALIAS_ALREADY_ASSIGNED,INVALID_ALIAS_KEY,KEY_REQUIRED,BAD_ENCODING,AUTORENEW_DURATION_NOT_IN_RANGE,INVALID_ZERO_BYTE_IN_STRING,INVALID_ADMIN_KEY,ACCOUNT_DELETED,BUSY,INSUFFICIENT_PAYER_BALANCE,INSUFFICIENT_TX_FEE,INVALID_ACCOUNT_ID,INVALID_NODE_ACCOUNT,INVALID_SIGNATURE,INVALID_TRANSACTION,INVALID_TRANSACTION_BODY,INVALID_TRANSACTION_DURATION,INVALID_TRANSACTION_ID,INVALID_TRANSACTION_START,KEY_PREFIX_MISMATCH,MEMO_TOO_LONG,PAYER_ACCOUNT_NOT_FOUND,PLATFORM_NOT_ACTIVE,TRANSACTION_EXPIRED,TRANSACTION_HAS_UNKNOWN_FIELDS,TRANSACTION_ID_FIELD_NOT_ALLOWED,TRANSACTION_OVERSIZE,TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT,EMPTY_ALLOWANCES,REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT,TOKEN_HAS_NO_FREEZE_KEY,TOKEN_HAS_NO_SUPPLY_KEY,INVALID_TOKEN_INITIAL_SUPPLY,INVALID_TOKEN_DECIMALS,INVALID_TOKEN_MAX_SUPPLY,ACCOUNT_REPEATED_IN_ACCOUNT_AMOUNTS,TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN,INVALID_ACCOUNT_AMOUNTS,TOKEN_NAME_TOO_LONG,TOKEN_SYMBOL_TOO_LONG,INVALID_TOKEN_NFT_SERIAL_NUMBER,PERMANENT_REMOVAL_REQUIRES_SYSTEM_INITIATION,MISSING_TOKEN_SYMBOL,MISSING_TOKEN_NAME,INVALID_EXPIRATION_TIME,EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS,INVALID_ALLOWANCE_OWNER_ID,FUNGIBLE_TOKEN_IN_NFT_ALLOWANCES,TOKEN_NOT_ASSOCIATED_TO_ACCOUNT,MAX_ALLOWANCES_EXCEEDED,INVALID_ALLOWANCE_SPENDER_ID,AMOUNT_EXCEEDS_TOKEN_MAX_SUPPLY,NFT_IN_FUNGIBLE_TOKEN_ALLOWANCES,NEGATIVE_ALLOWANCE_AMOUNT,DELEGATING_SPENDER_DOES_NOT_HAVE_APPROVE_FOR_ALL,DELEGATING_SPENDER_CANNOT_GRANT_APPROVE_FOR_ALL,INVALID_TOKEN_MINT_AMOUNT,INVALID_TOKEN_BURN_AMOUNT,INVALID_WIPING_AMOUNT,INVALID_NFT_ID,BATCH_SIZE_LIMIT_EXCEEDED,METADATA_TOO_LONG,INVALID_RENEWAL_PERIOD,INVALID_CUSTOM_FEE_SCHEDULE_KEY,MAX_GAS_LIMIT_EXCEEDED,CONTRACT_DELETED,INVALID_ETHEREUM_TRANSACTION status.deferredResolves.doAsync=true status.preResolve.pause.ms=0 status.wait.sleep.ms=500 From 1d9aa274205d98c8878217ffbbe20ecd6b7e5dea Mon Sep 17 00:00:00 2001 From: Maxi Tartaglia <152629744+mxtartaglia-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 18:11:43 -0300 Subject: [PATCH 22/80] fix: 10558 remove AbstractEnumConfigConverter (#10559) Signed-off-by: Maxi Tartaglia --- .../config/BootstrapConfigProviderImpl.java | 2 - .../node/app/config/ConfigProviderImpl.java | 21 ++-- .../handle/SystemFileUpdateFacilityTest.java | 2 - .../config/converter/EntityTypeConverter.java | 37 ------ .../HederaFunctionalityConverter.java | 35 ------ .../converter/MapAccessTypeConverter.java | 36 ------ .../config/converter/ProfileConverter.java | 28 ----- .../converter/RecomputeTypeConverter.java | 37 ------ .../converter/SidecarTypeConverter.java | 37 ------ .../config/PropertySourceBasedConfigTest.java | 10 -- .../converter/EntityTypeConverterTest.java | 56 --------- .../HederaFunctionalityConverterTest.java | 59 ---------- .../converter/MapAccessTypeConverterTest.java | 56 --------- .../converter/ProfileConverterTest.java | 58 --------- .../converter/RecomputeTypeConverterTest.java | 56 --------- .../converter/SidecarTypeConverterTest.java | 59 ---------- .../testfixtures/HederaTestConfigBuilder.java | 12 -- .../build.gradle.kts | 2 - .../AbstractEnumConfigConverter.java | 47 -------- .../src/main/java/module-info.java | 1 - .../AbstractEnumConfigConverterTest.java | 110 ------------------ 21 files changed, 14 insertions(+), 747 deletions(-) delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java delete mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java delete mode 100644 platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java delete mode 100644 platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java index 4f10b5584595..a752185e0adc 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/BootstrapConfigProviderImpl.java @@ -20,7 +20,6 @@ import com.hedera.node.config.VersionedConfiguration; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.converter.LongPairConverter; -import com.hedera.node.config.converter.ProfileConverter; import com.hedera.node.config.converter.SemanticVersionConverter; import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.HederaConfig; @@ -60,7 +59,6 @@ public BootstrapConfigProviderImpl() { .withConfigDataType(LedgerConfig.class) .withConverter(new BytesConverter()) .withConverter(new SemanticVersionConverter()) - .withConverter(new ProfileConverter()) .withConverter(new LongPairConverter()); try { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java index d537ed845e75..0fe32dccc488 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java @@ -22,7 +22,20 @@ import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.VersionedConfiguration; -import com.hedera.node.config.converter.*; +import com.hedera.node.config.converter.AccountIDConverter; +import com.hedera.node.config.converter.BytesConverter; +import com.hedera.node.config.converter.CongestionMultipliersConverter; +import com.hedera.node.config.converter.ContractIDConverter; +import com.hedera.node.config.converter.EntityScaleFactorsConverter; +import com.hedera.node.config.converter.FileIDConverter; +import com.hedera.node.config.converter.FunctionalitySetConverter; +import com.hedera.node.config.converter.KeyValuePairConverter; +import com.hedera.node.config.converter.KnownBlockValuesConverter; +import com.hedera.node.config.converter.LegacyContractIdActivationsConverter; +import com.hedera.node.config.converter.LongPairConverter; +import com.hedera.node.config.converter.PermissionedAccountsRangeConverter; +import com.hedera.node.config.converter.ScaleFactorConverter; +import com.hedera.node.config.converter.SemanticVersionConverter; import com.hedera.node.config.data.AccountsConfig; import com.hedera.node.config.data.ApiPermissionConfig; import com.hedera.node.config.data.AutoCreationConfig; @@ -177,24 +190,18 @@ private ConfigurationBuilder createConfigurationBuilder() { .withConfigDataType(VersionConfig.class) .withConverter(new CongestionMultipliersConverter()) .withConverter(new EntityScaleFactorsConverter()) - .withConverter(new EntityTypeConverter()) .withConverter(new KnownBlockValuesConverter()) .withConverter(new LegacyContractIdActivationsConverter()) - .withConverter(new MapAccessTypeConverter()) - .withConverter(new RecomputeTypeConverter()) .withConverter(new ScaleFactorConverter()) .withConverter(new AccountIDConverter()) .withConverter(new ContractIDConverter()) .withConverter(new FileIDConverter()) - .withConverter(new HederaFunctionalityConverter()) .withConverter(new PermissionedAccountsRangeConverter()) - .withConverter(new SidecarTypeConverter()) .withConverter(new SemanticVersionConverter()) .withConverter(new LongPairConverter()) .withConverter(new KeyValuePairConverter()) .withConverter(new FunctionalitySetConverter()) .withConverter(new BytesConverter()) - .withConverter(new ProfileConverter()) .withValidator(new EmulatesMapValidator()); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java index 6a3ac2866b8b..6273d87e3a13 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java @@ -46,7 +46,6 @@ import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.converter.LongPairConverter; -import com.hedera.node.config.converter.ProfileConverter; import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; @@ -103,7 +102,6 @@ void setUp() { final var config = new TestConfigBuilder(false) .withConverter(new BytesConverter()) .withConverter(new LongPairConverter()) - .withConverter(new ProfileConverter()) .withConfigDataType(FilesConfig.class) .withConfigDataType(HederaConfig.class) .withConfigDataType(LedgerConfig.class) diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java deleted file mode 100644 index 5c3fa36d7690..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/EntityTypeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import com.hedera.node.app.service.mono.context.properties.EntityType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link EntityType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class EntityTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @NonNull - @Override - protected Class getEnumType() { - return EntityType.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java deleted file mode 100644 index 5fb31212e5bc..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/HederaFunctionalityConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import com.hedera.hapi.node.base.HederaFunctionality; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link HederaFunctionality}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class HederaFunctionalityConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @Override - protected Class getEnumType() { - return HederaFunctionality.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java deleted file mode 100644 index f7706ad11c3c..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/MapAccessTypeConverter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import com.hedera.node.app.service.mono.throttling.MapAccessType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link MapAccessType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class MapAccessTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - @NonNull - @Override - protected Class getEnumType() { - return MapAccessType.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java deleted file mode 100644 index a11a6f271d15..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/ProfileConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import com.hedera.node.config.types.Profile; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; - -public class ProfileConverter extends AbstractEnumConfigConverter implements ConfigConverter { - @Override - protected Class getEnumType() { - return Profile.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java deleted file mode 100644 index 397507dcb02e..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/RecomputeTypeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import com.hedera.node.app.service.mono.ledger.accounts.staking.StakeStartupHelper.RecomputeType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link RecomputeType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class RecomputeTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @NonNull - @Override - protected Class getEnumType() { - return RecomputeType.class; - } -} diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java deleted file mode 100644 index fc580d446231..000000000000 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/converter/SidecarTypeConverter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import com.hedera.hapi.streams.SidecarType; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Config api {@link ConfigConverter} implementation for the type {@link SidecarType}. Based on - * https://github.com/hashgraph/hedera-services/issues/6106 we need to add {@code implements ConfigConverter} to the - * class for now. - */ -public class SidecarTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @NonNull - @Override - protected Class getEnumType() { - return SidecarType.class; - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java index d6aaeb0aa38e..781cda6c3bc7 100644 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java +++ b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/PropertySourceBasedConfigTest.java @@ -37,16 +37,11 @@ import com.hedera.node.config.converter.CongestionMultipliersConverter; import com.hedera.node.config.converter.ContractIDConverter; import com.hedera.node.config.converter.EntityScaleFactorsConverter; -import com.hedera.node.config.converter.EntityTypeConverter; import com.hedera.node.config.converter.FileIDConverter; -import com.hedera.node.config.converter.HederaFunctionalityConverter; import com.hedera.node.config.converter.KnownBlockValuesConverter; import com.hedera.node.config.converter.LegacyContractIdActivationsConverter; -import com.hedera.node.config.converter.MapAccessTypeConverter; import com.hedera.node.config.converter.PermissionedAccountsRangeConverter; -import com.hedera.node.config.converter.RecomputeTypeConverter; import com.hedera.node.config.converter.ScaleFactorConverter; -import com.hedera.node.config.converter.SidecarTypeConverter; import com.hedera.node.config.sources.PropertySourceBasedConfigSource; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; @@ -96,18 +91,13 @@ void testConfig() { final Configuration configuration = ConfigurationBuilder.create() .withConverter(new CongestionMultipliersConverter()) .withConverter(new EntityScaleFactorsConverter()) - .withConverter(new EntityTypeConverter()) .withConverter(new KnownBlockValuesConverter()) .withConverter(new LegacyContractIdActivationsConverter()) - .withConverter(new MapAccessTypeConverter()) .withConverter(new PermissionedAccountsRangeConverter()) - .withConverter(new RecomputeTypeConverter()) .withConverter(new ScaleFactorConverter()) .withConverter(new AccountIDConverter()) .withConverter(new ContractIDConverter()) .withConverter(new FileIDConverter()) - .withConverter(new HederaFunctionalityConverter()) - .withConverter(new SidecarTypeConverter()) .withConverter(new BytesConverter()) .withSource(new PropertySourceBasedConfigSource(propertySource)) .build(); diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java deleted file mode 100644 index 04e7f2c920cf..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/EntityTypeConverterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.app.service.mono.context.properties.EntityType; -import org.junit.jupiter.api.Test; - -class EntityTypeConverterTest { - - @Test - void testNullParam() { - // given - final EntityTypeConverter converter = new EntityTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final EntityTypeConverter converter = new EntityTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testValidParam() { - // given - final EntityTypeConverter converter = new EntityTypeConverter(); - - // when - final EntityType entityType = converter.convert("ACCOUNT"); - - // then - assertThat(entityType).isEqualTo(EntityType.ACCOUNT); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java deleted file mode 100644 index a2c9f6a8130a..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/HederaFunctionalityConverterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.hapi.node.base.HederaFunctionality; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class HederaFunctionalityConverterTest { - - @Test - void testNullParam() { - // given - final HederaFunctionalityConverter converter = new HederaFunctionalityConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final HederaFunctionalityConverter converter = new HederaFunctionalityConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(HederaFunctionality.class) - void testValidParam(final HederaFunctionality functionality) { - // given - final HederaFunctionalityConverter converter = new HederaFunctionalityConverter(); - - // when - final HederaFunctionality cryptoTransfer = converter.convert(functionality.name()); - - // then - assertThat(cryptoTransfer).isEqualTo(functionality); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java deleted file mode 100644 index bd1f46aa1509..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/MapAccessTypeConverterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.app.service.mono.throttling.MapAccessType; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -class MapAccessTypeConverterTest { - - @Test - void testNullParam() { - // given - final MapAccessTypeConverter converter = new MapAccessTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final MapAccessTypeConverter converter = new MapAccessTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testValidParam() { - // given - final MapAccessTypeConverter converter = new MapAccessTypeConverter(); - - // when - final MapAccessType entityType = converter.convert("ACCOUNTS_GET"); - - // then - Assertions.assertThat(entityType).isEqualTo(MapAccessType.ACCOUNTS_GET); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java deleted file mode 100644 index a3d7c63ec753..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/ProfileConverterTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.config.types.Profile; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -public class ProfileConverterTest { - @Test - void testNullParam() { - // given - final ProfileConverter converter = new ProfileConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final ProfileConverter converter = new ProfileConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(Profile.class) - void testValidParam(final Profile value) { - // given - final ProfileConverter converter = new ProfileConverter(); - - // when - final Profile profile = converter.convert(value.name()); - - // then - assertThat(profile).isEqualTo(value); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java deleted file mode 100644 index d041646a78e7..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/RecomputeTypeConverterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.node.app.service.mono.ledger.accounts.staking.StakeStartupHelper.RecomputeType; -import org.junit.jupiter.api.Test; - -class RecomputeTypeConverterTest { - - @Test - void testNullParam() { - // given - final RecomputeTypeConverter converter = new RecomputeTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final RecomputeTypeConverter converter = new RecomputeTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @Test - void testValidParam() { - // given - final RecomputeTypeConverter converter = new RecomputeTypeConverter(); - - // when - final RecomputeType entityType = converter.convert("NODE_STAKES"); - - // then - assertThat(entityType).isEqualTo(RecomputeType.NODE_STAKES); - } -} diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java deleted file mode 100644 index fadd76b6a303..000000000000 --- a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/converter/SidecarTypeConverterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.config.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.hedera.hapi.streams.SidecarType; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class SidecarTypeConverterTest { - - @Test - void testNullParam() { - // given - final SidecarTypeConverter converter = new SidecarTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidParam() { - // given - final SidecarTypeConverter converter = new SidecarTypeConverter(); - - // then - assertThatThrownBy(() -> converter.convert("null")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(SidecarType.class) - void testValidParam(final SidecarType value) { - // given - final SidecarTypeConverter converter = new SidecarTypeConverter(); - - // when - final SidecarType sidecarType = converter.convert(value.name()); - - // then - assertThat(sidecarType).isEqualTo(value); - } -} diff --git a/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java b/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java index 40c4693a28f7..cd469b65770f 100644 --- a/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java +++ b/hedera-node/hedera-config/src/testFixtures/java/com/hedera/node/config/testfixtures/HederaTestConfigBuilder.java @@ -23,21 +23,15 @@ import com.hedera.node.config.converter.CongestionMultipliersConverter; import com.hedera.node.config.converter.ContractIDConverter; import com.hedera.node.config.converter.EntityScaleFactorsConverter; -import com.hedera.node.config.converter.EntityTypeConverter; import com.hedera.node.config.converter.FileIDConverter; import com.hedera.node.config.converter.FunctionalitySetConverter; -import com.hedera.node.config.converter.HederaFunctionalityConverter; import com.hedera.node.config.converter.KeyValuePairConverter; import com.hedera.node.config.converter.KnownBlockValuesConverter; import com.hedera.node.config.converter.LegacyContractIdActivationsConverter; import com.hedera.node.config.converter.LongPairConverter; -import com.hedera.node.config.converter.MapAccessTypeConverter; import com.hedera.node.config.converter.PermissionedAccountsRangeConverter; -import com.hedera.node.config.converter.ProfileConverter; -import com.hedera.node.config.converter.RecomputeTypeConverter; import com.hedera.node.config.converter.ScaleFactorConverter; import com.hedera.node.config.converter.SemanticVersionConverter; -import com.hedera.node.config.converter.SidecarTypeConverter; import com.hedera.node.config.data.AccountsConfig; import com.hedera.node.config.data.ApiPermissionConfig; import com.hedera.node.config.data.AutoCreationConfig; @@ -183,24 +177,18 @@ public static TestConfigBuilder create() { .withConfigDataType(VersionConfig.class) .withConverter(new CongestionMultipliersConverter()) .withConverter(new EntityScaleFactorsConverter()) - .withConverter(new EntityTypeConverter()) .withConverter(new KnownBlockValuesConverter()) .withConverter(new LegacyContractIdActivationsConverter()) - .withConverter(new MapAccessTypeConverter()) - .withConverter(new RecomputeTypeConverter()) .withConverter(new ScaleFactorConverter()) .withConverter(new AccountIDConverter()) .withConverter(new ContractIDConverter()) .withConverter(new FileIDConverter()) - .withConverter(new HederaFunctionalityConverter()) .withConverter(new PermissionedAccountsRangeConverter()) - .withConverter(new SidecarTypeConverter()) .withConverter(new SemanticVersionConverter()) .withConverter(new KeyValuePairConverter()) .withConverter(new LongPairConverter()) .withConverter(new FunctionalitySetConverter()) .withConverter(new BytesConverter()) - .withConverter(new ProfileConverter()) .withValidator(new EmulatesMapValidator()); } diff --git a/platform-sdk/swirlds-config-extensions/build.gradle.kts b/platform-sdk/swirlds-config-extensions/build.gradle.kts index 4c46442a5261..219658007bf1 100644 --- a/platform-sdk/swirlds-config-extensions/build.gradle.kts +++ b/platform-sdk/swirlds-config-extensions/build.gradle.kts @@ -23,6 +23,4 @@ testModuleInfo { requires("com.swirlds.common") requires("com.swirlds.test.framework") requires("org.junit.jupiter.api") - requires("org.junit.jupiter.params") - requires("org.assertj.core") } diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java b/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java deleted file mode 100644 index b5769e2cca06..000000000000 --- a/platform-sdk/swirlds-config-extensions/src/main/java/com/swirlds/config/extensions/converters/AbstractEnumConfigConverter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.config.extensions.converters; - -import com.swirlds.config.api.converter.ConfigConverter; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; - -/** - * Abstract class to support {@link ConfigConverter} for {@link Enum} types. - * - * @param type of the enum - */ -public abstract class AbstractEnumConfigConverter> implements ConfigConverter { - - @Override - public E convert(final String value) throws IllegalArgumentException, NullPointerException { - if (value == null) { - throw new NullPointerException("null can not be converted"); - } - final Class enumType = getEnumType(); - Objects.requireNonNull(enumType, "enumType"); - return Enum.valueOf(enumType, value); - } - - /** - * Returns the {@link Class} of the {@link Enum} type. - * - * @return the {@link Class} of the {@link Enum} type - */ - @NonNull - protected abstract Class getEnumType(); -} diff --git a/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java b/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java index 1af176add9ff..f202576a6e16 100644 --- a/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java +++ b/platform-sdk/swirlds-config-extensions/src/main/java/module-info.java @@ -3,7 +3,6 @@ exports com.swirlds.config.extensions.reflection; exports com.swirlds.config.extensions.sources; exports com.swirlds.config.extensions.validators; - exports com.swirlds.config.extensions.converters; requires transitive com.swirlds.config.api; requires com.swirlds.base; diff --git a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java b/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java deleted file mode 100644 index 6c962118806b..000000000000 --- a/platform-sdk/swirlds-config-extensions/src/test/java/com/swirlds/config/extensions/test/converters/AbstractEnumConfigConverterTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.config.extensions.test.converters; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import com.swirlds.config.api.Configuration; -import com.swirlds.config.api.ConfigurationBuilder; -import com.swirlds.config.api.converter.ConfigConverter; -import com.swirlds.config.extensions.converters.AbstractEnumConfigConverter; -import com.swirlds.config.extensions.sources.SimpleConfigSource; -import java.lang.annotation.ElementType; -import java.lang.annotation.RetentionPolicy; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -class AbstractEnumConfigConverterTest { - - /** - * Based on https://github.com/hashgraph/hedera-services/issues/6106 we currently need to add ConfigConverter - * explicitly - */ - private static class RetentionPolicyConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @Override - protected Class getEnumType() { - return RetentionPolicy.class; - } - } - - /** - * Based on https://github.com/hashgraph/hedera-services/issues/6106 we currently need to add ConfigConverter - * explicitly - */ - private static class ElementTypeConverter extends AbstractEnumConfigConverter - implements ConfigConverter { - - @Override - protected Class getEnumType() { - return ElementType.class; - } - } - - @Test - void testNullValue() { - // given - final RetentionPolicyConverter converter = new RetentionPolicyConverter(); - - // then - assertThatThrownBy(() -> converter.convert(null)).isInstanceOf(NullPointerException.class); - } - - @Test - void testInvalidValue() { - // given - final RetentionPolicyConverter converter = new RetentionPolicyConverter(); - - // then - assertThatThrownBy(() -> converter.convert("not-supported-value")).isInstanceOf(IllegalArgumentException.class); - } - - @ParameterizedTest - @EnumSource(value = RetentionPolicy.class) - void testValidValue(final RetentionPolicy value) { - // given - final RetentionPolicyConverter converter = new RetentionPolicyConverter(); - - // when - final RetentionPolicy source = converter.convert(value.name()); - - // then - assertThat(source).isEqualTo(value); - } - - @Test - void testIntegration() { - // given - final Configuration configuration = ConfigurationBuilder.create() - .withSource(new SimpleConfigSource("retention-policy", "SOURCE")) - .withSource(new SimpleConfigSource("element-type", "TYPE")) - .withConverter(new RetentionPolicyConverter()) - .withConverter(new ElementTypeConverter()) - .build(); - - // when - final RetentionPolicy source = configuration.getValue("retention-policy", RetentionPolicy.class); - final ElementType type = configuration.getValue("element-type", ElementType.class); - - // then - assertThat(source).isEqualTo(RetentionPolicy.SOURCE); - assertThat(type).isEqualTo(ElementType.TYPE); - } -} From 8c3b3defac8a32914cff47b81d6149982454358e Mon Sep 17 00:00:00 2001 From: lukelee-sl <109538178+lukelee-sl@users.noreply.github.com> Date: Wed, 20 Dec 2023 19:06:11 -0800 Subject: [PATCH 23/80] feat: add sanity precheck for contract call (#10585) Signed-off-by: lukelee-sl --- .../contract/ContractCallTransitionLogic.java | 14 +++++ .../ContractCallTransitionLogicTest.java | 21 +++++++ .../contract/hapi/ContractCallSuite.java | 63 ++++++++++++++++++- .../opcodes/CreateOperationSuite.java | 11 +++- .../suites/leaky/LeakyContractTestsSuite.java | 29 ++++++--- 5 files changed, 126 insertions(+), 12 deletions(-) diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java index 3f5c018ca16a..a1708167533f 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogic.java @@ -27,6 +27,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_GAS_LIMIT_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; import com.hedera.node.app.service.mono.context.TransactionContext; import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.contracts.execution.CallEvmTxProcessor; @@ -238,6 +239,19 @@ private ResponseCodeEnum validateSemantics(final TransactionBody transactionBody if (op.getGas() > properties.maxGasPerSec()) { return MAX_GAS_LIMIT_EXCEEDED; } + // Do some sanity checking in advance to ensure that the target is a valid contract + // Missing entity num is a valid target for lazy create. Tokens are also valid targets + final var target = targetOf(op); + if (!target.equals(EntityNum.MISSING_NUM) + && !entityAccess.isTokenAccount(target.toId().asEvmAddress()) + && op.getAmount() == 0) { + try { + final var receiver = accountStore.loadContract(target.toId()); + validateTrue(receiver != null && receiver.isSmartContract(), INVALID_CONTRACT_ID); + } catch (InvalidTransactionException e) { + return e.getResponseCode(); + } + } return OK; } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java index dcad5cd00895..39987043b697 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/contract/ContractCallTransitionLogicTest.java @@ -716,10 +716,22 @@ void codeCacheThrowingExceptionDuringGetDoesntPropagate() { void acceptsOkSyntax() { givenValidTxnCtx(); given(properties.maxGasPerSec()).willReturn(gas + 1); + contractAccount.setSmartContract(true); // expect: assertEquals(OK, subject.semanticCheck().apply(contractCallTxn)); } + @Test + void failsIfNotSmartContractSyntax() { + givenValidTxnCtxWithNoAmount(); + given(properties.maxGasPerSec()).willReturn(gas + 1); + given(accountStore.loadContract(new Id(target.getShardNum(), target.getRealmNum(), target.getContractNum()))) + .willReturn(contractAccount); + contractAccount.setSmartContract(false); + // expect: + assertEquals(INVALID_CONTRACT_ID, subject.semanticCheck().apply(contractCallTxn)); + } + @Test void providingGasOverLimitReturnsCorrectPrecheck() { givenValidTxnCtx(); @@ -781,6 +793,15 @@ private void givenValidTxnCtx() { contractCallTxn = op.build(); } + private void givenValidTxnCtxWithNoAmount() { + var op = TransactionBody.newBuilder() + .setContractCall(ContractCallTransactionBody.newBuilder() + .setGas(gas) + .setAmount(0) + .setContractID(target)); + contractCallTxn = op.build(); + } + private AccountID ourAccount() { return senderAccount.getId().asGrpcAccount(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java index 915a13074cec..f364ac684815 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java @@ -63,6 +63,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.createLargeFile; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; @@ -92,6 +94,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OBTAINER_SAME_CONTRACT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import static com.swirlds.common.utility.CommonUtils.unhex; @@ -232,6 +235,8 @@ public List getSpecsInSuite() { insufficientFee(), nonPayable(), invalidContract(), + invalidContractIsAnAccount(), + invalidContractDoesNotExist(), smartContractFailFirst(), contractTransferToSigReqAccountWithoutKeyFails(), callingDestructedContractReturnsStatusDeleted(), @@ -1512,9 +1517,15 @@ HapiSpec callingDestructedContractReturnsStatusDeleted() { "del", asHeadlongAddress(asAddress(accountIDAtomicReference.get()))) .gas(1_000_000L))) - .then(contractCall(SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) - .gas(350_000L) - .hasKnownStatus(CONTRACT_DELETED)); + .then( + ifHapiTest(contractCall( + SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) + .gas(350_000L) + .hasKnownStatus(CONTRACT_DELETED)), + ifNotHapiTest(contractCall( + SIMPLE_UPDATE_CONTRACT, "set", BigInteger.valueOf(15), BigInteger.valueOf(434)) + .gas(350_000L) + .hasPrecheck(CONTRACT_DELETED))); } @HapiTest @@ -1569,6 +1580,52 @@ HapiSpec invalidContract() { .then(contractCallWithFunctionAbi("invalid", function).hasKnownStatus(INVALID_CONTRACT_ID)); } + @HapiTest + HapiSpec invalidContractDoesNotExist() { + final AtomicReference accountID = new AtomicReference<>(); + final var function = getABIFor(FUNCTION, "getIndirect", CREATE_TRIVIAL); + + return defaultHapiSpec("invalidContractDoesNotExist") + .given() + .when( + withOpContext((spec, opLog) -> allRunFor( + spec, + ifHapiTest(contractCallWithFunctionAbi("0.0.100000001", function) + .hasKnownStatus(INVALID_CONTRACT_ID) + .via("contractDoesNotExist")))), + ifNotHapiTest(contractCallWithFunctionAbi("0.0.100000001", function) + .hasPrecheck(INVALID_CONTRACT_ID) + .via("contractDoesNotExist"))) + .then(ifNotHapiTest(getTxnRecord("contractDoesNotExist").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND))); + } + + @HapiTest + HapiSpec invalidContractIsAnAccount() { + final AtomicReference accountID = new AtomicReference<>(); + final var function = getABIFor(FUNCTION, "getIndirect", CREATE_TRIVIAL); + + return defaultHapiSpec("invalidContractIsAnAccount") + .given(cryptoCreate("invalid") + .balance(ONE_MILLION_HBARS) + .payingWith(GENESIS) + .exposingCreatedIdTo(accountID::set)) + .when( + ifHapiTest(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCallWithFunctionAbi( + "0.0." + accountID.get().getAccountNum(), function) + .hasKnownStatus(SUCCESS) + .via("contractIsAccount")))), + ifNotHapiTest(withOpContext((spec, opLog) -> allRunFor( + spec, + contractCallWithFunctionAbi( + "0.0." + accountID.get().getAccountNum(), function) + .hasPrecheck(INVALID_CONTRACT_ID) + .via("contractIsAccount"))))) + .then(ifNotHapiTest(getTxnRecord("contractIsAccount").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND))); + } + + // This test disabled for modularization service @HapiTest HapiSpec smartContractFailFirst() { final var civilian = "civilian"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java index 8b5cb03d54ba..67ddfed90aff 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java @@ -33,6 +33,8 @@ import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.contractListWithPropertiesInheritedFrom; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; @@ -103,7 +105,14 @@ final HapiSpec factoryAndSelfDestructInConstructorContract() { uploadInitCode(contract), cryptoCreate(sender).balance(ONE_HUNDRED_HBARS), contractCreate(contract).balance(10).payingWith(sender)) - .when(contractCall(contract).hasKnownStatus(CONTRACT_DELETED).payingWith(sender)) + .when( + // Currently only mono service has a precheck for deleted contracts + ifHapiTest(contractCall(contract) + .hasKnownStatus(CONTRACT_DELETED) + .payingWith(sender)), + ifNotHapiTest(contractCall(contract) + .hasPrecheck(CONTRACT_DELETED) + .payingWith(sender))) .then(getContractBytecode(contract).hasCostAnswerPrecheck(CONTRACT_DELETED)); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 77022b60e0c3..743afbebd1fe 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -81,6 +81,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; @@ -1717,14 +1719,25 @@ final HapiSpec canCallPendingContractSafely() { .adminKey(THRESHOLD)) .toArray(HapiSpecOperation[]::new))) .when() - .then(sourcing(() -> contractCallWithFunctionAbi( - "0.0." + (createdFileNum.get() + createBurstSize), - getABIFor(FUNCTION, "addNthFib", contract), - targets, - 12L) - .payingWith(GENESIS) - .gas(300_000L) - .via(callTxn))); + .then( + sourcing(() -> ifHapiTest(contractCallWithFunctionAbi( + "0.0." + (createdFileNum.get() + createBurstSize), + getABIFor(FUNCTION, "addNthFib", contract), + targets, + 12L) + .payingWith(GENESIS) + .gas(300_000L) + .via(callTxn))), + ifNotHapiTest(contractCallWithFunctionAbi( + "0.0." + (createdFileNum.get() + createBurstSize), + getABIFor(FUNCTION, "addNthFib", contract), + targets, + 12L) + .payingWith(GENESIS) + .gas(300_000L) + // This will fail the semantics validity check that verifies existence of the contract, + .hasPrecheck(INVALID_CONTRACT_ID) + .via(callTxn))); } @HapiTest From 1061cc98303eedcf65c0edc00082cee73b0704eb Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Thu, 21 Dec 2023 09:15:26 +0200 Subject: [PATCH 24/80] test: fix submittingNodeStillPaidIfServiceFeesOmitted (#10589) Signed-off-by: Mustafa Uzun --- .../com/hedera/node/app/workflows/SolvencyPreCheck.java | 2 +- .../services/bdd/suites/records/RecordCreationSuite.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java index af571db3baec..fa3377e79663 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java @@ -142,7 +142,7 @@ public void checkSolvency( return; } - final var totalFee = fees.totalFee(); + final var totalFee = ingestCheck ? fees.totalWithoutServiceFee() : fees.totalFee(); final var availableBalance = account.tinybarBalance(); final var offeredFee = txBody.transactionFee(); final ResponseCodeEnum insufficientFeeResponseCode; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java index 70a94726705c..61f1d30b99df 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java @@ -88,12 +88,17 @@ public List getSpecsInSuite() { submittingNodeStillPaidIfServiceFeesOmitted()); } + @HapiTest final HapiSpec submittingNodeStillPaidIfServiceFeesOmitted() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final AtomicReference feeObs = new AtomicReference<>(); - return defaultHapiSpec("submittingNodeStillPaidIfServiceFeesOmitted") + return propertyPreservingHapiSpec("submittingNodeStillPaidIfServiceFeesOmitted") + .preserving(STAKING_FEES_NODE_REWARD_PERCENTAGE, STAKING_FEES_STAKING_REWARD_PERCENTAGE) .given( + overridingTwo( + STAKING_FEES_NODE_REWARD_PERCENTAGE, "10", + STAKING_FEES_STAKING_REWARD_PERCENTAGE, "10"), cryptoTransfer(tinyBarsFromTo(GENESIS, TO_ACCOUNT, ONE_HBAR)) .payingWith(GENESIS), cryptoCreate(PAYER), From 2567c21a9ef2a0d77aacdc9bd76dca1a2e9aa619 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Thu, 21 Dec 2023 02:58:25 -0600 Subject: [PATCH 25/80] feat: move transaction prehandling into the wiring framework (#10493) Signed-off-by: Cody Littley Signed-off-by: Lazar Petrovic Co-authored-by: Lazar Petrovic --- .../wiring/transformers/WireFilter.java | 91 +++++++++++++ .../wiring/transformers/WireListSplitter.java | 85 ++++++++++++ .../wiring/transformers/WireTransformer.java | 87 +++++++++++++ .../internal/AdvancedWireTransformer.java | 1 + .../transformers/internal/WireFilter.java | 73 ----------- .../internal/WireListSplitter.java | 65 --------- .../internal/WireTransformer.java | 78 ----------- .../common/wiring/wires/input/InputWire.java | 3 +- .../wiring/wires/output/OutputWire.java | 67 ++++++---- .../internal/TransformingOutputWire.java | 2 +- .../common/wiring/model/ModelTests.java | 12 +- .../TaskSchedulerTransformersTests.java | 10 +- .../com/swirlds/platform/SwirldsPlatform.java | 32 +++-- .../platform/components/EventIntake.java | 7 +- .../components/LinkedEventIntake.java | 49 +------ .../system/ScopedSystemTransaction.java | 33 +++++ .../system/SystemTransactionExtractor.java | 75 +++++++++++ .../swirlds/platform/event/GossipEvent.java | 22 ++++ .../platform/eventhandling/EventConfig.java | 3 +- .../PreConsensusEventHandler.java | 4 +- .../swirlds/platform/internal/EventImpl.java | 22 ---- .../platform/state/SwirldStateManager.java | 49 ++++--- .../wiring/LinkedEventIntakeWiring.java | 3 +- .../platform/wiring/OrphanBufferWiring.java | 2 +- .../platform/wiring/PlatformCoordinator.java | 28 ++-- .../platform/wiring/PlatformSchedulers.java | 39 ++++-- .../wiring/PlatformSchedulersConfig.java | 64 +++++---- .../platform/wiring/PlatformWiring.java | 35 ++++- .../wiring/SignedStateFileManagerWiring.java | 6 +- ...pplicationTransactionPrehandlerWiring.java | 59 +++++++++ .../StateSignatureCollectorWiring.java | 123 ++++++++++++++++++ .../platform/wiring/diagram-commands.txt | 23 ++-- .../PreConsensusEventHandlerTests.java | 2 +- .../platform/wiring/PlatformWiringTests.java | 6 +- .../generator/AbstractGraphGenerator.java | 1 + .../test/components/EventIntakeTest.java | 3 + 36 files changed, 840 insertions(+), 424 deletions(-) create mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireFilter.java create mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireListSplitter.java create mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireTransformer.java delete mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireFilter.java delete mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireListSplitter.java delete mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireTransformer.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/ScopedSystemTransaction.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/SystemTransactionExtractor.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/ApplicationTransactionPrehandlerWiring.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireFilter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireFilter.java new file mode 100644 index 000000000000..bc9206a68051 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireFilter.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.wiring.transformers; + +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.input.InputWire; +import com.swirlds.common.wiring.wires.output.OutputWire; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * Filters out data, allowing some objects to pass and blocking others. + * + * @param the type of data being filtered + */ +public class WireFilter { + + private final BindableInputWire inputWire; + private final OutputWire outputWire; + + /** + * Constructor. + * + * @param model the wiring model containing this output channel + * @param filterName the name of the filter + * @param filterInputName the label for the input wire going into the filter + * @param predicate only data that causes this method to return true is forwarded. This method must be very + * fast. Putting large amounts of work into this transformer violates the intended usage + * pattern of the wiring framework and may result in very poor system performance. + */ + public WireFilter( + @NonNull final WiringModel model, + @NonNull final String filterName, + @NonNull final String filterInputName, + @NonNull final Predicate predicate) { + + Objects.requireNonNull(predicate); + + final TaskScheduler taskScheduler = model.schedulerBuilder(filterName) + .withType(TaskSchedulerType.DIRECT_STATELESS) + .build() + .cast(); + + inputWire = taskScheduler.buildInputWire(filterInputName); + inputWire.bind(t -> { + if (predicate.test(t)) { + return t; + } + return null; + }); + this.outputWire = taskScheduler.getOutputWire(); + } + + /** + * Get the input wire for this filter. + * + * @return the input wire + */ + @NonNull + public InputWire getInputWire() { + return inputWire; + } + + /** + * Get the output wire for this filter. + * + * @return the output wire + */ + @NonNull + public OutputWire getOutputWire() { + return outputWire; + } +} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireListSplitter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireListSplitter.java new file mode 100644 index 000000000000..a80f9e32c8c0 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireListSplitter.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.wiring.transformers; + +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.input.InputWire; +import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.common.wiring.wires.output.StandardOutputWire; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * Transforms a list of items to a sequence of individual items. Expects that there will not be any null values in the + * collection. + * + * @param the type of data in the list that is being split + */ +public class WireListSplitter { + + private final BindableInputWire, T> inputWire; + private final StandardOutputWire outputWire; + + /** + * Constructor. + * + * @param model the wiring model containing this output wire + * @param splitterName the name of the output channel + * @param splitterInputName the label for the input wire going into the splitter + */ + public WireListSplitter( + @NonNull final WiringModel model, + @NonNull final String splitterName, + @NonNull final String splitterInputName) { + final TaskScheduler taskScheduler = model.schedulerBuilder(splitterName) + .withType(TaskSchedulerType.DIRECT_STATELESS) + .build() + .cast(); + + inputWire = taskScheduler.buildInputWire(splitterInputName); + outputWire = (StandardOutputWire) taskScheduler.getOutputWire(); + + inputWire.bind(list -> { + for (final T t : list) { + outputWire.forward(t); + } + }); + } + + /** + * Get the input wire for this splitter. + * + * @return the input wire + */ + @NonNull + public InputWire> getInputWire() { + return inputWire; + } + + /** + * Get the output wire for this splitter. + * + * @return the output wire + */ + @NonNull + public OutputWire getOutputWire() { + return outputWire; + } +} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireTransformer.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireTransformer.java new file mode 100644 index 000000000000..b4505f9bc763 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/WireTransformer.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.wiring.transformers; + +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.input.InputWire; +import com.swirlds.common.wiring.wires.output.OutputWire; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; +import java.util.function.Function; + +/** + * Transforms data on a wire from one type to another. + * + * @param the input type + * @param the output type + */ +public class WireTransformer { + + private final BindableInputWire inputWire; + private final OutputWire outputWire; + + /** + * Constructor. + * + * @param model the wiring model containing this output channel + * @param transformerName the name of the transformer + * @param transformerInputName the label for the input wire going into the transformer + * @param transformer an object that transforms from type A to type B. If this method returns null then no + * data is forwarded. This method must be very fast. Putting large amounts of work into + * this transformer violates the intended usage pattern of the wiring framework and may + * result in very poor system performance. + */ + public WireTransformer( + @NonNull final WiringModel model, + @NonNull final String transformerName, + @NonNull final String transformerInputName, + @NonNull final Function transformer) { + Objects.requireNonNull(transformer); + + final TaskScheduler taskScheduler = model.schedulerBuilder(transformerName) + .withType(TaskSchedulerType.DIRECT_STATELESS) + .build() + .cast(); + + inputWire = taskScheduler.buildInputWire(transformerInputName); + inputWire.bind(transformer); + outputWire = taskScheduler.getOutputWire(); + } + + /** + * Get the input wire for this transformer. + * + * @return the input wire + */ + @NonNull + public InputWire getInputWire() { + return inputWire; + } + + /** + * Get the output wire for this transformer. + * + * @return the output wire + */ + @NonNull + public OutputWire getOutputWire() { + return outputWire; + } +} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/AdvancedWireTransformer.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/AdvancedWireTransformer.java index c84c8346246b..60e49a1442ce 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/AdvancedWireTransformer.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/AdvancedWireTransformer.java @@ -18,6 +18,7 @@ import com.swirlds.common.wiring.model.internal.StandardWiringModel; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; +import com.swirlds.common.wiring.transformers.WireTransformer; import com.swirlds.common.wiring.wires.output.OutputWire; import com.swirlds.common.wiring.wires.output.internal.ForwardingOutputWire; import com.swirlds.common.wiring.wires.output.internal.TransformingOutputWire; diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireFilter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireFilter.java deleted file mode 100644 index 777c9e5544e6..000000000000 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.wiring.transformers.internal; - -import com.swirlds.common.wiring.model.internal.StandardWiringModel; -import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.common.wiring.wires.output.StandardOutputWire; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Filters out data, allowing some objects to pass and blocking others. - */ -public class WireFilter implements Consumer { - - private final Predicate predicate; - private final StandardOutputWire outputWire; - - /** - * Constructor. - * - * @param model the wiring model containing this output channel - * @param name the name of the output wire - * @param predicate only data that causes this method to return true is forwarded. This method must be very fast. - * Putting large amounts of work into this transformer violates the intended usage pattern of the - * wiring framework and may result in very poor system performance. - */ - public WireFilter( - @NonNull final StandardWiringModel model, - @NonNull final String name, - @NonNull final Predicate predicate) { - this.predicate = Objects.requireNonNull(predicate); - this.outputWire = new StandardOutputWire<>(model, name); - model.registerVertex(name, TaskSchedulerType.DIRECT_STATELESS, true); - } - - /** - * {@inheritDoc} - */ - @Override - public void accept(@NonNull final T t) { - if (predicate.test(t)) { - outputWire.forward(t); - } - } - - /** - * Get the output wire for this transformer. - * - * @return the output wire - */ - @NonNull - public OutputWire getOutputWire() { - return outputWire; - } -} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireListSplitter.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireListSplitter.java deleted file mode 100644 index aae32b030ea3..000000000000 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireListSplitter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.wiring.transformers.internal; - -import com.swirlds.common.wiring.model.internal.StandardWiringModel; -import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.common.wiring.wires.output.StandardOutputWire; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import java.util.function.Consumer; - -/** - * Transforms a list of items to a sequence of individual items. Expects that there will not be any null values in the - * collection. - */ -public class WireListSplitter implements Consumer> { - - private final StandardOutputWire outputWire; - - /** - * Constructor. - * - * @param model the wiring model containing this output wire - * @param name the name of the output channel - */ - public WireListSplitter(@NonNull final StandardWiringModel model, @NonNull final String name) { - model.registerVertex(name, TaskSchedulerType.DIRECT_STATELESS, true); - outputWire = new StandardOutputWire<>(model, name); - } - - /** - * {@inheritDoc} - */ - @Override - public void accept(@NonNull final List list) { - for (final T t : list) { - outputWire.forward(t); - } - } - - /** - * Get the output wire for this transformer. - * - * @return the output wire - */ - @NonNull - public OutputWire getOutputWire() { - return outputWire; - } -} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireTransformer.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireTransformer.java deleted file mode 100644 index c8cd19b732bc..000000000000 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/transformers/internal/WireTransformer.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.common.wiring.transformers.internal; - -import com.swirlds.common.wiring.model.internal.StandardWiringModel; -import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; -import com.swirlds.common.wiring.wires.output.OutputWire; -import com.swirlds.common.wiring.wires.output.StandardOutputWire; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * Transforms data on a wire from one type to another. - * - * @param the input type - * @param the output type - */ -public class WireTransformer implements Consumer { - - private final Function transformer; - private final StandardOutputWire outputWire; - - /** - * Constructor. - * - * @param model the wiring model containing this output channel - * @param name the name of the output wire - * @param transformer an object that transforms from type A to type B. If this method returns null then no data is - * forwarded. This method must be very fast. Putting large amounts of work into this transformer - * violates the intended usage pattern of the wiring framework and may result in very poor system - * performance. - */ - public WireTransformer( - @NonNull final StandardWiringModel model, - @NonNull final String name, - @NonNull final Function transformer) { - model.registerVertex(name, TaskSchedulerType.DIRECT_STATELESS, true); - this.transformer = Objects.requireNonNull(transformer); - outputWire = new StandardOutputWire<>(model, name); - } - - /** - * {@inheritDoc} - */ - @Override - public void accept(@NonNull final A a) { - final B b = transformer.apply(a); - if (b != null) { - outputWire.forward(b); - } - } - - /** - * Get the output wire for this transformer. - * - * @return the output wire - */ - @NonNull - public OutputWire getOutputWire() { - return outputWire; - } -} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/input/InputWire.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/input/InputWire.java index 88591ba77091..0b4f204f7c00 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/input/InputWire.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/input/InputWire.java @@ -24,7 +24,7 @@ /** * An object that can insert work to be handled by a {@link TaskScheduler}. * - * @param the type of data that passes into the wire + * @param the type of data that passes into the wire */ public abstract class InputWire { @@ -40,6 +40,7 @@ public abstract class InputWire { * @param name the name of the input wire */ protected InputWire(@NonNull final TaskScheduler taskScheduler, @NonNull final String name) { + this.taskSchedulerInput = Objects.requireNonNull(taskScheduler); this.name = Objects.requireNonNull(name); this.taskSchedulerName = taskScheduler.getName(); diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/OutputWire.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/OutputWire.java index 02579cfa4fd6..80795f040d63 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/OutputWire.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/OutputWire.java @@ -18,10 +18,10 @@ import com.swirlds.common.wiring.model.internal.StandardWiringModel; import com.swirlds.common.wiring.transformers.AdvancedTransformation; +import com.swirlds.common.wiring.transformers.WireFilter; +import com.swirlds.common.wiring.transformers.WireListSplitter; +import com.swirlds.common.wiring.transformers.WireTransformer; import com.swirlds.common.wiring.transformers.internal.AdvancedWireTransformer; -import com.swirlds.common.wiring.transformers.internal.WireFilter; -import com.swirlds.common.wiring.transformers.internal.WireListSplitter; -import com.swirlds.common.wiring.transformers.internal.WireTransformer; import com.swirlds.common.wiring.wires.SolderType; import com.swirlds.common.wiring.wires.input.InputWire; import edu.umd.cs.findbugs.annotations.NonNull; @@ -130,15 +130,23 @@ public void solderTo(@NonNull final String handlerName, @NonNull final Consumer< * data that comes out of the wire will be inserted into the filter). The output wire of the filter is returned by * this method. * - * @param name the name of the filter - * @param predicate the predicate that filters the output of this wire + * @param filterName the name of the filter + * @param filterInputName the label for the input wire going into the filter + * @param predicate the predicate that filters the output of this wire * @return the output wire of the filter */ @NonNull - public OutputWire buildFilter(@NonNull final String name, @NonNull final Predicate predicate) { - final WireFilter filter = - new WireFilter<>(model, Objects.requireNonNull(name), Objects.requireNonNull(predicate)); - solderTo(name, filter); + public OutputWire buildFilter( + @NonNull final String filterName, + @NonNull final String filterInputName, + @NonNull final Predicate predicate) { + + Objects.requireNonNull(filterName); + Objects.requireNonNull(filterInputName); + Objects.requireNonNull(predicate); + + final WireFilter filter = new WireFilter<>(model, filterName, filterInputName, predicate); + solderTo(filter.getInputWire()); return filter.getOutputWire(); } @@ -153,10 +161,14 @@ public OutputWire buildFilter(@NonNull final String name, @NonNull final Pr */ @SuppressWarnings("unchecked") @NonNull - public OutputWire buildSplitter() { - final String splitterName = name + "_splitter"; - final WireListSplitter splitter = new WireListSplitter<>(model, splitterName); - solderTo(splitterName, (Consumer) splitter); + public OutputWire buildSplitter( + @NonNull final String splitterName, @NonNull final String splitterInputName) { + + Objects.requireNonNull(splitterName); + Objects.requireNonNull(splitterInputName); + + final WireListSplitter splitter = new WireListSplitter<>(model, splitterName, splitterInputName); + solderTo((InputWire) splitter.getInputWire()); return splitter.getOutputWire(); } @@ -165,26 +177,33 @@ public OutputWire buildSplitter() { * (i.e. all data that comes out of the wire will be inserted into the transformer). The output wire of the * transformer is returned by this method. * - * @param name the name of the transformer - * @param transformer the function that transforms the output of this wire into the output of the transformer. - * Called once per data item. Null data returned by this method his not forwarded. - * @param the output type of the transformer + * @param transformerName the name of the transformer + * @param transformer the function that transforms the output of this wire into the output of the transformer. + * Called once per data item. Null data returned by this method his not forwarded. + * @param the output type of the transformer * @return the output wire of the transformer */ @NonNull public OutputWire buildTransformer( - @NonNull final String name, @NonNull final Function transformer) { + @NonNull final String transformerName, + @NonNull final String transformerInputName, + @NonNull final Function transformer) { + + Objects.requireNonNull(transformerName); + Objects.requireNonNull(transformerInputName); + Objects.requireNonNull(transformer); + final WireTransformer wireTransformer = - new WireTransformer<>(model, Objects.requireNonNull(name), Objects.requireNonNull(transformer)); - solderTo(name, wireTransformer); + new WireTransformer<>(model, transformerName, transformerInputName, transformer); + solderTo(wireTransformer.getInputWire()); return wireTransformer.getOutputWire(); } /** * Build a {@link AdvancedWireTransformer}. The input wire to the transformer is automatically soldered to this * output wire (i.e. all data that comes out of the wire will be inserted into the transformer). The output wire of - * the transformer is returned by this method. Similar to {@link #buildTransformer(String, Function)}, but instead - * of the transformer method being called once per data item, it is called once per output per data item. + * the transformer is returned by this method. Similar to {@link #buildTransformer(String, String, Function)}, but + * instead of the transformer method being called once per data item, it is called once per output per data item. * * @param name the name of the transformer * @param transform the function that transforms the output of this wire into the output of the transformer, called @@ -211,8 +230,8 @@ public OutputWire buildAdvancedTransformer( /** * Build a {@link AdvancedWireTransformer}. The input wire to the transformer is automatically soldered to this * output wire (i.e. all data that comes out of the wire will be inserted into the transformer). The output wire of - * the transformer is returned by this method. Similar to {@link #buildTransformer(String, Function)}, but instead - * of the transformer method being called once per data item, it is called once per output per data item. + * the transformer is returned by this method. Similar to {@link #buildTransformer(String, String, Function)}, but + * instead of the transformer method being called once per data item, it is called once per output per data item. * *

* This method is very similar to {@link #buildAdvancedTransformer(String, Function, Consumer)}, but with a diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/internal/TransformingOutputWire.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/internal/TransformingOutputWire.java index 504ff6e9b9a9..863b6f0ce4b1 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/internal/TransformingOutputWire.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/wires/output/internal/TransformingOutputWire.java @@ -32,7 +32,7 @@ /** * An output wire that transforms data that flows across it. For advanced use cases where - * {@link OutputWire#buildTransformer(String, Function)} semantics are insufficient. + * {@link OutputWire#buildTransformer(String, String, Function)} semantics are insufficient. * * @param the type of data passed to the forwarding method * @param the type of data forwarded to things soldered to this wire diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java index 3636d77dbf3c..76b907209267 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java @@ -999,7 +999,10 @@ void filterInCycleTest() { taskSchedulerA.getOutputWire().solderTo(inputB); taskSchedulerB.getOutputWire().solderTo(inputC); taskSchedulerC.getOutputWire().solderTo(inputD); - taskSchedulerD.getOutputWire().buildFilter("onlyEven", x -> x % 2 == 0).solderTo(inputE); + taskSchedulerD + .getOutputWire() + .buildFilter("onlyEven", "onlyEvenInput", x -> x % 2 == 0) + .solderTo(inputE); taskSchedulerE.getOutputWire().solderTo(inputF); taskSchedulerF.getOutputWire().solderTo(inputG); taskSchedulerG.getOutputWire().solderTo(inputD); @@ -1079,7 +1082,10 @@ void transformerInCycleTest() { taskSchedulerA.getOutputWire().solderTo(inputB); taskSchedulerB.getOutputWire().solderTo(inputC); taskSchedulerC.getOutputWire().solderTo(inputD); - taskSchedulerD.getOutputWire().buildTransformer("inverter", x -> -x).solderTo(inputE); + taskSchedulerD + .getOutputWire() + .buildTransformer("inverter", "inverterInput", x -> -x) + .solderTo(inputE); taskSchedulerE.getOutputWire().solderTo(inputF); taskSchedulerF.getOutputWire().solderTo(inputG); taskSchedulerG.getOutputWire().solderTo(inputD); @@ -1159,7 +1165,7 @@ void splitterInCycleTest() { taskSchedulerA.getOutputWire().solderTo(inputB); taskSchedulerB.getOutputWire().solderTo(inputC); taskSchedulerC.getOutputWire().solderTo(inputD); - final OutputWire splitter = taskSchedulerD.getOutputWire().buildSplitter(); + final OutputWire splitter = taskSchedulerD.getOutputWire().buildSplitter("splitter", "splitterInput"); splitter.solderTo(inputE); taskSchedulerE.getOutputWire().solderTo(inputF); taskSchedulerF.getOutputWire().solderTo(inputG); diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/transformers/TaskSchedulerTransformersTests.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/transformers/TaskSchedulerTransformersTests.java index b87a370c4412..2a1e0b4b9ad2 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/transformers/TaskSchedulerTransformersTests.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/transformers/TaskSchedulerTransformersTests.java @@ -62,7 +62,8 @@ void wireListSplitterTest() { model.schedulerBuilder("D").build().cast(); final BindableInputWire, Void> wireDIn = taskSchedulerD.buildInputWire("D in"); - final OutputWire splitter = taskSchedulerA.getOutputWire().buildSplitter(); + final OutputWire splitter = + taskSchedulerA.getOutputWire().buildSplitter("testSplitter", "test splitter input"); splitter.solderTo(wireBIn); splitter.solderTo(wireCIn); taskSchedulerA.getOutputWire().solderTo(wireDIn); @@ -135,7 +136,8 @@ void wireFilterTest() { final AtomicInteger countLambda = new AtomicInteger(0); taskSchedulerA.getOutputWire().solderTo(inB); - final OutputWire filter = taskSchedulerA.getOutputWire().buildFilter("onlyEven", x -> x % 2 == 0); + final OutputWire filter = + taskSchedulerA.getOutputWire().buildFilter("onlyEven", "onlyEvenInput", x -> x % 2 == 0); filter.solderTo(inC); filter.solderTo("lambda", x -> countLambda.set(hash32(countLambda.get(), x))); @@ -198,11 +200,11 @@ void wireTransformerTest() { taskSchedulerA.getOutputWire().solderTo(inB); taskSchedulerA .getOutputWire() - .buildTransformer("getValue", TestData::value) + .buildTransformer("getValue", "getValueInput", TestData::value) .solderTo(inC); taskSchedulerA .getOutputWire() - .buildTransformer("getInvert", TestData::invert) + .buildTransformer("getInvert", "getInvertInput", TestData::invert) .solderTo(inD); final AtomicInteger countA = new AtomicInteger(0); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 87ca6e340dde..c939791dd16e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -652,8 +652,18 @@ public class SwirldsPlatform implements Platform { .build()); final ThreadConfig threadConfig = platformContext.getConfiguration().getConfigData(ThreadConfig.class); - final PreConsensusEventHandler preConsensusEventHandler = components.add(new PreConsensusEventHandler( - metrics, threadManager, selfId, swirldStateManager, consensusMetrics, threadConfig)); + + final Consumer preconsensusEventHandlerConsumer; + final Clearable preconsensusEventHandlerClear; + if (eventConfig.useLegacyIntake()) { + final PreConsensusEventHandler preConsensusEventHandler = components.add(new PreConsensusEventHandler( + metrics, threadManager, selfId, swirldStateManager, consensusMetrics, threadConfig)); + preconsensusEventHandlerConsumer = preConsensusEventHandler::preconsensusEvent; + preconsensusEventHandlerClear = preConsensusEventHandler; + } else { + preconsensusEventHandlerConsumer = event -> {}; + preconsensusEventHandlerClear = null; + } consensusRoundHandler = components.add(new ConsensusRoundHandler( platformContext, @@ -724,7 +734,7 @@ public class SwirldsPlatform implements Platform { eventObserverDispatcher, eventIntakePhaseTimer, shadowGraph, - preConsensusEventHandler::preconsensusEvent, + preconsensusEventHandlerConsumer, intakeEventCounter); final List validators = new ArrayList<>(); @@ -766,14 +776,7 @@ public class SwirldsPlatform implements Platform { final OrphanBuffer orphanBuffer = new OrphanBuffer(platformContext, intakeEventCounter); final InOrderLinker inOrderLinker = new InOrderLinker(platformContext, time, intakeEventCounter); final LinkedEventIntake linkedEventIntake = new LinkedEventIntake( - platformContext, - threadManager, - time, - consensusRef::get, - eventObserverDispatcher, - shadowGraph, - preConsensusEventHandler::preconsensusEvent, - intakeEventCounter); + platformContext, time, consensusRef::get, eventObserverDispatcher, shadowGraph, intakeEventCounter); final EventCreationManager eventCreationManager = buildEventCreationManager( platformContext, @@ -793,7 +796,9 @@ public class SwirldsPlatform implements Platform { orphanBuffer, inOrderLinker, linkedEventIntake, - eventCreationManager); + eventCreationManager, + swirldStateManager, + signedStateManager); intakeHandler = platformWiring.getEventInput()::put; } @@ -912,7 +917,7 @@ public class SwirldsPlatform implements Platform { List.of( Pair.of(pauseEventCreation, "eventCreator"), Pair.of(gossip, "gossip"), - Pair.of(preConsensusEventHandler, "preConsensusEventHandler"), + Pair.of(preconsensusEventHandlerClear, "preConsensusEventHandler"), Pair.of(consensusRoundHandler, "consensusRoundHandler"), Pair.of(transactionPool, "transactionPool"))); } else { @@ -922,7 +927,6 @@ public class SwirldsPlatform implements Platform { Pair.of(intakeQueue, "intakeQueue"), Pair.of(platformWiring, "platformWiring"), Pair.of(shadowGraph, "shadowGraph"), - Pair.of(preConsensusEventHandler, "preConsensusEventHandler"), Pair.of(consensusRoundHandler, "consensusRoundHandler"), Pair.of(transactionPool, "transactionPool"))); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java index 856c74f7b810..c6b58effacc5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java @@ -215,10 +215,7 @@ public void addEvent(final EventImpl event) { */ @NonNull private Runnable buildPrehandleTask(@NonNull final EventImpl event) { - return () -> { - prehandleEvent.accept(event); - event.signalPrehandleCompletion(); - }; + return () -> prehandleEvent.accept(event); } /** @@ -251,7 +248,7 @@ private void handleConsensus(final ConsensusRound consensusRound) { // It is critically important that prehandle is always called prior to handleConsensusRound(). final long start = time.nanoTime(); - consensusRound.forEach(e -> ((EventImpl) e).awaitPrehandleCompletion()); + consensusRound.forEach(e -> ((EventImpl) e).getBaseEvent().awaitPrehandleCompletion()); final long end = time.nanoTime(); metrics.reportTimeWaitedForPrehandlingTransaction(end - start); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java index 274801c1ae01..a1b2a98b0511 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java @@ -18,10 +18,8 @@ import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.platform.Consensus; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; -import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.internal.ConsensusRound; @@ -31,12 +29,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -63,9 +55,6 @@ public class LinkedEventIntake { */ private final ShadowGraph shadowGraph; - private final ExecutorService prehandlePool; - private final Consumer prehandleEvent; - private final EventIntakeMetrics metrics; private final Time time; @@ -85,46 +74,28 @@ public class LinkedEventIntake { * Constructor * * @param platformContext the platform context - * @param threadManager creates new threading resources * @param time provides the wall clock time * @param consensusSupplier provides the current consensus instance * @param dispatcher invokes event related callbacks * @param shadowGraph tracks events in the hashgraph - * @param prehandleEvent prehandles transactions in an event * @param intakeEventCounter tracks the number of events from each peer that are currently in the intake pipeline */ public LinkedEventIntake( @NonNull final PlatformContext platformContext, - @NonNull final ThreadManager threadManager, @NonNull final Time time, @NonNull final Supplier consensusSupplier, @NonNull final EventObserverDispatcher dispatcher, @NonNull final ShadowGraph shadowGraph, - @NonNull final Consumer prehandleEvent, @NonNull final IntakeEventCounter intakeEventCounter) { this.time = Objects.requireNonNull(time); this.consensusSupplier = Objects.requireNonNull(consensusSupplier); this.dispatcher = Objects.requireNonNull(dispatcher); this.shadowGraph = Objects.requireNonNull(shadowGraph); - this.prehandleEvent = Objects.requireNonNull(prehandleEvent); this.intakeEventCounter = Objects.requireNonNull(intakeEventCounter); this.paused = false; - - final EventConfig eventConfig = platformContext.getConfiguration().getConfigData(EventConfig.class); - - final BlockingQueue prehandlePoolQueue = new LinkedBlockingQueue<>(); - - prehandlePool = new ThreadPoolExecutor( - eventConfig.prehandlePoolSize(), - eventConfig.prehandlePoolSize(), - 0L, - TimeUnit.MILLISECONDS, - prehandlePoolQueue, - threadManager.createThreadFactory("platform", "txn-prehandle")); - - metrics = new EventIntakeMetrics(platformContext, prehandlePoolQueue::size); + metrics = new EventIntakeMetrics(platformContext, () -> -1); } /** @@ -154,9 +125,6 @@ public List addEvent(@NonNull final EventImpl event) { final long minGenNonAncientBeforeAdding = consensusSupplier.get().getMinGenerationNonAncient(); - // Prehandle transactions on the thread pool. - prehandlePool.submit(buildPrehandleTask(event)); - // record the event in the hashgraph, which results in the events in consEvent reaching consensus final List consensusRounds = consensusSupplier.get().addEvent(event); @@ -187,19 +155,6 @@ public void setPaused(final boolean paused) { this.paused = paused; } - /** - * Build a task that will prehandle transactions in an event. Executed on a thread pool. - * - * @param event the event to prehandle - */ - @NonNull - private Runnable buildPrehandleTask(@NonNull final EventImpl event) { - return () -> { - prehandleEvent.accept(event); - event.signalPrehandleCompletion(); - }; - } - /** * Notify observer of stale events, of all event in the consensus stale event queue. * @@ -238,7 +193,7 @@ private void handleConsensus(final @NonNull ConsensusRound consensusRound) { // It is critically important that prehandle is always called prior to handleConsensusRound(). final long start = time.nanoTime(); - consensusRound.forEach(event -> ((EventImpl) event).awaitPrehandleCompletion()); + consensusRound.forEach(event -> ((EventImpl) event).getBaseEvent().awaitPrehandleCompletion()); final long end = time.nanoTime(); metrics.reportTimeWaitedForPrehandlingTransaction(end - start); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/ScopedSystemTransaction.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/ScopedSystemTransaction.java new file mode 100644 index 000000000000..8ac8067d45ef --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/ScopedSystemTransaction.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.components.transaction.system; + +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.system.transaction.SystemTransaction; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A system transaction with a submitter ID. The submitter ID is not included with the transaction, it is determined + * by the event that the transaction is contained within. This is intentional, as it makes it impossible for a + * transaction to lie and claim to be submitted by a node that did not actually submit it. + * + * @param submitterId the ID of the node that submitted the transaction + * @param transaction the transaction + * @param the type of transaction + */ +public record ScopedSystemTransaction( + @NonNull NodeId submitterId, @NonNull T transaction) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/SystemTransactionExtractor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/SystemTransactionExtractor.java new file mode 100644 index 000000000000..c5d5c04c9703 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/transaction/system/SystemTransactionExtractor.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.components.transaction.system; + +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.transaction.StateSignatureTransaction; +import com.swirlds.platform.system.transaction.SystemTransaction; +import com.swirlds.platform.system.transaction.Transaction; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * A simple utility for extracting and filtering system transactions from events. + */ +public final class SystemTransactionExtractor { + + private SystemTransactionExtractor() {} + + /** + * Extract all system transactions from the given event. + * + * @param event the event to extract system transactions from + * @return the system transactions contained within the event + */ + @Nullable + public static List> getScopedSystemTransactions(@NonNull final GossipEvent event) { + final var transactions = event.getHashedData().getTransactions(); + if (transactions == null) { + return null; + } + + final List> scopedTransactions = new ArrayList<>(); + + for (final Transaction transaction : event.getHashedData().getTransactions()) { + if (transaction instanceof final SystemTransaction systemTransaction) { + scopedTransactions.add( + new ScopedSystemTransaction<>(event.getHashedData().getCreatorId(), systemTransaction)); + } + } + + return scopedTransactions; + } + + /** + * Filter system transactions for state signature transactions. + * + * @param scopedTransaction the transaction to filter and cast + * @return the state signature transaction, or null if the transaction is not a state signature transaction + */ + @SuppressWarnings("unchecked") + @Nullable + public static ScopedSystemTransaction stateSignatureTransactionFilter( + @NonNull final ScopedSystemTransaction scopedTransaction) { + if (scopedTransaction.transaction() instanceof StateSignatureTransaction) { + return (ScopedSystemTransaction) scopedTransaction; + } + return null; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java index e4c860e816a3..8bc64086cf85 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/GossipEvent.java @@ -16,6 +16,8 @@ package com.swirlds.platform.event; +import static com.swirlds.common.threading.interrupt.Uninterruptable.abortAndLogIfInterrupted; + import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; @@ -30,6 +32,7 @@ import java.io.IOException; import java.time.Instant; import java.util.Objects; +import java.util.concurrent.CountDownLatch; /** * A class used to hold information about an event transferred through gossip @@ -79,6 +82,11 @@ private static final class ClassVersion { */ private NodeId senderId; + /** + * This latch counts down when prehandle has been called on all application transactions contained in this event. + */ + private final CountDownLatch prehandleCompleted = new CountDownLatch(1); + @SuppressWarnings("unused") // needed for RuntimeConstructable public GossipEvent() {} @@ -226,6 +234,20 @@ public void setSenderId(@NonNull final NodeId senderId) { this.senderId = senderId; } + /** + * Signal that all transactions have been prehandled for this event. + */ + public void signalPrehandleCompletion() { + prehandleCompleted.countDown(); + } + + /** + * Wait until all transactions have been prehandled for this event. + */ + public void awaitPrehandleCompletion() { + abortAndLogIfInterrupted(prehandleCompleted::await, "interrupted while waiting for prehandle completion"); + } + /** * {@inheritDoc} */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java index 395482f45168..5130379b8be1 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java @@ -57,7 +57,8 @@ * @param eventsLogDir eventStream files will be generated in this directory. * @param enableEventStreaming enable stream event to server. * @param prehandlePoolSize the size of the thread pool used for prehandling transactions - * @param useLegacyIntake if true then use the legacy intake monolith, if false then use the new intake pipeline + * @param useLegacyIntake if true then use the legacy intake monolith, if false then use the new + * intake pipeline */ @ConfigData("event") public record EventConfig( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/PreConsensusEventHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/PreConsensusEventHandler.java index 2ea31e29e6a3..b5cfac93ccf3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/PreConsensusEventHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/PreConsensusEventHandler.java @@ -101,7 +101,7 @@ public PreConsensusEventHandler( .setComponent(PLATFORM_THREAD_POOL_NAME) .setThreadName("thread-curr") .setStopBehavior(Stoppable.StopBehavior.BLOCKING) - .setHandler(swirldStateManager::handlePreConsensusEvent) + .setHandler(swirldStateManager::prehandleSystemTransactions) .setLogAfterPauseDuration(threadConfig.logStackTracePauseDuration()) .setMetricsConfiguration(new QueueThreadMetricsConfiguration(metrics).enableBusyTimeMetric()) .build(); @@ -148,7 +148,7 @@ public void preconsensusEvent(final EventImpl event) { } // All events are supplied for preHandle - swirldStateManager.preHandle(event); + swirldStateManager.prehandleApplicationTransactions(event); try { // update the estimate now, so the queue can sort on it diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java index 95f7975e6cd3..9e5f1affb295 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/EventImpl.java @@ -16,8 +16,6 @@ package com.swirlds.platform.internal; -import static com.swirlds.common.threading.interrupt.Uninterruptable.abortAndLogIfInterrupted; - import com.swirlds.common.constructable.ConstructableIgnored; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.RunningHash; @@ -58,7 +56,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.CountDownLatch; /** * An internal platform event. It holds all the event data relevant to the platform. It implements the Event interface @@ -112,11 +109,6 @@ public class EventImpl extends EventMetadata /** The number of application transactions in this round */ private int numAppTransactions = 0; - /** - * This latch counts down when prehandle has been called on all application transactions contained in this event. - */ - private final CountDownLatch prehandleCompleted = new CountDownLatch(1); - public EventImpl() {} public EventImpl(final BaseEventHashedData baseEventHashedData, final BaseEventUnhashedData baseEventUnhashedData) { @@ -172,20 +164,6 @@ public EventImpl( findSystemTransactions(); } - /** - * Signal that all transactions have been prehandled for this event. - */ - public void signalPrehandleCompletion() { - prehandleCompleted.countDown(); - } - - /** - * Wait until all transactions have been prehandled for this event. - */ - public void awaitPrehandleCompletion() { - abortAndLogIfInterrupted(prehandleCompleted::await, "interrupted while waiting for prehandle completion"); - } - /** * initialize RunningHash instance */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index 2b60e4b415e5..c061903902ae 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -24,6 +24,7 @@ import com.swirlds.platform.FreezePeriodChecker; import com.swirlds.platform.components.transaction.system.ConsensusSystemTransactionManager; import com.swirlds.platform.components.transaction.system.PreconsensusSystemTransactionManager; +import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.SwirldStateMetrics; @@ -127,13 +128,24 @@ public SwirldStateManager( } /** - * Invokes the pre-handle method. Called after the event has been verified but before - * {@link #handlePreConsensusEvent(EventImpl)}. + * Prehandles application transactions. Similar to {@link #prehandleApplicationTransactions(EventImpl)} but accepts + * a {@link GossipEvent} instead of an {@link EventImpl}. * - * @param event - * the event to handle + * @param event the event to handle */ - public void preHandle(final EventImpl event) { + public void prehandleApplicationTransactions(final GossipEvent event) { + // As a temporary work around, convert to EventImpl. + // Once we remove the legacy pathway, we can remove this. + final EventImpl eventImpl = new EventImpl(event, null, null); + prehandleApplicationTransactions(eventImpl); + } + + /** + * Prehandles application transactions. + * + * @param event the event to handle + */ + public void prehandleApplicationTransactions(final EventImpl event) { final long startTime = System.nanoTime(); State immutableState = latestImmutableState.get(); @@ -141,18 +153,18 @@ public void preHandle(final EventImpl event) { immutableState = latestImmutableState.get(); } transactionHandler.preHandle(event, immutableState.getSwirldState()); + event.getBaseEvent().signalPrehandleCompletion(); immutableState.release(); stats.preHandleTime(startTime, System.nanoTime()); } /** - * Handles an event before it reaches consensus.. + * Prehandles system transactions. * - * @param event - * the event to handle + * @param event the event to handle */ - public void handlePreConsensusEvent(final EventImpl event) { + public void prehandleSystemTransactions(final EventImpl event) { final long startTime = System.nanoTime(); preconsensusSystemTransactionManager.handleEvent(event); @@ -161,11 +173,10 @@ public void handlePreConsensusEvent(final EventImpl event) { } /** - * Handles the events in a consensus round. Implementations are responsible for invoking {@link - * SwirldState#handleConsensusRound(Round, SwirldDualState)}. + * Handles the events in a consensus round. Implementations are responsible for invoking + * {@link SwirldState#handleConsensusRound(Round, SwirldDualState)}. * - * @param round - * the round to handle + * @param round the round to handle */ public void handleConsensusRound(final ConsensusRound round) { final State state = stateRef.get(); @@ -180,8 +191,8 @@ public void handleConsensusRound(final ConsensusRound round) { } /** - * Returns the consensus state. The consensus state could become immutable at any time. Modifications must - * not be made to the returned state. + * Returns the consensus state. The consensus state could become immutable at any time. Modifications must not be + * made to the returned state. */ public State getConsensusState() { return stateRef.get(); @@ -190,8 +201,8 @@ public State getConsensusState() { /** * Invoked when a signed state is about to be created for the current freeze period. *

- * Invoked only by the consensus handling thread, so there is no chance of the state being modified by a - * concurrent thread. + * Invoked only by the consensus handling thread, so there is no chance of the state being modified by a concurrent + * thread. *

*/ public void savedStateInFreezePeriod() { @@ -276,8 +287,8 @@ public boolean isInFreezePeriod(final Instant timestamp) { /** *

Updates the state to a fast copy of itself and returns a reference to the previous state to be used for * signing. The reference count of the previous state returned by this is incremented to prevent it from being - * garbage collected until it is put in a signed state, so callers are responsible for decrementing the - * reference count when it is no longer needed.

+ * garbage collected until it is put in a signed state, so callers are responsible for decrementing the reference + * count when it is no longer needed.

* *

Consensus event handling will block until this method returns. Pre-consensus * event handling may or may not be blocked depending on the implementation.

diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java index 2eea97e99c83..7623a57f42a3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java @@ -52,7 +52,7 @@ public record LinkedEventIntakeWiring( */ public static LinkedEventIntakeWiring create(@NonNull final TaskScheduler> taskScheduler) { final OutputWire consensusRoundOutput = - taskScheduler.getOutputWire().buildSplitter(); + taskScheduler.getOutputWire().buildSplitter("linkedEventIntakeSplitter", "round lists"); return new LinkedEventIntakeWiring( taskScheduler.buildInputWire("linked events"), @@ -60,6 +60,7 @@ public static LinkedEventIntakeWiring create(@NonNull final TaskScheduler consensusRound.getGenerations().getMinGenerationNonAncient()), taskScheduler::flush); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java index 040e7453543e..1551ee212284 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java @@ -52,7 +52,7 @@ public static OrphanBufferWiring create(@NonNull final TaskScheduler internalEventValidatorScheduler, @@ -49,7 +51,9 @@ public record PlatformSchedulers( @NonNull TaskScheduler> linkedEventIntakeScheduler, @NonNull TaskScheduler eventCreationManagerScheduler, @NonNull TaskScheduler signedStateFileManagerScheduler, - @NonNull TaskScheduler stateSignerScheduler) { + @NonNull TaskScheduler stateSignerScheduler, + @NonNull TaskScheduler applicationTransactionPrehandlerScheduler, + @NonNull TaskScheduler stateSignatureCollectorScheduler) { /** * Instantiate the schedulers for the platform, for the given wiring model @@ -121,6 +125,21 @@ public static PlatformSchedulers create(@NonNull final PlatformContext context, model.schedulerBuilder("stateSigner") .withType(config.stateSignerSchedulerType()) .withUnhandledTaskCapacity(config.stateSignerUnhandledCapacity()) + .withMetricsBuilder(model.metricsBuilder().withUnhandledTaskMetricEnabled(true)) + .build() + .cast(), + model.schedulerBuilder("applicationTransactionPrehandler") + .withType(config.applicationTransactionPrehandlerSchedulerType()) + .withUnhandledTaskCapacity(config.applicationTransactionPrehandlerUnhandledCapacity()) + .withMetricsBuilder(model.metricsBuilder().withUnhandledTaskMetricEnabled(true)) + .withFlushingEnabled(true) + .build() + .cast(), + model.schedulerBuilder("stateSignatureCollector") + .withType(config.stateSignatureCollectorSchedulerType()) + .withUnhandledTaskCapacity(config.stateSignatureCollectorUnhandledCapacity()) + .withMetricsBuilder(model.metricsBuilder().withUnhandledTaskMetricEnabled(true)) + .withFlushingEnabled(true) .build() .cast()); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java index ea407df23b5b..fc45000bd04c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformSchedulersConfig.java @@ -23,31 +23,39 @@ /** * Contains configuration values for the platform schedulers. * - * @param internalEventValidatorSchedulerType the internal event validator scheduler type - * @param internalEventValidatorUnhandledCapacity number of unhandled events allowed in the internal event validator - * scheduler - * @param eventDeduplicatorSchedulerType the event deduplicator scheduler type - * @param eventDeduplicatorUnhandledCapacity number of unhandled tasks allowed in the event deduplicator - * scheduler - * @param eventSignatureValidatorSchedulerType the event signature validator scheduler type - * @param eventSignatureValidatorUnhandledCapacity number of unhandled tasks allowed in the event signature validator - * scheduler - * @param orphanBufferSchedulerType the orphan buffer scheduler type - * @param orphanBufferUnhandledCapacity number of unhandled tasks allowed in the orphan buffer scheduler - * @param inOrderLinkerSchedulerType the in-order linker scheduler type - * @param inOrderLinkerUnhandledCapacity number of unhandled tasks allowed in the in-order linker scheduler - * @param linkedEventIntakeSchedulerType the linked event intake scheduler type - * @param linkedEventIntakeUnhandledCapacity number of unhandled tasks allowed in the linked event intake - * scheduler - * @param eventCreationManagerSchedulerType the event creation manager scheduler type - * @param eventCreationManagerUnhandledCapacity number of unhandled tasks allowed in the event creation manager - * scheduler - * @param signedStateFileManagerSchedulerType the signed state file manager scheduler type - * @param signedStateFileManagerUnhandledCapacity number of unhandled tasks allowed in the signed state file manager - * scheduler - * @param stateSignerSchedulerType the state signer scheduler type - * @param stateSignerUnhandledCapacity number of unhandled tasks allowed in the state signer scheduler, - * default is -1 (unlimited) + * @param internalEventValidatorSchedulerType the internal event validator scheduler type + * @param internalEventValidatorUnhandledCapacity number of unhandled events allowed in the internal event + * validator scheduler + * @param eventDeduplicatorSchedulerType the event deduplicator scheduler type + * @param eventDeduplicatorUnhandledCapacity number of unhandled tasks allowed in the event deduplicator + * scheduler + * @param eventSignatureValidatorSchedulerType the event signature validator scheduler type + * @param eventSignatureValidatorUnhandledCapacity number of unhandled tasks allowed in the event signature + * validator scheduler + * @param orphanBufferSchedulerType the orphan buffer scheduler type + * @param orphanBufferUnhandledCapacity number of unhandled tasks allowed in the orphan buffer + * scheduler + * @param inOrderLinkerSchedulerType the in-order linker scheduler type + * @param inOrderLinkerUnhandledCapacity number of unhandled tasks allowed in the in-order linker + * scheduler + * @param linkedEventIntakeSchedulerType the linked event intake scheduler type + * @param linkedEventIntakeUnhandledCapacity number of unhandled tasks allowed in the linked event intake + * scheduler + * @param eventCreationManagerSchedulerType the event creation manager scheduler type + * @param eventCreationManagerUnhandledCapacity number of unhandled tasks allowed in the event creation + * manager scheduler + * @param signedStateFileManagerSchedulerType the signed state file manager scheduler type + * @param signedStateFileManagerUnhandledCapacity number of unhandled tasks allowed in the signed state file + * manager scheduler + * @param stateSignerSchedulerType the state signer scheduler type + * @param stateSignerUnhandledCapacity number of unhandled tasks allowed in the state signer + * scheduler, default is -1 (unlimited) + * @param applicationTransactionPrehandlerSchedulerType the application transaction prehandler scheduler type + * @param applicationTransactionPrehandlerUnhandledCapacity number of unhandled tasks allowed for the application + * transaction prehandler + * @param stateSignatureCollectorSchedulerType the state signature collector scheduler type + * @param stateSignatureCollectorUnhandledCapacity number of unhandled tasks allowed for the state signature + * collector */ @ConfigData("platformSchedulers") public record PlatformSchedulersConfig( @@ -68,4 +76,8 @@ public record PlatformSchedulersConfig( @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType signedStateFileManagerSchedulerType, @ConfigProperty(defaultValue = "20") int signedStateFileManagerUnhandledCapacity, @ConfigProperty(defaultValue = "SEQUENTIAL_THREAD") TaskSchedulerType stateSignerSchedulerType, - @ConfigProperty(defaultValue = "-1") int stateSignerUnhandledCapacity) {} + @ConfigProperty(defaultValue = "-1") int stateSignerUnhandledCapacity, + @ConfigProperty(defaultValue = "CONCURRENT") TaskSchedulerType applicationTransactionPrehandlerSchedulerType, + @ConfigProperty(defaultValue = "500") int applicationTransactionPrehandlerUnhandledCapacity, + @ConfigProperty(defaultValue = "SEQUENTIAL") TaskSchedulerType stateSignatureCollectorSchedulerType, + @ConfigProperty(defaultValue = "500") int stateSignatureCollectorUnhandledCapacity) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java index cf2a18a0be06..e560340e85c2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java @@ -40,11 +40,15 @@ import com.swirlds.platform.event.validation.InternalEventValidator; import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.eventhandling.TransactionPool; +import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateFileManager; +import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.platform.state.signed.StateDumpRequest; import com.swirlds.platform.system.status.PlatformStatusManager; +import com.swirlds.platform.wiring.components.ApplicationTransactionPrehandlerWiring; import com.swirlds.platform.wiring.components.EventCreationManagerWiring; +import com.swirlds.platform.wiring.components.StateSignatureCollectorWiring; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; @@ -64,9 +68,10 @@ public class PlatformWiring implements Startable, Stoppable, Clearable { private final EventCreationManagerWiring eventCreationManagerWiring; private final SignedStateFileManagerWiring signedStateFileManagerWiring; private final StateSignerWiring stateSignerWiring; + private final ApplicationTransactionPrehandlerWiring applicationTransactionPrehandlerWiring; + private final StateSignatureCollectorWiring stateSignatureCollectorWiring; private final PlatformCoordinator platformCoordinator; - /** * Constructor. * @@ -93,6 +98,12 @@ public PlatformWiring(@NonNull final PlatformContext platformContext, @NonNull f linkedEventIntakeWiring = LinkedEventIntakeWiring.create(schedulers.linkedEventIntakeScheduler()); eventCreationManagerWiring = EventCreationManagerWiring.create(platformContext, schedulers.eventCreationManagerScheduler()); + + applicationTransactionPrehandlerWiring = ApplicationTransactionPrehandlerWiring.create( + schedulers.applicationTransactionPrehandlerScheduler()); + stateSignatureCollectorWiring = + StateSignatureCollectorWiring.create(model, schedulers.stateSignatureCollectorScheduler()); + platformCoordinator = new PlatformCoordinator( internalEventValidatorWiring, eventDeduplicatorWiring, @@ -100,7 +111,9 @@ public PlatformWiring(@NonNull final PlatformContext platformContext, @NonNull f orphanBufferWiring, inOrderLinkerWiring, linkedEventIntakeWiring, - eventCreationManagerWiring); + eventCreationManagerWiring, + applicationTransactionPrehandlerWiring, + stateSignatureCollectorWiring); } else { internalEventValidatorWiring = null; eventDeduplicatorWiring = null; @@ -110,6 +123,8 @@ public PlatformWiring(@NonNull final PlatformContext platformContext, @NonNull f linkedEventIntakeWiring = null; eventCreationManagerWiring = null; platformCoordinator = null; + applicationTransactionPrehandlerWiring = null; + stateSignatureCollectorWiring = null; } signedStateFileManagerWiring = @@ -157,6 +172,10 @@ private void wire() { inOrderLinkerWiring.eventOutput().solderTo(linkedEventIntakeWiring.eventInput()); orphanBufferWiring.eventOutput().solderTo(eventCreationManagerWiring.eventInput()); eventCreationManagerWiring.newEventOutput().solderTo(internalEventValidatorWiring.eventInput(), INJECT); + orphanBufferWiring + .eventOutput() + .solderTo(applicationTransactionPrehandlerWiring.appTransactionsToPrehandleInput()); + orphanBufferWiring.eventOutput().solderTo(stateSignatureCollectorWiring.preconsensusEventInput()); solderMinimumGenerationNonAncient(); } @@ -206,6 +225,8 @@ public void wireExternalComponents( * @param inOrderLinker the in order linker to bind * @param linkedEventIntake the linked event intake to bind * @param eventCreationManager the event creation manager to bind + * @param swirldStateManager the swirld state manager to bind + * @param signedStateManager the signed state manager to bind */ public void bindIntake( @NonNull final InternalEventValidator internalEventValidator, @@ -214,7 +235,9 @@ public void bindIntake( @NonNull final OrphanBuffer orphanBuffer, @NonNull final InOrderLinker inOrderLinker, @NonNull final LinkedEventIntake linkedEventIntake, - @NonNull final EventCreationManager eventCreationManager) { + @NonNull final EventCreationManager eventCreationManager, + @NonNull final SwirldStateManager swirldStateManager, + @NonNull final SignedStateManager signedStateManager) { internalEventValidatorWiring.bind(internalEventValidator); eventDeduplicatorWiring.bind(eventDeduplicator); @@ -223,6 +246,8 @@ public void bindIntake( inOrderLinkerWiring.bind(inOrderLinker); linkedEventIntakeWiring.bind(linkedEventIntake); eventCreationManagerWiring.bind(eventCreationManager); + applicationTransactionPrehandlerWiring.bind(swirldStateManager); + stateSignatureCollectorWiring.bind(signedStateManager); } /** @@ -293,8 +318,8 @@ public InputWire getDumpStateToDiskInput() { /** * Get the input wire for signing a state *

- * Future work: this is a temporary hook to allow the components to sign a state, prior to the whole - * system being migrated to the new framework. + * Future work: this is a temporary hook to allow the components to sign a state, prior to the whole system being + * migrated to the new framework. * * @return the input wire for signing a state */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java index 5fbe04bea08e..cc33f67dd9ef 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/SignedStateFileManagerWiring.java @@ -56,12 +56,14 @@ public static SignedStateFileManagerWiring create(@NonNull final TaskScheduler new StateWrittenToDiskAction(ssr.round(), ssr.freezeState()))); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/ApplicationTransactionPrehandlerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/ApplicationTransactionPrehandlerWiring.java new file mode 100644 index 000000000000..137aeb83da5e --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/ApplicationTransactionPrehandlerWiring.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.wiring.components; + +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.input.InputWire; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.state.SwirldStateManager; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Wiring for application transaction prehandling. + * + * @param appTransactionsToPrehandleInput the input wire containing events where application transactions should be + * prehandled + * @param flushRunnable the runnable that will flush the application transaction prehandler + */ +public record ApplicationTransactionPrehandlerWiring( + @NonNull InputWire appTransactionsToPrehandleInput, @NonNull Runnable flushRunnable) { + + /** + * Create a new instance of this wiring. + * + * @param taskScheduler the task scheduler that will perform the prehandling + * @return the new wiring instance + */ + @NonNull + public static ApplicationTransactionPrehandlerWiring create(@NonNull final TaskScheduler taskScheduler) { + return new ApplicationTransactionPrehandlerWiring( + taskScheduler.buildInputWire("preconsensus events"), taskScheduler::flush); + } + + /** + * Bind the preconsensus event handler to the input wire. + * + * @param swirldStateManager manages operations on the current state, and also transaction prehandling on recent + * immutable states why not + */ + public void bind(@NonNull final SwirldStateManager swirldStateManager) { + ((BindableInputWire) appTransactionsToPrehandleInput).bind(event -> { + swirldStateManager.prehandleApplicationTransactions(event); + }); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java new file mode 100644 index 000000000000..d4f2c92e6b65 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.wiring.components; + +import com.swirlds.common.wiring.model.WiringModel; +import com.swirlds.common.wiring.schedulers.TaskScheduler; +import com.swirlds.common.wiring.transformers.WireListSplitter; +import com.swirlds.common.wiring.transformers.WireTransformer; +import com.swirlds.common.wiring.wires.input.BindableInputWire; +import com.swirlds.common.wiring.wires.input.InputWire; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; +import com.swirlds.platform.components.transaction.system.SystemTransactionExtractor; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.state.signed.SignedStateManager; +import com.swirlds.platform.system.transaction.StateSignatureTransaction; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.Objects; + +/** + * Wiring for the state signature collector. + */ +public class StateSignatureCollectorWiring { + + private final TaskScheduler taskScheduler; + private final BindableInputWire, Void> + stateSignatureTransactionInput; + private final InputWire preconsensusEventInput; + + /** + * Constructor. + * + * @param model the wiring model for the platform + * @param taskScheduler the task scheduler that will perform the prehandling + */ + private StateSignatureCollectorWiring( + @NonNull final WiringModel model, @NonNull final TaskScheduler taskScheduler) { + + this.taskScheduler = Objects.requireNonNull(taskScheduler); + + final WireTransformer>> systemTransactionsFilter = + new WireTransformer<>( + model, + "systemTransactionsFilter", + "preconsensus events", + SystemTransactionExtractor::getScopedSystemTransactions); + preconsensusEventInput = systemTransactionsFilter.getInputWire(); + + final WireListSplitter> systemTransactionsSplitter = + new WireListSplitter<>(model, "systemTransactionsSplitter", "system transaction lists"); + + final WireTransformer, ScopedSystemTransaction> + stateSignatureTransactionFilter = new WireTransformer<>( + model, + "stateSignatureTransactionsFilter", + "system transactions", + SystemTransactionExtractor::stateSignatureTransactionFilter); + + stateSignatureTransactionInput = taskScheduler.buildInputWire("state signature transactions"); + + systemTransactionsFilter.getOutputWire().solderTo(systemTransactionsSplitter.getInputWire()); + systemTransactionsSplitter.getOutputWire().solderTo(stateSignatureTransactionFilter.getInputWire()); + stateSignatureTransactionFilter.getOutputWire().solderTo(stateSignatureTransactionInput); + } + + /** + * Create a new instance of this wiring. + * + * @param model the wiring model + * @param taskScheduler the task scheduler that will perform the prehandling + * @return the new wiring instance + */ + @NonNull + public static StateSignatureCollectorWiring create( + @NonNull final WiringModel model, @NonNull final TaskScheduler taskScheduler) { + return new StateSignatureCollectorWiring(model, taskScheduler); + } + + /** + * Bind the preconsensus event handler to the input wire. + * + * @param signedStateManager collects and manages state signatures + */ + public void bind(@NonNull final SignedStateManager signedStateManager) { + Objects.requireNonNull(signedStateManager); + + stateSignatureTransactionInput.bind(scopedTransaction -> { + signedStateManager.handlePostconsensusSignatureTransaction( + scopedTransaction.submitterId(), scopedTransaction.transaction()); + }); + } + + /** + * Get the input wire for the preconsensus events. + * + * @return the input wire + */ + @NonNull + public InputWire preconsensusEventInput() { + return preconsensusEventInput; + } + + /** + * Flush the task scheduler. + */ + public void flush() { + taskScheduler.flush(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt index 50bde61d800c..612978ff7392 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt @@ -8,27 +8,30 @@ pcli diagram \ -s 'heartbeat:heartbeat:♡' \ -s 'eventCreationManager:non-validated events:†' \ -g 'Event Validation:internalEventValidator,eventDeduplicator,eventSignatureValidator' \ - -g 'Orphan Buffer:orphanBuffer,orphanBuffer_splitter' \ - -g 'Linked Event Intake:linkedEventIntake,linkedEventIntake_splitter,getMinimumGenerationNonAncient' \ - -g 'State File Management:signedStateFileManager,extract oldestMinimumGenerationOnDisk,to StateWrittenToDiskAction' \ + -g 'Orphan Buffer:orphanBuffer,orphanBufferSplitter' \ + -g 'Linked Event Intake:linkedEventIntake,linkedEventIntakeSplitter,getMinimumGenerationNonAncient' \ + -g 'State File Management:signedStateFileManager,extractOldestMinimumGenerationOnDisk,toStateWrittenToDiskAction' \ + -g 'State Signature Collection:systemTransactionsFilter,systemTransactionsSplitter,stateSignatureTransactionsFilter,stateSignatureCollector' \ -g 'Intake Pipeline:Event Validation,Orphan Buffer' \ -g 'Consensus Pipeline:inOrderLinker,Linked Event Intake' ######################################################################################################################## #### Simple Collapsing -Same as "Uncollapsed" but with low level things collapsed. Attempts to hide things like transformers and splitters. +Same as 'Uncollapsed' but with low level things collapsed. Attempts to hide things like transformers and splitters. pcli diagram \ -s 'getMinimumGenerationNonAncient:minimum generation non ancient:*' \ -s 'heartbeat:heartbeat:♡' \ -s 'eventCreationManager:non-validated events:†' \ -g 'Event Validation:internalEventValidator,eventDeduplicator,eventSignatureValidator' \ - -g 'Orphan Buffer:orphanBuffer,orphanBuffer_splitter' \ - -g 'Linked Event Intake:linkedEventIntake,linkedEventIntake_splitter,getMinimumGenerationNonAncient' \ - -g 'State File Management:signedStateFileManager,extract oldestMinimumGenerationOnDisk,to StateWrittenToDiskAction' \ + -g 'Orphan Buffer:orphanBuffer,orphanBufferSplitter' \ + -g 'Linked Event Intake:linkedEventIntake,linkedEventIntakeSplitter,getMinimumGenerationNonAncient' \ + -g 'State File Management:signedStateFileManager,extractOldestMinimumGenerationOnDisk,toStateWrittenToDiskAction' \ + -g 'State Signature Collection:systemTransactionsFilter,systemTransactionsSplitter,stateSignatureTransactionsFilter,stateSignatureCollector' \ -g 'Intake Pipeline:Event Validation,Orphan Buffer' \ -g 'Consensus Pipeline:inOrderLinker,Linked Event Intake' \ - -c "Orphan Buffer" \ - -c "Linked Event Intake" \ - -c "State File Management" \ No newline at end of file + -c 'Orphan Buffer' \ + -c 'Linked Event Intake' \ + -c 'State File Management' \ + -c 'State Signature Collection' diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/PreConsensusEventHandlerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/PreConsensusEventHandlerTests.java index 141ef5e9b2ac..8be0ce368592 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/PreConsensusEventHandlerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/PreConsensusEventHandlerTests.java @@ -81,7 +81,7 @@ void queueNotDrainedOnReconnect() { return null; }) .when(swirldStateManager) - .handlePreConsensusEvent(any(EventImpl.class)); + .prehandleSystemTransactions(any(EventImpl.class)); final ExecutorService executor = Executors.newFixedThreadPool(1); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java index 041122de5f0c..09525f078ae5 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/wiring/PlatformWiringTests.java @@ -30,7 +30,9 @@ import com.swirlds.platform.event.orphan.OrphanBuffer; import com.swirlds.platform.event.validation.EventSignatureValidator; import com.swirlds.platform.event.validation.InternalEventValidator; +import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.signed.SignedStateFileManager; +import com.swirlds.platform.state.signed.SignedStateManager; import com.swirlds.test.framework.config.TestConfigBuilder; import com.swirlds.test.framework.context.TestPlatformContextBuilder; import org.junit.jupiter.api.DisplayName; @@ -76,7 +78,9 @@ void testBindingsWithNewIntake() { mock(OrphanBuffer.class), mock(InOrderLinker.class), mock(LinkedEventIntake.class), - mock(EventCreationManager.class)); + mock(EventCreationManager.class), + mock(SwirldStateManager.class), + mock(SignedStateManager.class)); wiring.bind(mock(SignedStateFileManager.class), mock(StateSigner.class)); assertFalse(wiring.getModel().checkForUnboundInputWires()); diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/AbstractGraphGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/AbstractGraphGenerator.java index b0f769b390b7..91dabb36192d 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/AbstractGraphGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/generator/AbstractGraphGenerator.java @@ -86,6 +86,7 @@ public final void reset() { */ public final IndexedEvent generateEvent() { final IndexedEvent next = buildNextEvent(numEventsGenerated); + next.getBaseEvent().signalPrehandleCompletion(); numEventsGenerated++; updateMaxGeneration(next); return next; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java index 2d3dc2c90cda..8dc08865a15a 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java @@ -88,8 +88,11 @@ void test() { final EventImpl added = mock(EventImpl.class); when(added.getBaseEvent()).thenReturn(gossipEvent); final EventImpl consEvent1 = mock(EventImpl.class); + when(consEvent1.getBaseEvent()).thenReturn(mock(GossipEvent.class)); final EventImpl consEvent2 = mock(EventImpl.class); + when(consEvent2.getBaseEvent()).thenReturn(mock(GossipEvent.class)); final EventImpl stale = mock(EventImpl.class); + when(stale.getBaseEvent()).thenReturn(mock(GossipEvent.class)); final Queue staleQueue = new LinkedList<>(List.of(stale)); when(shadowGraph.findByGeneration(anyLong(), anyLong(), any())).thenReturn(staleQueue); From 65116feac1874f300823e93a6530b0a43dbc502e Mon Sep 17 00:00:00 2001 From: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:20:17 +0000 Subject: [PATCH 26/80] fix: Improved error message and for properties file missing (#10591) Signed-off-by: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> --- .../config/sources/PropertyConfigSource.java | 4 + .../sources/PropertyConfigSourceTest.java | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 hedera-node/hedera-config/src/test/java/com/hedera/node/config/sources/PropertyConfigSourceTest.java diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java index ae3ca234f6f6..b0db6248c832 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/sources/PropertyConfigSource.java @@ -63,6 +63,10 @@ private static Properties loadProperties(@NonNull final String resourceName) { // It is important to use the Thread's context class loader because the resource we want to load might // be part of another module. try (final var in = Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceName)) { + if (in == null) { + throw new UncheckedIOException( + "Property file " + resourceName + " could not be found", new IOException()); + } final var props = new Properties(); props.load(in); return props; diff --git a/hedera-node/hedera-config/src/test/java/com/hedera/node/config/sources/PropertyConfigSourceTest.java b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/sources/PropertyConfigSourceTest.java new file mode 100644 index 000000000000..686fe7297a5c --- /dev/null +++ b/hedera-node/hedera-config/src/test/java/com/hedera/node/config/sources/PropertyConfigSourceTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.config.sources; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class PropertyConfigSourceTest { + + private Properties properties; + + @BeforeEach + void setUp() { + properties = new Properties(); + properties.setProperty("key1", "value1"); + properties.setProperty("key2", "value2"); + } + + @Test + void testExistentPropertyConfigSource() { + // given + int ordinal = 1; // used to determine priority of config source + + // when + PropertyConfigSource propertyConfigSource = new PropertyConfigSource(properties, ordinal); + + // then + assertEquals(2, propertyConfigSource.getPropertyNames().size()); + assertEquals("value1", propertyConfigSource.getValue("key1")); + assertEquals("value2", propertyConfigSource.getValue("key2")); + assertEquals(ordinal, propertyConfigSource.getOrdinal()); + } + + @Test + void testPropertyConfigSourceWhenPropertyFileNotFound() { + // given + String nonExistentFile = "non-existent-file.properties"; + + // then + assertThrows(UncheckedIOException.class, () -> new PropertyConfigSource(nonExistentFile, 1)); + } + + @Test + void testLoadProperties() throws Exception { + // given + String existingFile = "bootstrap.properties"; + + // when + Method method = PropertyConfigSource.class.getDeclaredMethod("loadProperties", String.class); + method.setAccessible(true); + + // then + // Test successful path + Properties properties = (Properties) method.invoke(null, existingFile); + assertNotNull(properties); + } + + @AfterEach + void tearDown() { + properties = null; + } +} From f10afe62a16bc94d8b61132a3ee82395914d29b3 Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Thu, 21 Dec 2023 11:23:12 +0200 Subject: [PATCH 27/80] fix: ERC approve and remove scenarios (#10325) Signed-off-by: Mustafa Uzun Signed-off-by: Stanimir Stoyanov Co-authored-by: Stanimir Stoyanov Co-authored-by: Michael Tinker --- .../AbstractGrantApprovalCall.java | 82 +++++++++++++++++-- .../grantapproval/ERCGrantApprovalCall.java | 23 ++++++ .../impl/utils/SystemContractUtils.java | 23 ++++++ .../contract/impl/test/TestHelpers.java | 2 + .../ClassicGrantApprovalCallTest.java | 16 ++++ .../ERCGrantApprovalCallTest.java | 78 ++++++++++++++++++ .../test/utils/SystemContractUtilsTest.java | 15 ++++ .../CryptoDeleteAllowanceHandler.java | 13 ++- .../CryptoDeleteAllowanceHandlerTest.java | 6 ++ .../precompile/ERCPrecompileSuite.java | 1 + 10 files changed, 247 insertions(+), 12 deletions(-) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java index e196f39346bd..66ffb61d5519 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/AbstractGrantApprovalCall.java @@ -16,11 +16,16 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.state.token.AccountApprovalForAllAllowance; import com.hedera.hapi.node.token.CryptoApproveAllowanceTransactionBody; +import com.hedera.hapi.node.token.CryptoDeleteAllowanceTransactionBody; import com.hedera.hapi.node.token.NftAllowance; +import com.hedera.hapi.node.token.NftRemoveAllowance; import com.hedera.hapi.node.token.TokenAllowance; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -29,6 +34,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; +import java.util.List; public abstract class AbstractGrantApprovalCall extends AbstractHtsCall { protected final VerificationStrategy verificationStrategy; @@ -60,22 +66,60 @@ protected AbstractGrantApprovalCall( } public TransactionBody callGrantApproval() { - return TransactionBody.newBuilder() - .cryptoApproveAllowance(approve(token, spender, amount, tokenType)) + if (tokenType == TokenType.NON_FUNGIBLE_UNIQUE) { + var ownerId = getOwnerId(); + + if (ownerId != null && !isNftApprovalRevocation()) { + List accountApprovalForAllAllowances = enhancement + .nativeOperations() + .getAccount(ownerId.accountNum()) + .approveForAllNftAllowances(); + if (accountApprovalForAllAllowances != null) { + for (var approvedForAll : accountApprovalForAllAllowances) { + if (approvedForAll.tokenId().equals(token)) { + return buildCryptoApproveAllowance(approveDelegate(ownerId, approvedForAll.spenderId())); + } + } + } + } + + return isNftApprovalRevocation() + ? buildCryptoDeleteAllowance(remove(ownerId)) + : buildCryptoApproveAllowance(approve(ownerId)); + } else { + return buildCryptoApproveAllowance(approve(senderId)); + } + } + + private CryptoDeleteAllowanceTransactionBody remove(AccountID ownerId) { + return CryptoDeleteAllowanceTransactionBody.newBuilder() + .nftAllowances(NftRemoveAllowance.newBuilder() + .tokenId(token) + .owner(ownerId) + .serialNumbers(amount.longValue()) + .build()) .build(); } - private CryptoApproveAllowanceTransactionBody approve( - @NonNull final TokenID token, - @NonNull final AccountID spender, - @NonNull final BigInteger amount, - @NonNull final TokenType tokenType) { + private CryptoApproveAllowanceTransactionBody approveDelegate(AccountID ownerId, AccountID delegateSpenderId) { + return CryptoApproveAllowanceTransactionBody.newBuilder() + .nftAllowances(NftAllowance.newBuilder() + .tokenId(token) + .spender(spender) + .delegatingSpender(delegateSpenderId) + .owner(ownerId) + .serialNumbers(amount.longValue()) + .build()) + .build(); + } + + private CryptoApproveAllowanceTransactionBody approve(AccountID ownerId) { return tokenType.equals(TokenType.FUNGIBLE_COMMON) ? CryptoApproveAllowanceTransactionBody.newBuilder() .tokenAllowances(TokenAllowance.newBuilder() .tokenId(token) .spender(spender) - .owner(senderId) + .owner(ownerId) .amount(amount.longValue()) .build()) .build() @@ -83,9 +127,29 @@ private CryptoApproveAllowanceTransactionBody approve( .nftAllowances(NftAllowance.newBuilder() .tokenId(token) .spender(spender) - .owner(senderId) + .owner(ownerId) .serialNumbers(amount.longValue()) .build()) .build(); } + + private TransactionBody buildCryptoDeleteAllowance(CryptoDeleteAllowanceTransactionBody body) { + return TransactionBody.newBuilder().cryptoDeleteAllowance(body).build(); + } + + private TransactionBody buildCryptoApproveAllowance(CryptoApproveAllowanceTransactionBody body) { + return TransactionBody.newBuilder().cryptoApproveAllowance(body).build(); + } + + private AccountID getOwnerId() { + final var nft = enhancement.nativeOperations().getNft(token.tokenNum(), amount.longValue()); + requireNonNull(nft); + return nft.hasOwnerId() + ? nft.ownerId() + : enhancement.nativeOperations().getToken(token.tokenNum()).treasuryAccountId(); + } + + private boolean isNftApprovalRevocation() { + return spender.accountNum() == 0; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java index e800efb0d6c5..f4ffb552021e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCall.java @@ -16,10 +16,14 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_SPENDER_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmContractId; +import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedForProto; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ResponseCodeEnum; @@ -28,10 +32,14 @@ import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; +import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; +import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; +import org.hyperledger.besu.datatypes.Address; public class ERCGrantApprovalCall extends AbstractGrantApprovalCall { @@ -53,7 +61,22 @@ public PricedResult execute() { if (token == null) { return reversionWith(INVALID_TOKEN_ID, gasCalculator.canonicalGasRequirement(DispatchType.APPROVE)); } + final var spenderAccount = enhancement.nativeOperations().getAccount(spender.accountNum()); final var body = callGrantApproval(); + if (spenderAccount == null && spender.accountNum() != 0) { + var gasRequirement = gasCalculator.canonicalGasRequirement(DispatchType.APPROVE); + var revertResult = FullResult.revertResult(INVALID_ALLOWANCE_SPENDER_ID, gasRequirement); + var result = gasOnly(revertResult, INVALID_ALLOWANCE_SPENDER_ID, false); + + var contractID = asEvmContractId(Address.fromHexString(HTS_EVM_ADDRESS)); + var encodedRc = ReturnTypes.encodedRc(INVALID_ALLOWANCE_SPENDER_ID).array(); + var contractFunctionResult = contractFunctionResultFailedForProto( + gasRequirement, INVALID_ALLOWANCE_SPENDER_ID.protoName(), contractID, Bytes.wrap(encodedRc)); + + enhancement.systemOperations().externalizeResult(contractFunctionResult, INVALID_ALLOWANCE_SPENDER_ID); + + return result; + } final var recordBuilder = systemContractOperations() .dispatch(body, verificationStrategy, senderId, SingleTransactionRecordBuilder.class); final var gasRequirement = gasCalculator.gasRequirement(body, DispatchType.APPROVE, senderId); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java index d8a39596917b..75259e46262c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SystemContractUtils.java @@ -90,6 +90,29 @@ public static ContractFunctionResult contractFunctionResultFailedFor( .build(); } + /** + * Create an error contract function result. + * + * @param gasUsed Report the gas used. + * @param errorMsg The error message to report back to the caller. + * @param contractID The contract ID. + * @param contractCallResult Bytes representation of the contract call result error + * @return The created contract function result when for a failed call. + */ + @NonNull + public static ContractFunctionResult contractFunctionResultFailedForProto( + final long gasUsed, + final String errorMsg, + final ContractID contractID, + final com.hedera.pbj.runtime.io.buffer.Bytes contractCallResult) { + return ContractFunctionResult.newBuilder() + .gasUsed(gasUsed) + .contractID(contractID) + .errorMessage(errorMsg) + .contractCallResult(contractCallResult) + .build(); + } + private static ContractID contractIdFromEvmAddress(final byte[] bytes) { return ContractID.newBuilder() .contractNum(Longs.fromByteArray(Arrays.copyOfRange(bytes, 12, 20))) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java index 95c487da9287..fc7317d1f91e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java @@ -573,6 +573,8 @@ public class TestHelpers { public static final Address OWNER_BESU_ADDRESS = pbjToBesuAddress(OWNER_ADDRESS); public static final AccountID UNAUTHORIZED_SPENDER_ID = AccountID.newBuilder().accountNum(999999L).build(); + public static final AccountID REVOKE_APPROVAL_SPENDER_ID = + AccountID.newBuilder().accountNum(0L).build(); public static final Bytes UNAUTHORIZED_SPENDER_ADDRESS = Bytes.fromHex("b284224b8b83a724438cc3cc7c0d333a2b6b3222"); public static final com.esaulpaugh.headlong.abi.Address UNAUTHORIZED_SPENDER_HEADLONG_ADDRESS = asHeadlongAddress(UNAUTHORIZED_SPENDER_ADDRESS.toByteArray()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java index b715def87aed..d7c8b3c3347e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ClassicGrantApprovalCallTest.java @@ -27,6 +27,9 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Nft; +import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.grantapproval.ClassicGrantApprovalCall; @@ -51,6 +54,15 @@ public class ClassicGrantApprovalCallTest extends HtsCallTestBase { @Mock private SystemContractGasCalculator systemContractGasCalculator; + @Mock + private Nft nft; + + @Mock + private Token token; + + @Mock + private Account account; + @Test void fungibleApprove() { subject = new ClassicGrantApprovalCall( @@ -87,6 +99,10 @@ void nftApprove() { TokenType.NON_FUNGIBLE_UNIQUE); given(systemContractOperations.dispatch(any(), any(), any(), any())).willReturn(recordBuilder); given(recordBuilder.status()).willReturn(ResponseCodeEnum.SUCCESS); + given(nativeOperations.getNft(NON_FUNGIBLE_TOKEN_ID.tokenNum(), 100L)).willReturn(nft); + given(nativeOperations.getToken(NON_FUNGIBLE_TOKEN_ID.tokenNum())).willReturn(token); + given(token.treasuryAccountId()).willReturn(OWNER_ID); + given(nativeOperations.getAccount(OWNER_ID.accountNum())).willReturn(account); final var result = subject.execute(frame).fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java index 5a087c080276..f413496bdd35 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/grantapproval/ERCGrantApprovalCallTest.java @@ -19,15 +19,20 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.OWNER_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.REVOKE_APPROVAL_SPENDER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.UNAUTHORIZED_SPENDER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.asBytesResult; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.TokenType; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Nft; +import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; @@ -37,7 +42,9 @@ import com.hedera.node.app.service.token.records.CryptoTransferRecordBuilder; import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import java.math.BigInteger; +import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.frame.MessageFrame.State; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -54,6 +61,15 @@ class ERCGrantApprovalCallTest extends HtsCallTestBase { @Mock private CryptoTransferRecordBuilder recordBuilder; + @Mock + private Nft nft; + + @Mock + private Token token; + + @Mock + private Account account; + @Test void erc20approve() { subject = new ERCGrantApprovalCall( @@ -72,6 +88,7 @@ void erc20approve() { eq(SingleTransactionRecordBuilder.class))) .willReturn(recordBuilder); given(recordBuilder.status()).willReturn(ResponseCodeEnum.SUCCESS); + given(nativeOperations.getAccount(anyLong())).willReturn(account); final var result = subject.execute().fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); @@ -99,6 +116,67 @@ void erc721approve() { eq(SingleTransactionRecordBuilder.class))) .willReturn(recordBuilder); given(recordBuilder.status()).willReturn(ResponseCodeEnum.SUCCESS); + given(nativeOperations.getNft(NON_FUNGIBLE_TOKEN_ID.tokenNum(), 100L)).willReturn(nft); + given(nativeOperations.getToken(NON_FUNGIBLE_TOKEN_ID.tokenNum())).willReturn(token); + given(token.treasuryAccountId()).willReturn(OWNER_ID); + given(nativeOperations.getAccount(anyLong())).willReturn(account); + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + asBytesResult(GrantApprovalTranslator.ERC_GRANT_APPROVAL_NFT + .getOutputs() + .encodeElements()), + result.getOutput()); + } + + @Test + void erc721approveFailsWithInvalidSpenderAllowance() { + subject = new ERCGrantApprovalCall( + mockEnhancement(), + systemContractGasCalculator, + verificationStrategy, + OWNER_ID, + NON_FUNGIBLE_TOKEN_ID, + UNAUTHORIZED_SPENDER_ID, + BigInteger.valueOf(100L), + TokenType.NON_FUNGIBLE_UNIQUE); + given(nativeOperations.getNft(NON_FUNGIBLE_TOKEN_ID.tokenNum(), 100L)).willReturn(nft); + given(nativeOperations.getToken(NON_FUNGIBLE_TOKEN_ID.tokenNum())).willReturn(token); + given(token.treasuryAccountId()).willReturn(OWNER_ID); + given(nativeOperations.getAccount(anyLong())).willReturn(null).willReturn(account); + final var result = subject.execute().fullResult().result(); + + assertEquals(State.REVERT, result.getState()); + assertEquals( + Bytes.wrap(ResponseCodeEnum.INVALID_ALLOWANCE_SPENDER_ID + .protoName() + .getBytes()), + result.getOutput()); + } + + @Test + void erc721revoke() { + subject = new ERCGrantApprovalCall( + mockEnhancement(), + systemContractGasCalculator, + verificationStrategy, + OWNER_ID, + NON_FUNGIBLE_TOKEN_ID, + REVOKE_APPROVAL_SPENDER_ID, + BigInteger.valueOf(100L), + TokenType.NON_FUNGIBLE_UNIQUE); + given(systemContractOperations.dispatch( + any(TransactionBody.class), + eq(verificationStrategy), + eq(OWNER_ID), + eq(SingleTransactionRecordBuilder.class))) + .willReturn(recordBuilder); + given(recordBuilder.status()).willReturn(ResponseCodeEnum.SUCCESS); + given(nativeOperations.getNft(NON_FUNGIBLE_TOKEN_ID.tokenNum(), 100L)).willReturn(nft); + given(nativeOperations.getToken(NON_FUNGIBLE_TOKEN_ID.tokenNum())).willReturn(token); + given(nativeOperations.getAccount(anyLong())).willReturn(account); + given(token.treasuryAccountId()).willReturn(OWNER_ID); final var result = subject.execute().fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java index 4be9383023f6..fd948b65d075 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SystemContractUtilsTest.java @@ -31,6 +31,8 @@ class SystemContractUtilsTest { private static final long gasUsed = 0L; private static final Bytes result = Bytes.EMPTY; + private static final com.hedera.pbj.runtime.io.buffer.Bytes contractCallResult = + com.hedera.pbj.runtime.io.buffer.Bytes.wrap("Contract Call Result"); private static final ContractID contractID = ContractID.newBuilder().contractNum(111).build(); private static final String errorMessage = ResponseCodeEnum.FAIL_INVALID.name(); @@ -64,4 +66,17 @@ void validateFailedContractResults() { final var actual = SystemContractUtils.contractFunctionResultFailedFor(gasUsed, errorMessage, contractID); assertThat(actual).isEqualTo(expected); } + + @Test + void validateFailedContractResultsForProto() { + final var expected = ContractFunctionResult.newBuilder() + .gasUsed(gasUsed) + .errorMessage(errorMessage) + .contractID(contractID) + .contractCallResult(contractCallResult) + .build(); + final var actual = SystemContractUtils.contractFunctionResultFailedForProto( + gasUsed, errorMessage, contractID, contractCallResult); + assertThat(actual).isEqualTo(expected); + } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java index 6ab784ecfe85..6c03145bd45d 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java @@ -88,15 +88,22 @@ public void preHandle(@NonNull final PreHandleContext context) throws PreCheckEx // Every owner whose allowances are being removed should sign (or the payer, if there is no owner) for (final var allowance : op.nftAllowancesOrElse(emptyList())) { if (allowance.hasOwner()) { - context.requireKeyOrThrow(allowance.ownerOrThrow(), INVALID_ALLOWANCE_OWNER_ID); + final var store = context.createStore(ReadableAccountStore.class); + final var ownerId = allowance.ownerOrThrow(); + final var owner = store.getAccountById(ownerId); + final var approvedForAll = owner.approveForAllNftAllowancesOrElse(emptyList()).stream() + .anyMatch(approveForAll -> approveForAll.tokenId().equals(allowance.tokenId()) + && approveForAll.spenderId().equals(context.payer())); + if (!context.payer().equals(ownerId) && !approvedForAll) { + context.requireKeyOrThrow(ownerId, INVALID_ALLOWANCE_OWNER_ID); + } } } } @Override public void handle(@NonNull final HandleContext context) throws HandleException { - final var txn = context.body(); - final var payer = txn.transactionIDOrThrow().accountIDOrThrow(); + final var payer = context.payer(); final var accountStore = context.writableStore(WritableAccountStore.class); // validate payer account exists diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java index d2001cf4a35f..1206eb70aa26 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java @@ -100,6 +100,7 @@ void happyPathDeletesAllowances() { final var txn = cryptoDeleteAllowanceTransaction(payerId); given(handleContext.body()).willReturn(txn); + given(handleContext.payer()).willReturn(payerId); given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); assertThat(ownerAccount.approveForAllNftAllowances()).hasSize(1); @@ -124,6 +125,7 @@ void canDeleteAllowancesOnTreasury() { final var txn = cryptoDeleteAllowanceTransaction(payerId); given(handleContext.body()).willReturn(txn); + given(handleContext.payer()).willReturn(payerId); given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); assertThat(ownerAccount.approveForAllNftAllowances()).hasSize(1); @@ -175,6 +177,7 @@ void failsDeleteAllowancesOnInvalidTreasury() { writableNftStore.put(nftSl2.copyBuilder().spenderId(spenderId).build()); final var txn = cryptoDeleteAllowanceTransaction(payerId); + given(handleContext.payer()).willReturn(payerId); given(handleContext.body()).willReturn(txn); given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); @@ -206,6 +209,7 @@ void doesntThrowIfAllowanceToBeDeletedDoesNotExist() { final var txn = txnWithAllowance(payerId, nftAllowance); given(handleContext.body()).willReturn(txn); + given(handleContext.payer()).willReturn(payerId); given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); assertThat(ownerAccount.approveForAllNftAllowances()).hasSize(1); @@ -235,6 +239,7 @@ void considersPayerIfOwnerNotSpecifiedAndFailIfDoesntOwn() { final var txn = txnWithAllowance(payerId, nftAllowance); given(handleContext.body()).willReturn(txn); + given(handleContext.payer()).willReturn(payerId); assertThat(ownerAccount.approveForAllNftAllowances()).hasSize(1); assertThat(writableNftStore.get(nftIdSl1).ownerId()).isEqualTo(ownerId); @@ -261,6 +266,7 @@ void considersPayerIfOwnerNotSpecified() { final var txn = txnWithAllowance(payerId, nftAllowance); given(handleContext.body()).willReturn(txn); + given(handleContext.payer()).willReturn(payerId); assertThat(ownerAccount.approveForAllNftAllowances()).hasSize(1); assertThat(writableNftStore.get(nftIdSl1).ownerId()).isEqualTo(payerId); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java index 13901687365f..6d6435fe5676 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ERCPrecompileSuite.java @@ -1516,6 +1516,7 @@ final HapiSpec someErc721NegativeTransferFromScenariosPass() { recordWith().status(SPENDER_DOES_NOT_HAVE_ALLOWANCE))); } + @HapiTest final HapiSpec someErc721ApproveAndRemoveScenariosPass() { final AtomicReference tokenMirrorAddr = new AtomicReference<>(); final AtomicReference aCivilianMirrorAddr = new AtomicReference<>(); From 220fdba189150c40ad1dc3e9f93d4345c91a9d00 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Thu, 21 Dec 2023 07:34:51 -0600 Subject: [PATCH 28/80] chore: Disable reconnect test in CI (#10603) Signed-off-by: Neeharika-Sompalli --- .../regression/system/MixedOpsNodeDeathReconnectTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java index 95f62b8684c4..ec75fe157b7a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/MixedOpsNodeDeathReconnectTest.java @@ -38,7 +38,6 @@ import static com.hedera.services.bdd.suites.token.TokenTransactSpecs.SUPPLY_KEY; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; @@ -54,7 +53,7 @@ * shuts one node,and starts it back after some time. Node will reconnect, and once reconnect is completed * submits the same burst of mixed operations again. */ -@HapiTestSuite // This should be enabled once there is a different tag to be run in CI, since it shuts down nodes +// @HapiTestSuite // This should be enabled once there is a different tag to be run in CI, since it shuts down nodes public class MixedOpsNodeDeathReconnectTest extends HapiSuite { private static final Logger log = LogManager.getLogger(MixedOpsNodeDeathReconnectTest.class); From 5cbc6f0f329d03621f2d0a1813d5df0ce7cd42e9 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:14:30 -0600 Subject: [PATCH 29/80] feat: add manual link support to wiring diagram (#10607) Signed-off-by: Cody Littley --- .../common/wiring/model/ModelManualLink.java | 29 +++++++++++++++ .../common/wiring/model/WiringModel.java | 5 ++- .../wiring/model/internal/ModelEdge.java | 31 +++++++++++++--- .../model/internal/StandardWiringModel.java | 9 +++-- .../model/internal/WiringFlowchart.java | 31 +++++++++++++++- .../common/wiring/model/ModelTests.java | 2 +- .../swirlds/platform/cli/DiagramCommand.java | 37 ++++++++++++++++++- .../platform/wiring/diagram-commands.txt | 2 + 8 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/ModelManualLink.java diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/ModelManualLink.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/ModelManualLink.java new file mode 100644 index 000000000000..505ea6ce8b96 --- /dev/null +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/ModelManualLink.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.common.wiring.model; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Describes a manual link between two components. Useful for adding information to the diagram that is not captured by + * the wiring framework + * + * @param source the source scheduler + * @param label the label on the edge + * @param target the target scheduler + */ +public record ModelManualLink(@NonNull String source, @NonNull String label, @NonNull String target) {} diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/WiringModel.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/WiringModel.java index ae70826b0589..fb356b32349d 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/WiringModel.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/WiringModel.java @@ -118,11 +118,14 @@ static WiringModel create(@NonNull final Metrics metrics, @NonNull final Time ti * * @param groups optional groupings of vertices * @param substitutions edges to substitute + * @param manualLinks manual links to add to the diagram * @return a mermaid style wiring diagram */ @NonNull String generateWiringDiagram( - @NonNull final List groups, @NonNull final List substitutions); + @NonNull final List groups, + @NonNull final List substitutions, + @NonNull final List manualLinks); /** * Start everything in the model that needs to be started. Performs static analysis of the wiring topology and diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/ModelEdge.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/ModelEdge.java index 3b63c417c6cb..e95d3b944aae 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/ModelEdge.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/ModelEdge.java @@ -31,6 +31,7 @@ public class ModelEdge implements Comparable { private ModelVertex destination; private final String label; private final boolean insertionIsBlocking; + private final boolean manual; /** * Constructor. @@ -39,17 +40,21 @@ public class ModelEdge implements Comparable { * @param destination the destination vertex * @param label the label of the edge, if a label is not needed for an edge then holds the value "" * @param insertionIsBlocking true if the insertion of this edge may block until capacity is available + * @param manual true if this edge has been manually added to the diagram, false if this edge + * represents something tracked by the wiring framework */ public ModelEdge( @NonNull final ModelVertex source, @NonNull final ModelVertex destination, @NonNull final String label, - final boolean insertionIsBlocking) { + final boolean insertionIsBlocking, + final boolean manual) { this.source = Objects.requireNonNull(source); this.destination = Objects.requireNonNull(destination); this.label = Objects.requireNonNull(label); this.insertionIsBlocking = insertionIsBlocking; + this.manual = manual; } /** @@ -146,6 +151,18 @@ public int compareTo(@NonNull final ModelEdge that) { return this.label.compareTo(that.label); } + /** + * Get the character for the outgoing end of this edge. + */ + @NonNull + private String getArrowCharacter() { + if (manual) { + return "o"; + } else { + return ">"; + } + } + /** * Render this edge to a string builder. * @@ -155,22 +172,24 @@ public int compareTo(@NonNull final ModelEdge that) { public void render(@NonNull final StringBuilder sb, @NonNull final MermaidNameShortener nameProvider) { final String sourceName = nameProvider.getShortVertexName(source.getName()); - sb.append(sourceName); + sb.append(sourceName).append(" "); if (insertionIsBlocking) { if (label.isEmpty()) { - sb.append(" --> "); + sb.append("--"); } else { - sb.append(" -- \"").append(label).append("\" --> "); + sb.append("-- \"").append(label).append("\" --"); } } else { if (label.isEmpty()) { - sb.append(" -.-> "); + sb.append("-.-"); } else { - sb.append(" -. \"").append(label).append("\" .-> "); + sb.append("-. \"").append(label).append("\" .-"); } } + sb.append(getArrowCharacter()).append(" "); + final String destinationName = nameProvider.getShortVertexName(destination.getName()); sb.append(destinationName).append("\n"); } diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/StandardWiringModel.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/StandardWiringModel.java index 8c334b4cfd7b..dab8e2665468 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/StandardWiringModel.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/StandardWiringModel.java @@ -24,6 +24,7 @@ import com.swirlds.common.metrics.Metrics; import com.swirlds.common.wiring.model.ModelEdgeSubstitution; import com.swirlds.common.wiring.model.ModelGroup; +import com.swirlds.common.wiring.model.ModelManualLink; import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.common.wiring.schedulers.TaskScheduler; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerBuilder; @@ -171,8 +172,10 @@ public boolean checkForUnboundInputWires() { @NonNull @Override public String generateWiringDiagram( - @NonNull final List groups, @NonNull final List substitutions) { - final WiringFlowchart flowchart = new WiringFlowchart(vertices, substitutions, groups); + @NonNull final List groups, + @NonNull final List substitutions, + @NonNull final List manualLinks) { + final WiringFlowchart flowchart = new WiringFlowchart(vertices, substitutions, groups, manualLinks); return flowchart.render(); } @@ -228,7 +231,7 @@ public void registerEdge( final ModelVertex destination = getVertex(destinationVertex); final boolean blocking = blockingEdge && destination.isInsertionIsBlocking(); - final ModelEdge edge = new ModelEdge(origin, destination, label, blocking); + final ModelEdge edge = new ModelEdge(origin, destination, label, blocking, false); origin.getOutgoingEdges().add(edge); final boolean unique = edges.add(edge); diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/WiringFlowchart.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/WiringFlowchart.java index 3492cb76634a..05e9f2c08c3d 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/WiringFlowchart.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/wiring/model/internal/WiringFlowchart.java @@ -26,6 +26,7 @@ import com.swirlds.common.wiring.model.ModelEdgeSubstitution; import com.swirlds.common.wiring.model.ModelGroup; +import com.swirlds.common.wiring.model.ModelManualLink; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; @@ -62,15 +63,18 @@ public class WiringFlowchart { * @param modelVertexMap a map from vertex name to vertex * @param substitutions a list of edge substitutions to perform * @param groups a list of groups to create + * @param manualLinks a list of manual links to draw */ public WiringFlowchart( @NonNull final Map modelVertexMap, @NonNull final List substitutions, - @NonNull final List groups) { + @NonNull final List groups, + @NonNull final List manualLinks) { Objects.requireNonNull(modelVertexMap); vertexMap = copyVertexMap(modelVertexMap); + addManualLinks(manualLinks); substituteEdges(substitutions); handleGroups(groups); } @@ -104,7 +108,7 @@ private Map copyVertexMap(@NonNull final Map copyVertexMap(@NonNull final Map manualLinks) { + for (final ModelManualLink link : manualLinks) { + final ModelVertex source = vertexMap.get(link.source()); + final ModelVertex destination = vertexMap.get(link.target()); + + if (source == null) { + throw new IllegalStateException("Source vertex " + link.source() + " does not exist."); + } + + if (destination == null) { + throw new IllegalStateException("Destination vertex " + link.target() + " does not exist."); + } + + final ModelEdge edge = new ModelEdge(source, destination, link.label(), false, true); + source.getOutgoingEdges().add(edge); + } + } + /** * Find all edges that need to be substituted and perform the substitution. */ diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java index 76b907209267..54361e000f4c 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/wiring/model/ModelTests.java @@ -58,7 +58,7 @@ private static void validateModel( assertEquals(illegalDirectSchedulerUseExpected, illegalDirectSchedulerUseDetected); // Should not throw. - final String diagram = model.generateWiringDiagram(List.of(), List.of()); + final String diagram = model.generateWiringDiagram(List.of(), List.of(), List.of()); if (printMermaidDiagram) { System.out.println(diagram); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java index f4e9e40f337a..eceef7ce325b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java @@ -26,6 +26,7 @@ import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.wiring.model.ModelEdgeSubstitution; import com.swirlds.common.wiring.model.ModelGroup; +import com.swirlds.common.wiring.model.ModelManualLink; import com.swirlds.config.api.Configuration; import com.swirlds.platform.config.DefaultConfiguration; import com.swirlds.platform.wiring.PlatformWiring; @@ -47,6 +48,7 @@ public final class DiagramCommand extends AbstractCommand { private List groupStrings = List.of(); private Set collapsedGroups = Set.of(); private List substitutionStrings = List.of(); + private List manualLinks = List.of(); private DiagramCommand() {} @@ -72,6 +74,14 @@ private void setCollapsedGroups(@NonNull final List substitutionStrings) this.substitutionStrings = substitutionStrings; } + @CommandLine.Option( + names = {"-l", "--link"}, + description = "Manually link two components in the diagram. " + + "Format: SOURCE_COMPONENT:EDGE_LABEL:TARGET_COMPONENT") + private void setManualLinks(@NonNull final List manualLinks) { + this.manualLinks = manualLinks; + } + /** * Entry point. */ @@ -83,8 +93,9 @@ public Integer call() throws IOException { final PlatformWiring platformWiring = new PlatformWiring(platformContext, Time.getCurrent()); - final String diagramString = - platformWiring.getModel().generateWiringDiagram(parseGroups(), parseSubstitutions()); + final String diagramString = platformWiring + .getModel() + .generateWiringDiagram(parseGroups(), parseSubstitutions(), parseManualLinks()); final String encodedDiagramString = Base64.getEncoder().encodeToString(diagramString.getBytes()); final String editorUrl = "https://mermaid.ink/svg/" + encodedDiagramString + "?bgColor=e8e8e8"; @@ -138,4 +149,26 @@ private List parseSubstitutions() { return substitutions; } + + /** + * Parse manual links from the command line arguments. + * + * @return a list of zero or more manual links + */ + @NonNull + private List parseManualLinks() { + final List links = new ArrayList<>(); + for (final String linkString : manualLinks) { + final String[] parts = linkString.split(":"); + if (parts.length != 3) { + throw new IllegalArgumentException("Invalid link string: " + linkString); + } + final String sourceComponent = parts[0]; + final String edgeLabel = parts[1]; + final String targetComponent = parts[2]; + links.add(new ModelManualLink(sourceComponent, edgeLabel, targetComponent)); + } + + return links; + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt index 612978ff7392..9474bdd2377e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/diagram-commands.txt @@ -4,6 +4,7 @@ Groups high level grouping of components and component groups. Substitutes known spammy wires. pcli diagram \ + -l 'applicationTransactionPrehandler:futures:linkedEventIntake' \ -s 'getMinimumGenerationNonAncient:minimum generation non ancient:*' \ -s 'heartbeat:heartbeat:♡' \ -s 'eventCreationManager:non-validated events:†' \ @@ -21,6 +22,7 @@ pcli diagram \ Same as 'Uncollapsed' but with low level things collapsed. Attempts to hide things like transformers and splitters. pcli diagram \ + -l 'applicationTransactionPrehandler:futures:linkedEventIntake' \ -s 'getMinimumGenerationNonAncient:minimum generation non ancient:*' \ -s 'heartbeat:heartbeat:♡' \ -s 'eventCreationManager:non-validated events:†' \ From 96d6f5ae3932dc4636f319ab8badaceb3a1ea009 Mon Sep 17 00:00:00 2001 From: Austin Littley <102969658+alittley@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:31:30 -0500 Subject: [PATCH 30/80] feat: html improvements (#10600) Signed-off-by: Austin Littley --- .../swirlds/cli/logging/HtmlGenerator.java | 371 +++++++++++++++--- .../swirlds/cli/logging/HtmlTagFactory.java | 2 +- 2 files changed, 322 insertions(+), 51 deletions(-) diff --git a/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlGenerator.java b/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlGenerator.java index 5e9d6c5553a7..e3925538ca87 100644 --- a/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlGenerator.java +++ b/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlGenerator.java @@ -25,6 +25,7 @@ import static com.swirlds.cli.logging.LogProcessingUtils.getLogLevelColor; import static com.swirlds.cli.logging.PlatformStatusLog.STATUS_HTML_CLASS; +import com.swirlds.common.formatting.TextEffect; import com.swirlds.common.platform.NodeId; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -55,6 +56,10 @@ public class HtmlGenerator { */ public static final String DEFAULT_TEXT_COLOR = "#bdbfc4"; + public static final String WHITELIST_RADIO_COLOR = "#6FD154"; + public static final String NEUTRALLIST_RADIO_COLOR = "#F3D412"; + public static final String BLACKLIST_RADIO_COLOR = "#DA4754"; + /** * Jetbrains font */ @@ -99,6 +104,11 @@ public class HtmlGenerator { public static final String REMAINDER_OF_LINE_COLUMN_LABEL = "remainder"; public static final String NON_STANDARD_LABEL = "non-standard"; + public static final String SELECT_MANY_BUTTON_LABEL = "select-many-button"; + public static final String DESELECT_MANY_BUTTON_LABEL = "deselect-many-button"; + public static final String SECLECT_COLUMN_BUTTON_LABEL = "select-column-button"; + public static final String SELECT_COMPACT_BUTTON_LABEL = "select-compact-button"; + /** * Signifies filter radio buttons */ @@ -170,7 +180,7 @@ public class HtmlGenerator { // each radio button has 3 classes, "filter-radio", the type of radio button this is, and the name of the class to be hidden for (const element of radioClasses) { - if (element === "filter-radio") { + if (element === "filter-radio" || element.endsWith("filter-section")) { continue; } @@ -248,7 +258,10 @@ public class HtmlGenerator { let toggleClass; for (const element of checkboxClasses) { - if (element === "filter-checkbox") { + if (element === "filter-checkbox" || + element === "compact-show" || + element === "compact-hide" || + element.endsWith("filter-section")) { continue; } @@ -287,7 +300,7 @@ public class HtmlGenerator { let toggleClass; for (const element of checkboxClasses) { - if (element === "no-show-checkbox") { + if (element === "no-show-checkbox" || element.endsWith("filter-section")) { continue; } @@ -329,6 +342,138 @@ public class HtmlGenerator { } }); } + + let selectManyButtons = document.getElementsByClassName("select-many-button"); + for (const selectManyButton of selectManyButtons) { + // get the other class name of the button + let selectManyButtonClasses = selectManyButton.classList; + + // the name of the section + let sectionClass; + + for (const buttonClass of selectManyButtonClasses) { + if (buttonClass === "select-many-button") { + continue; + } + + sectionClass = buttonClass; + } + + let sectionButtons = document.getElementsByClassName(sectionClass); + + selectManyButton.addEventListener("click", function() { + for (const button of sectionButtons) { + if ($(button).hasClass("select-many-button")) { + continue; + } + if (!$(button).is(":checked")) { + button.click() + } + } + }); + } + + let deselectManyButtons = document.getElementsByClassName("deselect-many-button"); + for (const deselectManyButton of deselectManyButtons) { + // get the other class name of the button + let deselectManyButtonClasses = deselectManyButton.classList; + + // the name of the section + let sectionClass; + + for (const buttonClass of deselectManyButtonClasses) { + if (buttonClass === "deselect-many-button") { + continue; + } + + sectionClass = buttonClass; + } + + let sectionButtons = document.getElementsByClassName(sectionClass); + + deselectManyButton.addEventListener("click", function() { + for (const button of sectionButtons) { + if ($(button).hasClass("deselect-many-button")) { + continue; + } + if ($(button).is(":checked")) { + button.click() + } + } + }); + } + + let selectCompactButtons = document.getElementsByClassName("select-compact-button"); + for (const selectCompactButton of selectCompactButtons) { + // get the other class name of the button + let selectCompactButtonClasses = selectCompactButton.classList; + + // the name of the section + let sectionClass; + + for (const buttonClass of selectCompactButtonClasses) { + if (buttonClass === "select-compact-button") { + continue; + } + + console.log(buttonClass); + sectionClass = buttonClass; + } + + let sectionButtons = document.getElementsByClassName(sectionClass); + + selectCompactButton.addEventListener("click", function() { + for (const button of sectionButtons) { + if ($(button).hasClass("select-compact-button")) { + continue; + } + if (!$(button).is(":checked") && $(button).hasClass("compact-show") || + $(button).is(":checked") && $(button).hasClass("compact-hide")) { + button.click() + } + } + }); + } + + let columnSelectButtons = document.getElementsByClassName("select-column-button"); + for (const columnSelectButton of columnSelectButtons) { + // get the other class name of the button + let columnSelectButtonClasses = columnSelectButton.classList; + + // the name of the section + let sectionClass; + let radioTypeClass; + + for (const buttonClass of columnSelectButtonClasses) { + if (buttonClass === "select-column-button") { + continue; + } + + if (buttonClass.endsWith("-radio")) { + radioTypeClass = buttonClass; + continue; + } + + sectionClass = buttonClass; + } + + let sectionButtons = document.getElementsByClassName(sectionClass + " " + radioTypeClass); + + columnSelectButton.addEventListener("click", function() { + for (const button of sectionButtons) { + if ($(button).hasClass("select-column-button")) { + continue; + } + button.click() + } + }); + } + + // set the compact view automatically + let compactHidden = document.getElementsByClassName("compact-hide"); + for (const element of compactHidden) { + element.click(); + } """; /** @@ -339,11 +484,12 @@ private HtmlGenerator() {} /** * Create show / hide checkboxes for node IDs * - * @param nodeId the node ID + * @param sectionName the name of the filter section this checkbox is in + * @param nodeId the node ID * @return the radio buttons */ @NonNull - private static String createNodeIdCheckbox(@NonNull final String nodeId) { + private static String createNodeIdCheckbox(@NonNull final String sectionName, @NonNull final String nodeId) { // label used for filtering by node ID final String nodeLogicLabel = "node" + nodeId; // label used for colorizing by node ID @@ -353,7 +499,7 @@ private static String createNodeIdCheckbox(@NonNull final String nodeId) { stringBuilder .append(new HtmlTagFactory("input") - .addClasses(List.of(NO_SHOW_CHECKBOX_LABEL, nodeLogicLabel)) + .addClasses(List.of(NO_SHOW_CHECKBOX_LABEL, nodeLogicLabel, sectionName)) .addAttribute("type", "checkbox") .addAttribute("checked", "checked") .generateTag()) @@ -373,18 +519,27 @@ private static String createNodeIdCheckbox(@NonNull final String nodeId) { * Create a single checkbox filter * * @param elementName the name of the element to hide / show + * @param sectionName the name of the filter section this checkbox is in + * @param compactView whether or not the checkbox should be checked in compact view * @return the checkbox */ @NonNull - private static String createCheckboxFilter(@NonNull final String elementName) { + private static String createCheckboxFilter( + @NonNull final String elementName, @NonNull final String sectionName, final boolean compactView) { + + final HtmlTagFactory tagFactory = new HtmlTagFactory("input") + .addClasses(List.of(FILTER_CHECKBOX_LABEL, elementName, sectionName)) + .addAttribute("type", "checkbox") + .addAttribute("checked", "checked"); + + if (compactView) { + tagFactory.addClass("compact-show"); + } else { + tagFactory.addClass("compact-hide"); + } + final StringBuilder stringBuilder = new StringBuilder(); - stringBuilder - .append(new HtmlTagFactory("input") - .addClasses(List.of(FILTER_CHECKBOX_LABEL, elementName)) - .addAttribute("type", "checkbox") - .addAttribute("checked", "checked") - .generateTag()) - .append("\n"); + stringBuilder.append(tagFactory.generateTag()).append("\n"); stringBuilder .append(new HtmlTagFactory("label", elementName).generateTag()) @@ -399,12 +554,14 @@ private static String createCheckboxFilter(@NonNull final String elementName) { *

* This method creates 3 radio buttons, whitelist, blacklist, and neutral * + * @param sectionName the name of the filter section this radio button is in * @param elementName the name of the element to hide * @return the radio filter group */ @NonNull - private static String createStandardRadioFilterWithoutLabelClass(@NonNull final String elementName) { - return createStandardRadioFilter(elementName, null); + private static String createStandardRadioFilterWithoutLabelClass( + @NonNull final String sectionName, @NonNull final String elementName) { + return createStandardRadioFilter(sectionName, elementName, null); } /** @@ -412,19 +569,22 @@ private static String createStandardRadioFilterWithoutLabelClass(@NonNull final *

* This method creates 3 radio buttons, whitelist, blacklist, and neutral * + * @param sectionName the name of the filter section this radio button is in * @param elementName the name of the element to hide * @param labelClass the class to apply to the label, for styling * @return the radio filter group */ @NonNull - private static String createStandardRadioFilter(@NonNull final String elementName, @Nullable String labelClass) { + private static String createStandardRadioFilter( + @NonNull final String sectionName, @NonNull final String elementName, @Nullable String labelClass) { final String commonRadioLabel = elementName + "-radio"; final StringBuilder stringBuilder = new StringBuilder(); stringBuilder .append(new HtmlTagFactory("input") - .addClasses(List.of(FILTER_RADIO_LABEL, WHITELIST_LABEL, WHITELIST_RADIO_LABEL, elementName)) + .addClasses(List.of( + FILTER_RADIO_LABEL, WHITELIST_LABEL, WHITELIST_RADIO_LABEL, elementName, sectionName)) .addAttribute("type", "radio") .addAttribute("name", commonRadioLabel) .addAttribute("value", "1") @@ -432,7 +592,7 @@ private static String createStandardRadioFilter(@NonNull final String elementNam .append("\n"); stringBuilder .append(new HtmlTagFactory("input") - .addClasses(List.of(FILTER_RADIO_LABEL, NEUTRALLIST_RADIO_LABEL, elementName)) + .addClasses(List.of(FILTER_RADIO_LABEL, NEUTRALLIST_RADIO_LABEL, elementName, sectionName)) .addAttribute("type", "radio") .addAttribute("name", commonRadioLabel) .addAttribute("checked", "checked") @@ -441,7 +601,8 @@ private static String createStandardRadioFilter(@NonNull final String elementNam .append("\n"); stringBuilder .append(new HtmlTagFactory("input") - .addClasses(List.of(FILTER_RADIO_LABEL, BLACKLIST_LABEL, BLACKLIST_RADIO_LABEL, elementName)) + .addClasses(List.of( + FILTER_RADIO_LABEL, BLACKLIST_LABEL, BLACKLIST_RADIO_LABEL, elementName, sectionName)) .addAttribute("type", "radio") .addAttribute("name", commonRadioLabel) .addAttribute("value", "3") @@ -489,22 +650,90 @@ private static String createInputDiv(@NonNull final String heading, @NonNull fin */ @NonNull private static String createNodeIdFilterDiv(@NonNull final List filterValues) { - final List filterRadios = - filterValues.stream().map(HtmlGenerator::createNodeIdCheckbox).toList(); - return createInputDiv("Node ID", filterRadios); + final List elements = new ArrayList<>(); + + final String sectionName = "node-filter-section"; + elements.add(new HtmlTagFactory("input") + .addClass(sectionName) + .addClass(SELECT_MANY_BUTTON_LABEL) + .addAttribute("type", "button") + .addAttribute("value", "All") + .generateTag()); + elements.add(new HtmlTagFactory("input") + .addClass(sectionName) + .addClass(DESELECT_MANY_BUTTON_LABEL) + .addAttribute("type", "button") + .addAttribute("value", "None") + .generateTag()); + elements.add(new HtmlTagFactory("br").generateTag()); + + filterValues.forEach(filterValue -> elements.add(createNodeIdCheckbox(sectionName, filterValue))); + + return createInputDiv("Node ID", elements); } /** - * Create the div for column filters + * Create the div for column filters. * * @param filterValues the different column names to make filters for + * @param compactView the compact states of the checkboxes. a value of true means shown * @return the column filter div */ @NonNull - private static String createColumnFilterDiv(@NonNull final List filterValues) { - final List filterCheckboxes = - filterValues.stream().map(HtmlGenerator::createCheckboxFilter).toList(); - return createInputDiv("Columns", filterCheckboxes); + private static String createColumnFilterDiv( + @NonNull final List filterValues, @NonNull final List compactView) { + + final String sectionName = "column-filter-section"; + + final List elements = new ArrayList<>(); + elements.add(new HtmlTagFactory("input") + .addClass(sectionName) + .addClass(SELECT_MANY_BUTTON_LABEL) + .addAttribute("type", "button") + .addAttribute("value", "All") + .generateTag()); + elements.add(new HtmlTagFactory("input") + .addClass(sectionName) + .addClass(SELECT_COMPACT_BUTTON_LABEL) + .addAttribute("type", "button") + .addAttribute("value", "Compact") + .generateTag()); + elements.add(new HtmlTagFactory("br").generateTag()); + + for (int i = 0; i < filterValues.size(); i++) { + elements.add(createCheckboxFilter(filterValues.get(i), sectionName, compactView.get(i))); + } + + return createInputDiv("Columns", elements); + } + + @NonNull + private static String createRadioColumnSelectorButtons(@NonNull final String sectionName) { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(new HtmlTagFactory("input") + .addClass(SECLECT_COLUMN_BUTTON_LABEL) + .addClass(WHITELIST_RADIO_LABEL) + .addClass(sectionName) + .addAttribute("type", "button") + .addAttribute("value", "↓") + .generateTag()); + stringBuilder.append(new HtmlTagFactory("input") + .addClass(SECLECT_COLUMN_BUTTON_LABEL) + .addClass(NEUTRALLIST_RADIO_LABEL) + .addClass(sectionName) + .addAttribute("type", "button") + .addAttribute("value", "↓") + .generateTag()); + stringBuilder.append(new HtmlTagFactory("input") + .addClass(SECLECT_COLUMN_BUTTON_LABEL) + .addClass(BLACKLIST_RADIO_LABEL) + .addClass(sectionName) + .addAttribute("type", "button") + .addAttribute("value", "↓") + .generateTag()); + stringBuilder.append(new HtmlTagFactory("br").generateTag()).append("\n"); + + return stringBuilder.toString(); } /** @@ -512,18 +741,24 @@ private static String createColumnFilterDiv(@NonNull final List filterVa *

* The filter div has a heading, and a series of radio buttons that can hide elements with the given names * + * @param sectionName the name of the filter section * @param filterName the filter name * @param filterValues the filter values * @return the filter div */ @NonNull private static String createStandardFilterDivWithoutLabelClasses( - @NonNull final String filterName, @NonNull final List filterValues) { + @NonNull final String sectionName, + @NonNull final String filterName, + @NonNull final List filterValues) { - final List filterRadios = filterValues.stream() - .map(HtmlGenerator::createStandardRadioFilterWithoutLabelClass) - .toList(); - return createInputDiv(filterName, filterRadios); + final List elements = new ArrayList<>(); + elements.add(createRadioColumnSelectorButtons(sectionName)); + + filterValues.forEach( + filterValue -> elements.add(createStandardRadioFilterWithoutLabelClass(sectionName, filterValue))); + ; + return createInputDiv(filterName, elements); } /** @@ -533,20 +768,24 @@ private static String createStandardFilterDivWithoutLabelClasses( *

* The radio labels will have the classes defined in labelClasses. * + * @param sectionName the name of the filter section * @param filterName the filter name * @param filterValues the filter values * @param labelClasses the classes to apply to the radio labels * @return the filter div */ private static String createStandardFilterDivWithLabelClasses( + @NonNull final String sectionName, @NonNull final String filterName, @NonNull final List filterValues, @NonNull final Map labelClasses) { - final List filterRadios = filterValues.stream() - .map(filterValue -> createStandardRadioFilter(filterValue, labelClasses.get(filterValue))) - .toList(); - return createInputDiv(filterName, filterRadios); + final List elements = new ArrayList<>(); + elements.add(createRadioColumnSelectorButtons(sectionName)); + + filterValues.forEach(filterValue -> + elements.add(createStandardRadioFilter(sectionName, filterValue, labelClasses.get(filterValue)))); + return createInputDiv(filterName, elements); } /** @@ -622,6 +861,27 @@ private static void createGeneralCssRules( // highlight log lines when you hover over them with your mouse cssFactory.addRule("." + LOG_LINE_LABEL + ":hover td", new CssDeclaration("background-color", HIGHLIGHT_COLOR)); + cssFactory.addRule( + "." + SELECT_MANY_BUTTON_LABEL + ", ." + DESELECT_MANY_BUTTON_LABEL + ", ." + + SELECT_COMPACT_BUTTON_LABEL, + new CssDeclaration("background-color", getHtmlColor(TextEffect.GRAY))); + + // make the select column buttons the same color as the accent of the radio buttons= + cssFactory.addRule( + "." + SECLECT_COLUMN_BUTTON_LABEL + "." + WHITELIST_RADIO_LABEL, + new CssDeclaration("border-color", WHITELIST_RADIO_COLOR)); + cssFactory.addRule( + "." + SECLECT_COLUMN_BUTTON_LABEL + "." + NEUTRALLIST_RADIO_LABEL, + new CssDeclaration("border-color", NEUTRALLIST_RADIO_COLOR)); + cssFactory.addRule( + "." + SECLECT_COLUMN_BUTTON_LABEL + "." + BLACKLIST_RADIO_LABEL, + new CssDeclaration("border-color", BLACKLIST_RADIO_COLOR)); + + cssFactory.addRule( + "." + SECLECT_COLUMN_BUTTON_LABEL, + new CssDeclaration("margin", "1px"), + new CssDeclaration("width", "2em")); + // create color rules for each log level logLines.stream() .map(LogLine::getLogLevel) @@ -694,21 +954,24 @@ private static String generateFiltersDiv( .map(NodeId::toString) .toList())); - filterDivBuilder.append(createColumnFilterDiv(List.of( - NODE_ID_COLUMN_LABEL, - ELAPSED_TIME_COLUMN_LABEL, - TIMESTAMP_COLUMN_LABEL, - LOG_NUMBER_COLUMN_LABEL, - LOG_LEVEL_COLUMN_LABEL, - MARKER_COLUMN_LABEL, - THREAD_NAME_COLUMN_LABEL, - CLASS_NAME_COLUMN_LABEL, - REMAINDER_OF_LINE_COLUMN_LABEL))); + filterDivBuilder.append(createColumnFilterDiv( + List.of( + NODE_ID_COLUMN_LABEL, + ELAPSED_TIME_COLUMN_LABEL, + TIMESTAMP_COLUMN_LABEL, + LOG_NUMBER_COLUMN_LABEL, + LOG_LEVEL_COLUMN_LABEL, + MARKER_COLUMN_LABEL, + THREAD_NAME_COLUMN_LABEL, + CLASS_NAME_COLUMN_LABEL, + REMAINDER_OF_LINE_COLUMN_LABEL), + List.of(true, true, false, false, true, false, false, true, true))); logLevelLabels.forEach((logLevel, labelClass) -> cssFactory.addRule( "." + labelClass, new CssDeclaration("color", getHtmlColor(getLogLevelColor(logLevel))))); filterDivBuilder .append(createStandardFilterDivWithLabelClasses( + "log-level-filter-section", "Log Level", logLines.stream() .map(LogLine::getLogLevel) @@ -720,10 +983,18 @@ private static String generateFiltersDiv( filterDivBuilder .append(createStandardFilterDivWithoutLabelClasses( + "log-marker-filter-section", "Log Marker", logLines.stream().map(LogLine::getMarker).distinct().toList())) .append("\n"); + filterDivBuilder + .append(createStandardFilterDivWithoutLabelClasses( + "class-filter-section", + "Class", + logLines.stream().map(LogLine::getClassName).distinct().toList())) + .append("\n"); + final StringBuilder containingDivBuilder = new StringBuilder(); containingDivBuilder .append(new HtmlTagFactory("h2", "Filters").generateTag()) @@ -735,9 +1006,9 @@ private static String generateFiltersDiv( .generateTag()) .append("\n"); - cssFactory.addRule("." + WHITELIST_RADIO_LABEL, new CssDeclaration("accent-color", "#6FD154")); - cssFactory.addRule("." + NEUTRALLIST_RADIO_LABEL, new CssDeclaration("accent-color", "#F3D412")); - cssFactory.addRule("." + BLACKLIST_RADIO_LABEL, new CssDeclaration("accent-color", "#DA4754")); + cssFactory.addRule("." + WHITELIST_RADIO_LABEL, new CssDeclaration("accent-color", WHITELIST_RADIO_COLOR)); + cssFactory.addRule("." + NEUTRALLIST_RADIO_LABEL, new CssDeclaration("accent-color", NEUTRALLIST_RADIO_COLOR)); + cssFactory.addRule("." + BLACKLIST_RADIO_LABEL, new CssDeclaration("accent-color", BLACKLIST_RADIO_COLOR)); // make the filter columns and the log table scroll independently cssFactory.addRule("." + INDEPENDENT_SCROLL_LABEL, new CssDeclaration("overflow", "auto")); diff --git a/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlTagFactory.java b/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlTagFactory.java index 8d7a7fc63aa5..29ab6e9c1556 100644 --- a/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlTagFactory.java +++ b/platform-sdk/swirlds-cli/src/main/java/com/swirlds/cli/logging/HtmlTagFactory.java @@ -79,7 +79,7 @@ public HtmlTagFactory addAttribute(@NonNull final String attributeName, @NonNull if (attributeMap.containsKey(attributeName)) { attributeMap.get(attributeName).addAll(values); } else { - attributeMap.put(attributeName, values); + attributeMap.put(attributeName, new ArrayList<>(values)); } return this; From f2754b97b237ad9c15eff93a781c2cfa910b36d9 Mon Sep 17 00:00:00 2001 From: Maxi Tartaglia <152629744+mxtartaglia-sl@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:58:23 -0300 Subject: [PATCH 31/80] fix: adding enum mention to supported datatypes (#10596) Signed-off-by: Maxi Tartaglia --- platform-sdk/docs/base/configuration/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/platform-sdk/docs/base/configuration/configuration.md b/platform-sdk/docs/base/configuration/configuration.md index d7b8b1f864f1..d3d9afd31b8d 100644 --- a/platform-sdk/docs/base/configuration/configuration.md +++ b/platform-sdk/docs/base/configuration/configuration.md @@ -171,6 +171,7 @@ The config API supports several datatypes for reading config properties. The fol 1994-11-05T08:15:30-05:00" - `Duration`: the custom format from the old settings is supported; examples are "2ms" or "10s" - `ChronoUnit`: examples are "millis" or "seconds" +- `Enum` The support for all data types is done by using the `com.swirlds.config.api.converter.ConfigConverter` API. The `com.swirlds.config.api.ConfigurationBuilder` must be used to add support for custom datatypes. The following code From 93cd4e5ee376d84b91c1ac1718ad5114a237503f Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:11:39 -0600 Subject: [PATCH 32/80] fix: sync ppm optimization (#10569) Signed-off-by: Cody Littley --- .../com/swirlds/platform/SwirldsPlatform.java | 33 ++++- .../platform/components/EventIntake.java | 41 ++++-- .../components/LinkedEventIntake.java | 32 +++-- .../platform/gossip/GossipFactory.java | 4 + .../gossip/chatter/ChatterGossip.java | 1 + .../shadowgraph/LatestEventTipsetTracker.java | 95 +++++++++++++ .../gossip/shadowgraph/ShadowGraph.java | 127 ++++++++---------- .../shadowgraph/ShadowGraphEventObserver.java | 26 +++- .../shadowgraph/ShadowGraphSynchronizer.java | 21 ++- .../gossip/shadowgraph/SyncUtils.java | 59 ++++---- .../platform/gossip/sync/SyncGossip.java | 4 + .../gossip/sync/config/SyncConfig.java | 4 +- .../swirlds/platform/metrics/SyncMetrics.java | 63 ++++----- .../platform/test/consensus/TestIntake.java | 6 +- .../test/components/EventIntakeTest.java | 1 + .../event/intake/OrphanEventsIntakeTest.java | 1 + .../platform/test/sync/ShadowGraphTest.java | 77 +++++------ .../platform/test/sync/SyncFilteringTest.java | 2 +- .../swirlds/platform/test/sync/SyncNode.java | 5 +- 19 files changed, 392 insertions(+), 210 deletions(-) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index c939791dd16e..39a92b85c5b6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -121,6 +121,7 @@ import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.NoOpIntakeEventCounter; import com.swirlds.platform.gossip.chatter.config.ChatterConfig; +import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.gossip.shadowgraph.ShadowGraphEventObserver; import com.swirlds.platform.gossip.sync.config.SyncConfig; @@ -341,8 +342,8 @@ public class SwirldsPlatform implements Platform { private final AsyncEventCreationManager eventCreator; /** - * The round of the most recent reconnect state received, or {@link UptimeData#NO_ROUND} - * if no reconnect state has been received since startup. + * The round of the most recent reconnect state received, or {@link UptimeData#NO_ROUND} if no reconnect state has + * been received since startup. */ private final AtomicLong latestReconnectRound = new AtomicLong(NO_ROUND); @@ -440,7 +441,18 @@ public class SwirldsPlatform implements Platform { final SyncMetrics syncMetrics = new SyncMetrics(metrics); RuntimeMetrics.setup(metrics); - this.shadowGraph = new ShadowGraph(syncMetrics, currentAddressBook.getSize()); + this.shadowGraph = new ShadowGraph(time, syncMetrics, currentAddressBook, selfId); + + final LatestEventTipsetTracker latestEventTipsetTracker; + final boolean enableEventFiltering = platformContext + .getConfiguration() + .getConfigData(SyncConfig.class) + .filterLikelyDuplicates(); + if (enableEventFiltering) { + latestEventTipsetTracker = new LatestEventTipsetTracker(time, currentAddressBook, selfId); + } else { + latestEventTipsetTracker = null; + } this.keysAndCerts = keysAndCerts; @@ -682,7 +694,7 @@ public class SwirldsPlatform implements Platform { final PreconsensusEventStreamSequencer sequencer = new PreconsensusEventStreamSequencer(); final EventObserverDispatcher eventObserverDispatcher = new EventObserverDispatcher( - new ShadowGraphEventObserver(shadowGraph), + new ShadowGraphEventObserver(shadowGraph, latestEventTipsetTracker), consensusRoundHandler, addedEventMetrics, eventIntakeMetrics, @@ -734,6 +746,7 @@ public class SwirldsPlatform implements Platform { eventObserverDispatcher, eventIntakePhaseTimer, shadowGraph, + latestEventTipsetTracker, preconsensusEventHandlerConsumer, intakeEventCounter); @@ -776,7 +789,13 @@ public class SwirldsPlatform implements Platform { final OrphanBuffer orphanBuffer = new OrphanBuffer(platformContext, intakeEventCounter); final InOrderLinker inOrderLinker = new InOrderLinker(platformContext, time, intakeEventCounter); final LinkedEventIntake linkedEventIntake = new LinkedEventIntake( - platformContext, time, consensusRef::get, eventObserverDispatcher, shadowGraph, intakeEventCounter); + platformContext, + time, + consensusRef::get, + eventObserverDispatcher, + shadowGraph, + latestEventTipsetTracker, + intakeEventCounter); final EventCreationManager eventCreationManager = buildEventCreationManager( platformContext, @@ -850,6 +869,7 @@ public class SwirldsPlatform implements Platform { appVersion, epochHash, shadowGraph, + latestEventTipsetTracker, emergencyRecoveryManager, consensusRef, intakeQueue, @@ -1076,6 +1096,9 @@ private void loadStateIntoConsensus(@NonNull final SignedState signedState) { signedState.getEvents().clone()), // we need to provide the minGen from consensus so that expiry matches after a restart/reconnect consensusRef.get().getMinRoundGeneration()); + + // Intentionally don't bother initiating the latestEventTipsetTracker here. We don't support this + // code path way any more. } else { shadowGraph.startFromGeneration(consensusRef.get().getMinGenerationNonAncient()); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java index c6b58effacc5..69e9b633362d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java @@ -31,6 +31,7 @@ import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.gossip.IntakeEventCounter; +import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.intake.EventIntakePhase; import com.swirlds.platform.internal.ConsensusRound; @@ -38,6 +39,7 @@ import com.swirlds.platform.observers.EventObserverDispatcher; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -70,6 +72,8 @@ public class EventIntake { /** Stores events, expires them, provides event lookup methods */ private final ShadowGraph shadowGraph; + private final LatestEventTipsetTracker latestEventTipsetTracker; + private final ExecutorService prehandlePool; private final Consumer prehandleEvent; @@ -89,19 +93,21 @@ public class EventIntake { /** * Constructor * - * @param platformContext the platform context - * @param threadManager creates new threading resources - * @param time provides the wall clock time - * @param selfId the ID of this node - * @param eventLinker links events together, holding orphaned events until their parents are found (if - * operating with the orphan buffer enabled) - * @param consensusSupplier provides the current consensus instance - * @param addressBook the current address book - * @param dispatcher invokes event related callbacks - * @param phaseTimer measures the time spent in each phase of intake - * @param shadowGraph tracks events in the hashgraph - * @param prehandleEvent prehandles transactions in an event - * @param intakeEventCounter tracks the number of events from each peer that are currently in the intake pipeline + * @param platformContext the platform context + * @param threadManager creates new threading resources + * @param time provides the wall clock time + * @param selfId the ID of this node + * @param eventLinker links events together, holding orphaned events until their parents are found (if + * operating with the orphan buffer enabled) + * @param consensusSupplier provides the current consensus instance + * @param addressBook the current address book + * @param dispatcher invokes event related callbacks + * @param phaseTimer measures the time spent in each phase of intake + * @param shadowGraph tracks events in the hashgraph + * @param latestEventTipsetTracker tracks the tipset of the latest self event, null if feature is not enabled + * @param prehandleEvent prehandles transactions in an event + * @param intakeEventCounter tracks the number of events from each peer that are currently in the intake + * pipeline */ public EventIntake( @NonNull final PlatformContext platformContext, @@ -114,6 +120,7 @@ public EventIntake( @NonNull final EventObserverDispatcher dispatcher, @NonNull final PhaseTimer phaseTimer, @NonNull final ShadowGraph shadowGraph, + @Nullable final LatestEventTipsetTracker latestEventTipsetTracker, @NonNull final Consumer prehandleEvent, @NonNull final IntakeEventCounter intakeEventCounter) { @@ -125,6 +132,7 @@ public EventIntake( this.dispatcher = Objects.requireNonNull(dispatcher); this.phaseTimer = Objects.requireNonNull(phaseTimer); this.shadowGraph = Objects.requireNonNull(shadowGraph); + this.latestEventTipsetTracker = latestEventTipsetTracker; this.prehandleEvent = Objects.requireNonNull(prehandleEvent); this.intakeEventCounter = Objects.requireNonNull(intakeEventCounter); @@ -198,11 +206,16 @@ public void addEvent(final EventImpl event) { consRounds.forEach(this::handleConsensus); } - if (consensus().getMinGenerationNonAncient() > minGenNonAncientBeforeAdding) { + final long minimumGenerationNonAncient = consensus().getMinGenerationNonAncient(); + + if (minimumGenerationNonAncient > minGenNonAncientBeforeAdding) { // consensus rounds can be null and the minNonAncient might change, this is probably because of a round // with no consensus events, so we check the diff in generations to look for stale events phaseTimer.activatePhase(EventIntakePhase.HANDLING_STALE_EVENTS); handleStale(minGenNonAncientBeforeAdding); + if (latestEventTipsetTracker != null) { + latestEventTipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + } } } finally { phaseTimer.activatePhase(EventIntakePhase.IDLE); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java index a1b2a98b0511..8a5e75832c95 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java @@ -21,11 +21,13 @@ import com.swirlds.platform.Consensus; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.gossip.IntakeEventCounter; +import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.observers.EventObserverDispatcher; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -55,6 +57,8 @@ public class LinkedEventIntake { */ private final ShadowGraph shadowGraph; + private final LatestEventTipsetTracker latestEventTipsetTracker; + private final EventIntakeMetrics metrics; private final Time time; @@ -73,12 +77,14 @@ public class LinkedEventIntake { /** * Constructor * - * @param platformContext the platform context - * @param time provides the wall clock time - * @param consensusSupplier provides the current consensus instance - * @param dispatcher invokes event related callbacks - * @param shadowGraph tracks events in the hashgraph - * @param intakeEventCounter tracks the number of events from each peer that are currently in the intake pipeline + * @param platformContext the platform context + * @param time provides the wall clock time + * @param consensusSupplier provides the current consensus instance + * @param dispatcher invokes event related callbacks + * @param shadowGraph tracks events in the hashgraph + * @param latestEventTipsetTracker tracks the tipset of the latest self event, null if feature is not enabled + * @param intakeEventCounter tracks the number of events from each peer that are currently in the intake + * pipeline */ public LinkedEventIntake( @NonNull final PlatformContext platformContext, @@ -86,6 +92,7 @@ public LinkedEventIntake( @NonNull final Supplier consensusSupplier, @NonNull final EventObserverDispatcher dispatcher, @NonNull final ShadowGraph shadowGraph, + @Nullable final LatestEventTipsetTracker latestEventTipsetTracker, @NonNull final IntakeEventCounter intakeEventCounter) { this.time = Objects.requireNonNull(time); @@ -93,6 +100,7 @@ public LinkedEventIntake( this.dispatcher = Objects.requireNonNull(dispatcher); this.shadowGraph = Objects.requireNonNull(shadowGraph); this.intakeEventCounter = Objects.requireNonNull(intakeEventCounter); + this.latestEventTipsetTracker = latestEventTipsetTracker; this.paused = false; metrics = new EventIntakeMetrics(platformContext, () -> -1); @@ -123,7 +131,8 @@ public List addEvent(@NonNull final EventImpl event) { dispatcher.preConsensusEvent(event); - final long minGenNonAncientBeforeAdding = consensusSupplier.get().getMinGenerationNonAncient(); + final long minimumGenerationNonAncientBeforeAdding = + consensusSupplier.get().getMinGenerationNonAncient(); // record the event in the hashgraph, which results in the events in consEvent reaching consensus final List consensusRounds = consensusSupplier.get().addEvent(event); @@ -134,10 +143,15 @@ public List addEvent(@NonNull final EventImpl event) { consensusRounds.forEach(this::handleConsensus); } - if (consensusSupplier.get().getMinGenerationNonAncient() > minGenNonAncientBeforeAdding) { + final long minimumGenerationNonAncient = consensusSupplier.get().getMinGenerationNonAncient(); + + if (minimumGenerationNonAncient > minimumGenerationNonAncientBeforeAdding) { // consensus rounds can be null and the minNonAncient might change, this is probably because of a round // with no consensus events, so we check the diff in generations to look for stale events - handleStale(minGenNonAncientBeforeAdding); + handleStale(minimumGenerationNonAncientBeforeAdding); + if (latestEventTipsetTracker != null) { + latestEventTipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + } } return Objects.requireNonNullElseGet(consensusRounds, List::of); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java index 4328da0c3feb..e755a7b9bd2a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java @@ -32,6 +32,7 @@ import com.swirlds.platform.event.validation.EventValidator; import com.swirlds.platform.gossip.chatter.ChatterGossip; import com.swirlds.platform.gossip.chatter.config.ChatterConfig; +import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.gossip.sync.SingleNodeSyncGossip; import com.swirlds.platform.gossip.sync.SyncGossip; @@ -76,6 +77,7 @@ private GossipFactory() {} * @param appVersion the version of the app * @param epochHash the epoch hash of the initial state * @param shadowGraph contains non-ancient events + * @param latestEventTipsetTracker tracks the tipset of the latest self event * @param emergencyRecoveryManager handles emergency recovery * @param consensusRef a pointer to consensus * @param intakeQueue the event intake queue @@ -103,6 +105,7 @@ public static Gossip buildGossip( @NonNull final SoftwareVersion appVersion, @Nullable final Hash epochHash, @NonNull final ShadowGraph shadowGraph, + @Nullable final LatestEventTipsetTracker latestEventTipsetTracker, @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, @NonNull final AtomicReference consensusRef, @NonNull final QueueThread intakeQueue, @@ -201,6 +204,7 @@ public static Gossip buildGossip( appVersion, epochHash, shadowGraph, + latestEventTipsetTracker, emergencyRecoveryManager, consensusRef, intakeQueue, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java index 9f659c9795a3..d3545968a9ba 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java @@ -203,6 +203,7 @@ public ChatterGossip( platformContext, time, shadowGraph, + null, addressBook.getSize(), syncMetrics, consensusRef::get, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java new file mode 100644 index 000000000000..d2a996a94fe7 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.gossip.shadowgraph; + +import com.swirlds.base.time.Time; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.event.creation.tipset.Tipset; +import com.swirlds.platform.event.creation.tipset.TipsetTracker; +import com.swirlds.platform.internal.EventImpl; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.events.EventDescriptor; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Tracks the tipset of the latest self event. Must be thread safe, gossip will access it from multiple threads. + */ +public class LatestEventTipsetTracker { + + private final TipsetTracker tipsetTracker; + private final NodeId selfId; + private Tipset latestSelfEventTipset; + + /** + * Constructor. + * + * @param time provides wall clock time + * @param addressBook the current address book + * @param selfId the ID of this node + */ + public LatestEventTipsetTracker( + @NonNull final Time time, @NonNull final AddressBook addressBook, @NonNull final NodeId selfId) { + + Objects.requireNonNull(time); + Objects.requireNonNull(addressBook); + + this.tipsetTracker = new TipsetTracker(time, addressBook); + this.selfId = Objects.requireNonNull(selfId); + } + + /** + * Update the minimum generation non-ancient. + * + * @param minimumGenerationNonAncient the new minimum generation non-ancient + */ + public synchronized void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { + tipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + } + + /** + * Get the tipset of the latest self event, or null if there have been no self events. + * + * @return the tipset of the latest self event, or null if there have been no self events + */ + @Nullable + public synchronized Tipset getLatestSelfEventTipset() { + return latestSelfEventTipset; + } + + /** + * The event to add. Used to update tipsets. + * + * @param event The event to insert. + */ + public synchronized void addEvent(final EventImpl event) { + final List parentDescriptors = new ArrayList<>(2); + if (event.getSelfParent() != null) { + parentDescriptors.add(event.getSelfParent().getBaseEvent().getDescriptor()); + } + if (event.getOtherParent() != null) { + parentDescriptors.add(event.getOtherParent().getBaseEvent().getDescriptor()); + } + final Tipset tipset = tipsetTracker.addEvent(event.getBaseEvent().getDescriptor(), parentDescriptors); + if (event.getCreatorId().equals(selfId)) { + latestSelfEventTipset = tipset; + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraph.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraph.java index 5e68148d3450..7daf94f8e0ef 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraph.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraph.java @@ -20,12 +20,16 @@ import static com.swirlds.logging.legacy.LogMarker.STARTUP; import static com.swirlds.logging.legacy.LogMarker.SYNC_INFO; +import com.swirlds.base.time.Time; import com.swirlds.common.crypto.Hash; +import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.Clearable; import com.swirlds.platform.EventStrings; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.SyncMetrics; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.PlatformEvent; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -108,16 +112,27 @@ public class ShadowGraph implements Clearable { /** the number of nodes in the network, used for debugging */ private final int numberOfNodes; + private final NodeId selfId; + /** - * Constructs a new instance. + * Constructor. + * + * @param time provides wall clock time + * @param syncMetrics metrics for sync gossip + * @param addressBook the address book + * @param selfId the id of this node */ - public ShadowGraph(final SyncMetrics syncMetrics) { - this(syncMetrics, -1); - } + public ShadowGraph( + @NonNull final Time time, + @NonNull final SyncMetrics syncMetrics, + @NonNull final AddressBook addressBook, + @NonNull final NodeId selfId) { + + Objects.requireNonNull(time); - public ShadowGraph(final SyncMetrics syncMetrics, final int numberOfNodes) { this.syncMetrics = syncMetrics; - this.numberOfNodes = numberOfNodes; + this.numberOfNodes = addressBook.getSize(); + this.selfId = Objects.requireNonNull(selfId); expireBelow = FIRST_GENERATION; oldestGeneration = FIRST_GENERATION; tips = new HashSet<>(); @@ -130,17 +145,14 @@ public ShadowGraph(final SyncMetrics syncMetrics, final int numberOfNodes) { *

Initializes the {@link ShadowGraph} with the given {@code events}. This method should be used after * reconnect or restart. {@code events} must be ordered by generation, smallest to largest.

* - *

A minimum generation is necessary because events loaded from signed state a could have generation gaps and are - * used in {@link com.swirlds.platform.Consensus}. {@link com.swirlds.platform.Consensus} will eventually expire its - * smallest generation and that generation must be present in the {@link ShadowGraph} or an exception is thrown, so - * we create empty generations to match {@link com.swirlds.platform.Consensus}.

+ *

A minimum generation is necessary because events loaded from signed state a could have generation gaps and + * are used in {@link com.swirlds.platform.Consensus}. {@link com.swirlds.platform.Consensus} will eventually expire + * its smallest generation and that generation must be present in the {@link ShadowGraph} or an exception is thrown, + * so we create empty generations to match {@link com.swirlds.platform.Consensus}.

* - * @param events - * the events to add to the shadow graph - * @param minGeneration - * the generation to use as a minimum generation - * @throws IllegalArgumentException - * if argument is null or empty + * @param events the events to add to the shadow graph + * @param minGeneration the generation to use as a minimum generation + * @throws IllegalArgumentException if argument is null or empty */ public synchronized void initFromEvents(final List events, final long minGeneration) { if (events == null || events.isEmpty()) { @@ -181,6 +193,7 @@ public synchronized void initFromEvents(final List events, final long /** * Define the starting generation for the shadowgraph, it will not keep any events older than this + * * @param generation the starting generation */ public synchronized void startFromGeneration(final long generation) { @@ -234,8 +247,7 @@ public synchronized GenerationReservation reserve() { /** * Determines if the provided {@code hash} is in the shadow graph. * - * @param hash - * the hash to look for + * @param hash the hash to look for * @return true if the hash matches the hash of a shadow event in the shadow graph, false otherwise */ public synchronized boolean isHashInGraph(final Hash hash) { @@ -244,8 +256,8 @@ public synchronized boolean isHashInGraph(final Hash hash) { /** *

Returns the ancestors of the provided {@code events} that pass the provided {@code predicate} using a - * depth-first search. The provided {@code events} are not included in the return set. Searching stops at nodes - * that have no parents, or nodes that do not pass the {@code predicate}.

+ * depth-first search. The provided {@code events} are not included in the return set. Searching stops at nodes that + * have no parents, or nodes that do not pass the {@code predicate}.

* *

It is safe for this method not to be synchronized because:

*
    @@ -257,10 +269,8 @@ public synchronized boolean isHashInGraph(final Hash hash) { * {@link #getTips()}, which acts as a memory gate and causes the calling thread to read the latest values for all * variables from memory, including {@link ShadowEvent} links.

    * - * @param events - * the event to find ancestors of - * @param predicate - * determines whether or not to add the ancestor to the return list + * @param events the event to find ancestors of + * @param predicate determines whether or not to add the ancestor to the return list * @return the set of matching ancestors */ public Set findAncestors(final Iterable events, final Predicate predicate) { @@ -276,12 +286,9 @@ public Set findAncestors(final Iterable events, final * Private method that searches for ancestors and takes a HashSet as input. This method exists for efficiency, when * looking for ancestors of multiple events, we want to append to the same HashSet. * - * @param ancestors - * the HashSet to add ancestors to - * @param event - * the event to find ancestors of - * @param predicate - * determines whether or not to add the ancestor to the return list + * @param ancestors the HashSet to add ancestors to + * @param event the event to find ancestors of + * @param predicate determines whether or not to add the ancestor to the return list */ private void findAncestors( final HashSet ancestors, final ShadowEvent event, final Predicate predicate) { @@ -329,12 +336,9 @@ private void findAncestors( /** * Looks for events in a generation range that pass the provided predicate. * - * @param startGen - * the start of the generation range (inclusive) - * @param endGen - * the end of the generation range (exclusive) - * @param predicate - * the predicate to filter out events + * @param startGen the start of the generation range (inclusive) + * @param endGen the end of the generation range (exclusive) + * @param predicate the predicate to filter out events * @return a collection of events found */ public synchronized Collection findByGeneration( @@ -363,9 +367,8 @@ public synchronized Collection findByGeneration( *
  1. whose generation is less than the smallest generation with a non-zero number of reservations
  2. *
* - * @param generation - * The generation below which all generations should be expired. For example, if {@code generation} - * is 100, events in generation 99 and below should be expired. + * @param generation The generation below which all generations should be expired. For example, if + * {@code generation} is 100, events in generation 99 and below should be expired. */ public synchronized void expireBelow(final long generation) { if (generation < expireBelow) { @@ -417,7 +420,7 @@ public synchronized void expireBelow(final long generation) { * Removes reservations that can and should be expired, starting with the oldest generation reservation. * * @return the oldest generation with at least one reservation, or {@code -1} if there are no generations with at - * least one reservation. + * least one reservation. * @see ShadowGraph#expireBelow */ private long pruneReservationList() { @@ -451,8 +454,7 @@ private long pruneReservationList() { /** * Expires a single {@link ShadowEvent} from the shadow graph. * - * @param shadow - * the shadow event to expire + * @param shadow the shadow event to expire */ private void expire(final ShadowEvent shadow) { // Remove the shadow from the shadow graph @@ -466,8 +468,7 @@ private void expire(final ShadowEvent shadow) { /** * Get the shadow event that references a hashgraph event instance. * - * @param e - * The event. + * @param e The event. * @return the shadow event that references an event, or null is {@code e} is null */ public synchronized ShadowEvent shadow(final PlatformEvent e) { @@ -479,11 +480,9 @@ public synchronized ShadowEvent shadow(final PlatformEvent e) { } /** - * Get the shadow events that reference the hashgraph event instances - * with the given hashes. + * Get the shadow events that reference the hashgraph event instances with the given hashes. * - * @param hashes - * The event hashes to get shadow events for + * @param hashes The event hashes to get shadow events for * @return the shadow events that reference the events with the given hashes */ public synchronized List shadows(final List hashes) { @@ -498,8 +497,7 @@ public synchronized List shadows(final List hashes) { /** * Get a hashgraph event from a hash * - * @param h - * the hash + * @param h the hash * @return the hashgraph event, if there is one in {@code this} shadow graph, else `null` */ public synchronized EventImpl hashgraphEvent(final Hash h) { @@ -512,23 +510,20 @@ public synchronized EventImpl hashgraphEvent(final Hash h) { } /** - * Returns a copy of the tips at the time of invocation. The returned list is not affected by changes - * made to the tip set. + * Returns a copy of the tips at the time of invocation. The returned list is not affected by changes made to the + * tip set. * * @return an unmodifiable copy of the tips */ public synchronized List getTips() { return new ArrayList<>(tips); } - /** * If Event `e` is insertable, then insert it and update the tip set, else do nothing. * - * @param e - * The event reference to insert. + * @param e The event reference to insert. * @return true iff e was inserted - * @throws ShadowGraphInsertionException - * if the event was unable to be added to the shadow graph + * @throws ShadowGraphInsertionException if the event was unable to be added to the shadow graph */ public synchronized boolean addEvent(final EventImpl e) throws ShadowGraphInsertionException { final InsertableStatus status = insertable(e); @@ -591,8 +586,7 @@ private ShadowEvent shadow(final Hash h) { } /** - * @param h - * the hash of the event + * @param h the hash of the event * @return the event that has the hash provided, or null if none exists */ public synchronized EventImpl getEvent(final Hash h) { @@ -601,11 +595,10 @@ public synchronized EventImpl getEvent(final Hash h) { } /** - * Attach a shadow of a Hashgraph event to this graph. Only a shadow for which a parent - * hash matches a hash in this@entry is inserted. + * Attach a shadow of a Hashgraph event to this graph. Only a shadow for which a parent hash matches a hash in + * this@entry is inserted. * - * @param e - * The Hashgraph event shadow to be inserted + * @param e The Hashgraph event shadow to be inserted * @return the inserted shadow event */ private ShadowEvent insert(final EventImpl e) { @@ -627,8 +620,7 @@ private ShadowEvent insert(final EventImpl e) { /** * Predicate to determine if an event has expired. * - * @param event - * The event. + * @param event The event. * @return true iff the given event is expired */ private boolean expired(final PlatformEvent event) { @@ -673,10 +665,9 @@ private boolean expired(final PlatformEvent event) { /** * Determine whether an event is insertable at time of call. * - * @param e - * The event to evaluate + * @param e The event to evaluate * @return An insertable status, indicating whether the event can be inserted, and if not, the reason it can not be - * inserted. + * inserted. */ private InsertableStatus insertable(final EventImpl e) { if (e == null) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphEventObserver.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphEventObserver.java index 1bcfa2219d33..762bb359cfc7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphEventObserver.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphEventObserver.java @@ -23,6 +23,9 @@ import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.observers.ConsensusRoundObserver; import com.swirlds.platform.observers.EventAddedObserver; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,16 +35,25 @@ public class ShadowGraphEventObserver implements EventAddedObserver, ConsensusRoundObserver { private static final Logger logger = LogManager.getLogger(ShadowGraphEventObserver.class); private final ShadowGraph shadowGraph; + private final LatestEventTipsetTracker latestEventTipsetTracker; - public ShadowGraphEventObserver(final ShadowGraph shadowGraph) { - this.shadowGraph = shadowGraph; + /** + * Constructor. + * + * @param shadowGraph the {@link ShadowGraph} to update + * @param latestEventTipsetTracker the {@link LatestEventTipsetTracker} to update, or null if this feature is not + * enabled + */ + public ShadowGraphEventObserver( + @NonNull final ShadowGraph shadowGraph, @Nullable final LatestEventTipsetTracker latestEventTipsetTracker) { + this.shadowGraph = Objects.requireNonNull(shadowGraph); + this.latestEventTipsetTracker = latestEventTipsetTracker; } /** * Expire events in {@link ShadowGraph} based on the new minimum round generation * - * @param consensusRound - * a new consensus round + * @param consensusRound a new consensus round */ @Override public void consensusRound(final ConsensusRound consensusRound) { @@ -51,8 +63,7 @@ public void consensusRound(final ConsensusRound consensusRound) { /** * Add an event to the {@link ShadowGraph} * - * @param event - * the event to add + * @param event the event to add */ @Override public void eventAdded(final EventImpl event) { @@ -65,5 +76,8 @@ public void eventAdded(final EventImpl event) { EventStrings.toMediumString(event), e); } + if (latestEventTipsetTracker != null) { + latestEventTipsetTracker.addEvent(event); + } } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphSynchronizer.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphSynchronizer.java index af3b9818889f..bc91e4e4f8d7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphSynchronizer.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/ShadowGraphSynchronizer.java @@ -80,6 +80,10 @@ public class ShadowGraphSynchronizer { * The shadow graph manager to use for this sync */ private final ShadowGraph shadowGraph; + /** + * Tracks the tipset of the latest self event. Null if feature is not enabled. + */ + private final LatestEventTipsetTracker latestEventTipsetTracker; /** * Number of member nodes in the network for this sync */ @@ -140,6 +144,7 @@ public ShadowGraphSynchronizer( @NonNull final PlatformContext platformContext, @NonNull final Time time, @NonNull final ShadowGraph shadowGraph, + @Nullable final LatestEventTipsetTracker latestEventTipsetTracker, final int numberOfNodes, @NonNull final SyncMetrics syncMetrics, @NonNull final Supplier generationsSupplier, @@ -166,8 +171,13 @@ public ShadowGraphSynchronizer( this.eventHandler = buildEventHandler(platformContext, intakeQueue); final SyncConfig syncConfig = platformContext.getConfiguration().getConfigData(SyncConfig.class); - this.filterLikelyDuplicates = syncConfig.filterLikelyDuplicates(); this.nonAncestorFilterThreshold = syncConfig.nonAncestorFilterThreshold(); + + this.filterLikelyDuplicates = syncConfig.filterLikelyDuplicates(); + this.latestEventTipsetTracker = latestEventTipsetTracker; + if (filterLikelyDuplicates) { + Objects.requireNonNull(latestEventTipsetTracker); + } } /** @@ -408,8 +418,15 @@ private List createSendList( final List sendList; if (filterLikelyDuplicates) { + final long startFilterTime = time.nanoTime(); sendList = filterLikelyDuplicates( - shadowGraph, selfId, nonAncestorFilterThreshold, time.now(), eventsTheyMayNeed); + selfId, + nonAncestorFilterThreshold, + time.now(), + eventsTheyMayNeed, + latestEventTipsetTracker.getLatestSelfEventTipset()); + final long endFilterTime = time.nanoTime(); + syncMetrics.recordSyncFilterTime(endFilterTime - startFilterTime); } else { sendList = eventsTheyMayNeed; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java index 0007df0816d7..e70ff38a75e9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java @@ -23,6 +23,7 @@ import com.swirlds.common.utility.CompareTo; import com.swirlds.platform.consensus.GraphGenerations; import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.event.creation.tipset.Tipset; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.SyncException; import com.swirlds.platform.internal.EventImpl; @@ -41,7 +42,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -407,35 +407,21 @@ private static ShadowEvent getLatestSelfEventInShadowgraph( *
  • Don't send non-ancestors of self events unless we've known about that event for a long time.
  • * * - * @param shadowGraph the shadow graph - * @param selfId the id of this node - * @param nonAncestorThreshold for each event that is not a self event and is not an ancestor of a self event, the - * amount of time the event must be known about before it is eligible to be sent - * @param now the current time - * @param eventsTheyNeed the list of events we think they need + * @param selfId the id of this node + * @param nonAncestorThreshold for each event that is not a self event and is not an ancestor of a self event, the + * amount of time the event must be known about before it is eligible to be sent + * @param now the current time + * @param eventsTheyNeed the list of events we think they need + * @param latestSelfEventTipset the tipset of the latest self event, or null if there is none * @return the events that should be actually sent, will be a subset of the eventsTheyNeed list */ @NonNull public static List filterLikelyDuplicates( - @NonNull final ShadowGraph shadowGraph, @NonNull final NodeId selfId, @NonNull final Duration nonAncestorThreshold, @NonNull final Instant now, - @NonNull final List eventsTheyNeed) { - - final ShadowEvent latestSelfEvent = getLatestSelfEventInShadowgraph(shadowGraph, selfId); - - final Set selfEventAncestors; - if (latestSelfEvent == null) { - selfEventAncestors = Set.of(); - } else { - final List listOfLatestSelfEvent = List.of(latestSelfEvent); - selfEventAncestors = shadowGraph.findAncestors(listOfLatestSelfEvent, event -> true); - } - - // Convert to a list of hashes for easy lookup. - final List selfEventAncestorHashes = - selfEventAncestors.stream().map(ShadowEvent::getEventBaseHash).toList(); + @NonNull final List eventsTheyNeed, + @Nullable final Tipset latestSelfEventTipset) { final List filteredList = new ArrayList<>(); @@ -446,12 +432,31 @@ public static List filterLikelyDuplicates( continue; } + // FUTURE WORK: make sure this doesn't break when AB size changes + + // We want to answer the question: "Is this event an ancestor of my latest self event?" + // The latest self event's tipset makes this easy to answer. A tipset is basically just + // an array containing the latest generations of each event creator in our ancestry. + // If we compare the generation of the event we're looking at to the generation of the + // latest ancestor from the same creator, we can tell if this is an ancestor. If the event's + // generation is less than or equal to the latest ancestor's generation, then it is an ancestor. + // If it is greater than the latest ancestor's generation, then it is not an ancestor. + // + // Note: there is an edge case where this breaks down a little. If this event's creator is branching, + // then we may falsely conclude that this event is in our ancestry when it is not. But this is not + // harmful. The purpose of this method is to reduce the number of events we send to the peer, and so + // the worst that can happen is that we send a few extra events and get a slightly higher duplication rate. + + final boolean isAncestor = latestSelfEventTipset != null + && latestSelfEventTipset.getTipGenerationForNode(event.getCreatorId()) >= event.getGeneration(); + if (isAncestor) { + filteredList.add(event); + continue; + } + final Instant eventReceivedTime = event.getBaseEvent().getTimeReceived(); final Duration timeKnown = Duration.between(eventReceivedTime, now); - - final boolean isAncestor = selfEventAncestorHashes.contains(event.getBaseHash()); - - if (isAncestor || CompareTo.isGreaterThan(timeKnown, nonAncestorThreshold)) { + if (CompareTo.isGreaterThan(timeKnown, nonAncestorThreshold)) { // Always send ancestors of self events right away. // For all other events, only send it if we've known about it for long enough. filteredList.add(event); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java index 55e2871b0216..1e0f170bc189 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java @@ -46,6 +46,7 @@ import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.ProtocolConfig; import com.swirlds.platform.gossip.SyncPermitProvider; +import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.gossip.shadowgraph.ShadowGraphSynchronizer; import com.swirlds.platform.gossip.sync.config.SyncConfig; @@ -120,6 +121,7 @@ public class SyncGossip extends AbstractGossip { * @param appVersion the version of the app * @param epochHash the epoch hash of the initial state * @param shadowGraph contains non-ancient events + * @param latestEventTipsetTracker tracks the tipset of the latest self event * @param emergencyRecoveryManager handles emergency recovery * @param consensusRef a pointer to consensus * @param intakeQueue the event intake queue @@ -144,6 +146,7 @@ public SyncGossip( @NonNull final SoftwareVersion appVersion, @Nullable final Hash epochHash, @NonNull final ShadowGraph shadowGraph, + @Nullable final LatestEventTipsetTracker latestEventTipsetTracker, @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, @NonNull final AtomicReference consensusRef, @NonNull final QueueThread intakeQueue, @@ -185,6 +188,7 @@ public SyncGossip( platformContext, time, shadowGraph, + latestEventTipsetTracker, addressBook.getSize(), syncMetrics, consensusRef::get, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/config/SyncConfig.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/config/SyncConfig.java index d791a9419ac4..7c5b3fd6c39b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/config/SyncConfig.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/config/SyncConfig.java @@ -48,11 +48,11 @@ public record SyncConfig( @ConfigProperty(defaultValue = "25") int syncSleepAfterFailedNegotiation, @ConfigProperty(defaultValue = "17") int syncProtocolPermitCount, - @ConfigProperty(defaultValue = "false") boolean onePermitPerPeer, + @ConfigProperty(defaultValue = "true") boolean onePermitPerPeer, @ConfigProperty(defaultValue = "1000") int syncProtocolHeartbeatPeriod, @ConfigProperty(defaultValue = "true") boolean hashOnGossipThreads, @ConfigProperty(defaultValue = "true") boolean waitForEventsInIntake, - @ConfigProperty(defaultValue = "false") boolean filterLikelyDuplicates, + @ConfigProperty(defaultValue = "true") boolean filterLikelyDuplicates, @ConfigProperty(defaultValue = "3s") Duration nonAncestorFilterThreshold, @ConfigProperty(defaultValue = "500ms") Duration syncKeepalivePeriod, @ConfigProperty(defaultValue = "1m") Duration maxSyncTime) {} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/SyncMetrics.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/SyncMetrics.java index 6470748b05ef..428fce5586cc 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/SyncMetrics.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/metrics/SyncMetrics.java @@ -106,6 +106,11 @@ public class SyncMetrics { .withFormat(FORMAT_14_7); private final CountPerSecond syncsPerSec; + private static final RunningAverageMetric.Config SYNC_FILTER_TIME_CONFIG = new RunningAverageMetric.Config( + PLATFORM_CATEGORY, "syncFilterTime") + .withDescription("the average time spent filtering events during a sync") + .withUnit("nanoseconds"); + private final RunningAverageMetric tipsPerSync; private final AverageStat syncGenerationDiff; @@ -121,14 +126,13 @@ public class SyncMetrics { private final AverageAndMax avgEventsPerSyncRec; private final MaxStat multiTipsPerSync; private final AverageStat gensWaitingForExpiry; + private final RunningAverageMetric syncFilterTime; /** * Constructor of {@code SyncMetrics} * - * @param metrics - * a reference to the metrics-system - * @throws IllegalArgumentException - * if {@code metrics} is {@code null} + * @param metrics a reference to the metrics-system + * @throws IllegalArgumentException if {@code metrics} is {@code null} */ public SyncMetrics(final Metrics metrics) { avgBytesPerSecSync = metrics.getOrCreate(AVG_BYTES_PER_SEC_SYNC_CONFIG); @@ -141,6 +145,7 @@ public SyncMetrics(final Metrics metrics) { opportunitiesToInitiateSyncPerSec = new CountPerSecond(metrics, OPPORTUNITIES_TO_INITIATE_SYNC_CONFIG); outgoingSyncRequestsPerSec = new CountPerSecond(metrics, OUTGOING_SYNC_REQUESTS_CONFIG); syncsPerSec = new CountPerSecond(metrics, SYNCS_PER_SECOND_CONFIG); + syncFilterTime = metrics.getOrCreate(SYNC_FILTER_TIME_CONFIG); avgSyncDuration = new AverageAndMaxTimeStat( metrics, @@ -228,10 +233,8 @@ public SyncMetrics(final Metrics metrics) { /** * Supplies the generation numbers of a sync for statistics * - * @param self - * generations of our graph at the start of the sync - * @param other - * generations of their graph at the start of the sync + * @param self generations of our graph at the start of the sync + * @param other generations of their graph at the start of the sync */ public void generations(final GraphGenerations self, final GraphGenerations other) { syncGenerationDiff.update(self.getMaxRoundGeneration() - other.getMaxRoundGeneration()); @@ -240,10 +243,8 @@ public void generations(final GraphGenerations self, final GraphGenerations othe /** * Supplies information about the rate of receiving events when all events are read * - * @param nanosStart - * The {@link System#nanoTime()} when we started receiving events - * @param numberReceived - * the number of events received + * @param nanosStart The {@link System#nanoTime()} when we started receiving events + * @param numberReceived the number of events received */ public void eventsReceived(final long nanosStart, final int numberReceived) { if (numberReceived == 0) { @@ -257,10 +258,8 @@ public void eventsReceived(final long nanosStart, final int numberReceived) { /** * Record all stats related to sync timing * - * @param timing - * object that holds the timing data - * @param conn - * the sync connections + * @param timing object that holds the timing data + * @param conn the sync connections */ public void recordSyncTiming(final SyncTiming timing, final Connection conn) { avgSyncDuration1.update(timing.getTimePoint(0), timing.getTimePoint(1)); @@ -284,8 +283,7 @@ public void recordSyncTiming(final SyncTiming timing, final Connection conn) { * Records the size of the known set during a sync. This is the most compute intensive part of the sync, so this is * useful information to validate sync performance. * - * @param knownSetSize - * the size of the known set + * @param knownSetSize the size of the known set */ public void knownSetSize(final int knownSetSize) { this.knownSetSize.update(knownSetSize); @@ -294,8 +292,7 @@ public void knownSetSize(final int knownSetSize) { /** * Notifies the stats that a sync is done * - * @param info - * information about the sync that occurred + * @param info information about the sync that occurred */ public void syncDone(final SyncResult info) { if (info.isCaller()) { @@ -313,30 +310,27 @@ public void syncDone(final SyncResult info) { * Called by {@link ShadowGraphSynchronizer} to update the {@code tips/sync} statistic with the number of creators * that have more than one {@code sendTip} in the current synchronization. * - * @param multiTipCount - * the number of creators in the current synchronization that have more than one sending tip. + * @param multiTipCount the number of creators in the current synchronization that have more than one sending tip. */ public void updateMultiTipsPerSync(final int multiTipCount) { multiTipsPerSync.update(multiTipCount); } /** - * Called by {@link ShadowGraphSynchronizer} to update the {@code tips/sync} statistic with the number of {@code - * sendTips} in the current synchronization. + * Called by {@link ShadowGraphSynchronizer} to update the {@code tips/sync} statistic with the number of + * {@code sendTips} in the current synchronization. * - * @param tipCount - * the number of sending tips in the current synchronization. + * @param tipCount the number of sending tips in the current synchronization. */ public void updateTipsPerSync(final int tipCount) { tipsPerSync.update(tipCount); } /** - * Called by {@link ShadowGraph} to update the number of generations that should - * be expired but can't be yet due to reservations. + * Called by {@link ShadowGraph} to update the number of generations that should be expired but can't be yet due to + * reservations. * - * @param numGenerations - * the new number of generations + * @param numGenerations the new number of generations */ public void updateGensWaitingForExpiry(final long numGenerations) { gensWaitingForExpiry.update(numGenerations); @@ -378,4 +372,13 @@ public void opportunityToInitiateSync() { public void outgoingSyncRequestSent() { outgoingSyncRequestsPerSec.count(); } + + /** + * Record the amount of time spent filtering events during a sync. + * + * @param nanoseconds the amount of time spent filtering events during a sync + */ + public void recordSyncFilterTime(final long nanoseconds) { + syncFilterTime.update(nanoseconds); + } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java index 2183742cf86a..7c004513a30f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java @@ -92,13 +92,14 @@ public TestIntake( @NonNull final AddressBook ab, @NonNull final Time time, @NonNull final ConsensusConfig consensusConfig) { output = new ConsensusOutput(time); consensus = new ConsensusImpl(consensusConfig, ConsensusUtils.NOOP_CONSENSUS_METRICS, ab); - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); final ParentFinder parentFinder = new ParentFinder(shadowGraph::hashgraphEvent); linker = new OrphanBufferingLinker(consensusConfig, parentFinder, 100000, mock(IntakeEventCounter.class)); final EventObserverDispatcher dispatcher = - new EventObserverDispatcher(new ShadowGraphEventObserver(shadowGraph), output); + new EventObserverDispatcher(new ShadowGraphEventObserver(shadowGraph, null), output); final PlatformContext platformContext = TestPlatformContextBuilder.create() .withConfiguration(new TestConfigBuilder().getOrCreateConfig()) @@ -115,6 +116,7 @@ public TestIntake( dispatcher, mock(PhaseTimer.class), shadowGraph, + null, e -> {}, mock(IntakeEventCounter.class)); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java index 8dc08865a15a..9f571f3f5366 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java @@ -81,6 +81,7 @@ void test() { dispatcher, mock(PhaseTimer.class), shadowGraph, + null, e -> {}, mock(IntakeEventCounter.class)); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/intake/OrphanEventsIntakeTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/intake/OrphanEventsIntakeTest.java index 1be42898739a..45dd76a39428 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/intake/OrphanEventsIntakeTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/intake/OrphanEventsIntakeTest.java @@ -115,6 +115,7 @@ public Intake(final long generatorSeed, final long randomSeed, final int numNode (ConsensusRoundObserver) rnd -> consensusEvents.addAll(rnd.getConsensusEvents())), mock(PhaseTimer.class), mock(ShadowGraph.class), + null, e -> {}, mock(IntakeEventCounter.class)); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java index 68584db10f97..3605e12ce13e 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/ShadowGraphTest.java @@ -28,7 +28,9 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; +import com.swirlds.base.time.Time; import com.swirlds.common.crypto.Hash; +import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.common.utility.CommonUtils; @@ -99,7 +101,8 @@ private void initShadowGraph(final Random random, final int numEvents, final int final EventEmitterFactory factory = new EventEmitterFactory(random, addressBook); emitter = factory.newStandardEmitter(); - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); for (int i = 0; i < numEvents; i++) { IndexedEvent event = emitter.emitEvent(); @@ -122,13 +125,10 @@ private void initShadowGraph(final Random random, final int numEvents, final int } /** - * Tests that the {@link ShadowGraph#findAncestors(Iterable, Predicate)} returns the correct set of - * ancestors. + * Tests that the {@link ShadowGraph#findAncestors(Iterable, Predicate)} returns the correct set of ancestors. * - * @param numEvents - * the number of events to put in the shadow graph - * @param numNodes - * the number of nodes in the shadow graph + * @param numEvents the number of events to put in the shadow graph + * @param numNodes the number of nodes in the shadow graph */ @ParameterizedTest @MethodSource("graphSizes") @@ -210,12 +210,9 @@ private void printAncestors() { /** * This test verifies a single reservation can be made and closed without any event expiry. * - * @param numEvents - * the number of events to put in the shadow graph - * @param numNodes - * the number of nodes in the shadow graph - * @throws Exception - * if there was an error closing the reservation + * @param numEvents the number of events to put in the shadow graph + * @param numNodes the number of nodes in the shadow graph + * @throws Exception if there was an error closing the reservation */ @ParameterizedTest @MethodSource("graphSizes") @@ -244,12 +241,9 @@ void testSingleReservation(final int numEvents, final int numNodes) throws Excep /** * This test verifies multiple reservations of the same generation without any event expiry. * - * @param numEvents - * the number of events to put in the shadow graph - * @param numNodes - * the number of nodes in the shadow graph - * @throws Exception - * if there was an error closing the reservation + * @param numEvents the number of events to put in the shadow graph + * @param numNodes the number of nodes in the shadow graph + * @throws Exception if there was an error closing the reservation */ @ParameterizedTest @MethodSource("graphSizes") @@ -293,12 +287,9 @@ void testMultipleReservationsNoExpiry(final int numEvents, final int numNodes) t /** * This test verifies multiple reservations of the same generation with event expiry. * - * @param numEvents - * the number of events to put in the shadow graph - * @param numNodes - * the number of nodes in the shadow graph - * @throws Exception - * if there was an error closing the reservation + * @param numEvents the number of events to put in the shadow graph + * @param numNodes the number of nodes in the shadow graph + * @throws Exception if there was an error closing the reservation */ @ParameterizedTest @MethodSource("graphSizes") @@ -359,12 +350,9 @@ void testMultipleReservationsWithExpiry(final int numEvents, final int numNodes) /** * This test verifies that event expiry works correctly when there are no reservations. * - * @param numEvents - * the number of events to put in the shadow graph - * @param numNodes - * the number of nodes in the shadow graph - * @throws Exception - * if there was an error closing the reservation + * @param numEvents the number of events to put in the shadow graph + * @param numNodes the number of nodes in the shadow graph + * @throws Exception if there was an error closing the reservation */ @ParameterizedTest @MethodSource("graphSizes") @@ -401,12 +389,9 @@ private void assertEventsBelowGenAreExpired(final long expireBelowGen) { /** * Tests that event expiry works correctly when there are reservations for generations that should be expired. * - * @param numEvents - * the number of events to put in the shadow graph - * @param numNodes - * the number of nodes in the shadow graph - * @throws Exception - * if there was an error closing the reservation + * @param numEvents the number of events to put in the shadow graph + * @param numNodes the number of nodes in the shadow graph + * @throws Exception if there was an error closing the reservation */ @ParameterizedTest @MethodSource("graphSizes") @@ -707,7 +692,8 @@ void testTipsExpired() { @Test void testInitFromEvents_NullEventList() { - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); assertThrows( IllegalArgumentException.class, () -> shadowGraph.initFromEvents(null, 0L), @@ -716,7 +702,8 @@ void testInitFromEvents_NullEventList() { @Test void testInitFromEvents_EmptyEventList() { - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); final List empty = Collections.emptyList(); assertThrows( IllegalArgumentException.class, @@ -733,7 +720,8 @@ void testInitFromEvents_EventList() { new RandomAddressBookGenerator(random).setSize(4).build(); final EventEmitterFactory factory = new EventEmitterFactory(random, addressBook); emitter = factory.newStandardEmitter(); - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); List events = emitter.emitEvents(20); List filteredEvents = events.stream() @@ -760,7 +748,8 @@ void testInitFromEvents_EventListDifferentMinGen() { new RandomAddressBookGenerator(random).setSize(4).build(); final EventEmitterFactory factory = new EventEmitterFactory(random, addressBook); emitter = factory.newStandardEmitter(); - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); List events = emitter.emitEvents(20); List filteredEvents = @@ -785,7 +774,8 @@ void testInitFromEvents_AddEventThrows() { new RandomAddressBookGenerator(random).setSize(4).build(); final EventEmitterFactory factory = new EventEmitterFactory(random, addressBook); emitter = factory.newStandardEmitter(); - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); List events = emitter.emitEvents(20); List filteredEvents = @@ -814,7 +804,8 @@ void findAncestorsPerformance() throws ShadowGraphInsertionException { new RandomAddressBookGenerator(random).setSize(numNodes).build(); final EventEmitterFactory factory = new EventEmitterFactory(random, addressBook); emitter = factory.newStandardEmitter(); - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); for (int i = 0; i < numEvents; i++) { shadowGraph.addEvent(emitter.emitEvent()); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java index 31103dbbe71d..69849689697f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncFilteringTest.java @@ -142,7 +142,7 @@ void filterLikelyDuplicatesTest() { } final List filteredEvents = - SyncUtils.filterLikelyDuplicates(shadowGraph, selfId, nonAncestorThreshold, clock.now(), allEvents); + SyncUtils.filterLikelyDuplicates(selfId, nonAncestorThreshold, clock.now(), allEvents, null); assertEquals(expectedEvents.size(), filteredEvents.size()); for (final EventImpl event : filteredEvents) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java index dd4f1fff75bd..f71bb91f227e 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/sync/SyncNode.java @@ -38,6 +38,7 @@ import com.swirlds.platform.gossip.shadowgraph.ShadowGraphSynchronizer; import com.swirlds.platform.metrics.SyncMetrics; import com.swirlds.platform.network.Connection; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.event.emitter.EventEmitter; import com.swirlds.platform.test.fixtures.event.IndexedEvent; import com.swirlds.test.framework.config.TestConfigBuilder; @@ -114,7 +115,8 @@ public SyncNode( discardedEvents = new LinkedList<>(); saveGeneratedEvents = false; - shadowGraph = new ShadowGraph(mock(SyncMetrics.class)); + shadowGraph = + new ShadowGraph(Time.getCurrent(), mock(SyncMetrics.class), mock(AddressBook.class), new NodeId(0)); consensus = mock(Consensus.class); this.executor = executor; } @@ -241,6 +243,7 @@ public ShadowGraphSynchronizer getSynchronizer() throws InterruptedException { platformContext, Time.getCurrent(), shadowGraph, + null, numNodes, mock(SyncMetrics.class), this::getConsensus, From 18f0219a929cd39ccc33f043867bcb3451df9515 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:12:13 -0600 Subject: [PATCH 33/80] fix: add event creation throttle for new intake pipeline (#10611) Signed-off-by: Cody Littley --- .../com/swirlds/platform/SwirldsPlatform.java | 10 ++++ .../creation/EventCreationManagerFactory.java | 46 ++++++++++--------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 39a92b85c5b6..9474a1fbf977 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -805,6 +805,7 @@ public class SwirldsPlatform implements Platform { selfId, appVersion, transactionPool, + this::getIntakeQueueSize, platformStatusManager::getCurrentStatus, latestReconnectRound::get); @@ -962,6 +963,15 @@ public class SwirldsPlatform implements Platform { GuiPlatformAccessor.getInstance().setLatestCompleteStateComponent(selfId, latestCompleteState); } + /** + * Get the current size of the intake queue. Helper method to break a circular dependency. + * + * @return the current size of the intake queue + */ + private int getIntakeQueueSize() { + return intakeQueue.size(); + } + /** * Clears all pipelines in preparation for a reconnect. This method is needed to break a circular dependency. */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java index 3901d65d3ca9..29300f7a40b4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java @@ -41,6 +41,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import java.util.Random; +import java.util.function.IntSupplier; import java.util.function.Supplier; /** @@ -53,18 +54,18 @@ private EventCreationManagerFactory() {} /** * Create a new event creation manager (legacy pre-wiring version). * - * @param platformContext the platform's context - * @param threadManager manages the creation of new threads - * @param time provides the wall clock time - * @param signer can sign with this node's key - * @param addressBook the current address book - * @param selfId the ID of this node - * @param appVersion the current application version - * @param transactionPool provides transactions to be added to new events - * @param eventIntakeQueue the queue to which new events should be added - * @param eventObserverDispatcher wires together event intake logic - * @param platformStatusSupplier provides the current platform status - * @param latestReconnectRound provides the latest reconnect round + * @param platformContext the platform's context + * @param threadManager manages the creation of new threads + * @param time provides the wall clock time + * @param signer can sign with this node's key + * @param addressBook the current address book + * @param selfId the ID of this node + * @param appVersion the current application version + * @param transactionPool provides transactions to be added to new events + * @param eventIntakeQueue the queue to which new events should be added + * @param eventObserverDispatcher wires together event intake logic + * @param platformStatusSupplier provides the current platform status + * @param latestReconnectRound provides the latest reconnect round * @return a new event creation manager */ @NonNull @@ -133,15 +134,16 @@ public static AsyncEventCreationManager buildLegacyEventCreationManager( /** * Create a new event creation manager. * - * @param platformContext the platform's context - * @param time provides the wall clock time - * @param signer can sign with this node's key - * @param addressBook the current address book - * @param selfId the ID of this node - * @param appVersion the current application version - * @param transactionPool provides transactions to be added to new events - * @param platformStatusSupplier provides the current platform status - * @param latestReconnectRound provides the latest reconnect round + * @param platformContext the platform's context + * @param time provides the wall clock time + * @param signer can sign with this node's key + * @param addressBook the current address book + * @param selfId the ID of this node + * @param appVersion the current application version + * @param transactionPool provides transactions to be added to new events + * @param getIntakeQueueSize provides the size of the event intake queue + * @param platformStatusSupplier provides the current platform status + * @param latestReconnectRound provides the latest reconnect round * @return a new event creation manager */ @NonNull @@ -153,6 +155,7 @@ public static EventCreationManager buildEventCreationManager( @NonNull final NodeId selfId, @NonNull final SoftwareVersion appVersion, @NonNull final TransactionPool transactionPool, + @NonNull final IntSupplier getIntakeQueueSize, @NonNull final Supplier platformStatusSupplier, @NonNull final Supplier latestReconnectRound) { @@ -178,6 +181,7 @@ public static EventCreationManager buildEventCreationManager( final EventCreationRule eventCreationRules = AggregateEventCreationRules.of( new MaximumRateRule(platformContext, time), + new BackpressureRule(platformContext, getIntakeQueueSize), new PlatformStatusRule(platformStatusSupplier, transactionPool)); return new EventCreationManager(platformContext, time, eventCreator, eventCreationRules); From e4d88a1113f73f2bee84e373439344953a562396 Mon Sep 17 00:00:00 2001 From: Austin Littley <102969658+alittley@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:46:07 -0500 Subject: [PATCH 34/80] feat: Add broken up version of PCES classes (#10595) Signed-off-by: Austin Littley --- .../AsyncPreconsensusEventWriter.java | 2 + .../preconsensus/EventDurabilityNexus.java | 72 +++ .../event/preconsensus/PcesFileManager.java | 306 +++++++++ .../event/preconsensus/PcesFileReader.java | 207 ++++++ .../event/preconsensus/PcesFileTracker.java | 263 ++++++++ .../event/preconsensus/PcesUtilities.java | 252 ++++++++ .../event/preconsensus/PcesWriter.java | 380 +++++++++++ .../PreconsensusEventFileManager.java | 2 + .../PreconsensusEventUtilities.java | 2 + .../preconsensus/PreconsensusEventWriter.java | 2 + .../SyncPreconsensusEventWriter.java | 2 + .../components/DoneStreamingPcesTrigger.java | 22 + .../event/preconsensus/PcesWriterTests.java | 610 ++++++++++++++++++ 13 files changed, 2122 insertions(+) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/EventDurabilityNexus.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileManager.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileReader.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileTracker.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesUtilities.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesWriter.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/DoneStreamingPcesTrigger.java create mode 100644 platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/AsyncPreconsensusEventWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/AsyncPreconsensusEventWriter.java index 739aaf9f1f55..d56f641b349c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/AsyncPreconsensusEventWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/AsyncPreconsensusEventWriter.java @@ -33,6 +33,8 @@ /** * An object capable of writing preconsensus events to disk. Work is done asynchronously on a background thread. + *

    + * Future work: This class will be deleted once the PCES migration to the new framework is complete. */ public class AsyncPreconsensusEventWriter implements PreconsensusEventWriter { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/EventDurabilityNexus.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/EventDurabilityNexus.java new file mode 100644 index 000000000000..9556c111b829 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/EventDurabilityNexus.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.preconsensus; + +import com.swirlds.common.threading.CountUpLatch; +import com.swirlds.platform.event.GossipEvent; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Objects; + +/** + * A class used to determine if an event is guaranteed to be durable, i.e. flushed to disk. + */ +public class EventDurabilityNexus { + /** + * The highest event sequence number that has been flushed durably to disk. + */ + private final CountUpLatch latestDurableSequenceNumber = new CountUpLatch(-1); + + /** + * Set the highest event sequence number that has been flushed. + * + * @param lastFlushedEvent the highest event sequence number that has been flushed + */ + public void setLatestDurableSequenceNumber(final long lastFlushedEvent) { + this.latestDurableSequenceNumber.set(lastFlushedEvent); + } + + /** + * Determine if an event is guaranteed to be durable, i.e. flushed to disk + * + * @param event the event in question + * @return true if the event is guaranteed to be durable, false otherwise + */ + public boolean isEventDurable(@NonNull final GossipEvent event) { + Objects.requireNonNull(event); + if (event.getStreamSequenceNumber() == GossipEvent.STALE_EVENT_STREAM_SEQUENCE_NUMBER) { + // Stale events are not written to disk. + return false; + } + + return event.getStreamSequenceNumber() <= latestDurableSequenceNumber.getCount(); + } + + /** + * Wait until an event is guaranteed to be durable, i.e. flushed to disk + * + * @param event the event in question + * @throws InterruptedException if interrupted while waiting + */ + public void waitUntilDurable(@NonNull final GossipEvent event) throws InterruptedException { + Objects.requireNonNull(event); + if (event.getStreamSequenceNumber() == GossipEvent.STALE_EVENT_STREAM_SEQUENCE_NUMBER) { + throw new IllegalStateException("Event is stale and will never be durable"); + } + + latestDurableSequenceNumber.await(event.getStreamSequenceNumber()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileManager.java new file mode 100644 index 000000000000..0859dec424d8 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileManager.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2016-2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.preconsensus; + +import static com.swirlds.logging.legacy.LogMarker.STARTUP; +import static com.swirlds.platform.event.preconsensus.PcesUtilities.getDatabaseDirectory; + +import com.swirlds.base.time.Time; +import com.swirlds.base.units.UnitConstants; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + *

    + * This object tracks a group of event files. + *

    + * + *

    + * This object is not thread safe. + *

    + */ +public class PcesFileManager { + + private static final Logger logger = LogManager.getLogger(PcesFileManager.class); + + /** + * This constant can be used when the caller wants all events, regardless of generation. + */ + public static final long NO_MINIMUM_GENERATION = -1; + + /** + * Provides the wall clock time. + */ + private final Time time; + + private final PreconsensusEventMetrics metrics; + + /** + * The root directory where event files are stored. + */ + private final Path databaseDirectory; + + /** + * The current origin round. + */ + private long currentOrigin; + + /** + * The minimum amount of time that must pass before a file becomes eligible for deletion. + */ + private final Duration minimumRetentionPeriod; + + /** + * The size of all tracked files, in bytes. + */ + private long totalFileByteCount = 0; + + private final PcesFileTracker files; + + /** + * Constructor + * + * @param platformContext the platform context for this node + * @param time provides wall clock time + * @param files the files to track + * @param selfId the ID of this node + * @param startingRound the round number of the initial state of the system + * @throws IOException if there is an error reading the files + */ + public PcesFileManager( + @NonNull final PlatformContext platformContext, + @NonNull final Time time, + @NonNull final PcesFileTracker files, + @NonNull final NodeId selfId, + final long startingRound) + throws IOException { + + Objects.requireNonNull(platformContext); + Objects.requireNonNull(selfId); + + if (startingRound < 0) { + throw new IllegalArgumentException("starting round must be non-negative"); + } + + final PreconsensusEventStreamConfig preconsensusEventStreamConfig = + platformContext.getConfiguration().getConfigData(PreconsensusEventStreamConfig.class); + + this.time = Objects.requireNonNull(time); + this.files = Objects.requireNonNull(files); + this.metrics = new PreconsensusEventMetrics(platformContext.getMetrics()); + this.minimumRetentionPeriod = preconsensusEventStreamConfig.minimumRetentionPeriod(); + this.databaseDirectory = getDatabaseDirectory(platformContext, selfId); + + this.currentOrigin = PcesUtilities.getInitialOrigin(files, startingRound); + + initializeMetrics(); + } + + /** + * Initialize metrics given the files currently on disk. + */ + private void initializeMetrics() throws IOException { + totalFileByteCount = files.getTotalFileByteCount(); + + if (files.getFileCount() > 0) { + metrics.getPreconsensusEventFileOldestGeneration() + .set(files.getFirstFile().getMinimumGeneration()); + metrics.getPreconsensusEventFileYoungestGeneration() + .set(files.getLastFile().getMaximumGeneration()); + final Duration age = Duration.between(files.getFirstFile().getTimestamp(), time.now()); + metrics.getPreconsensusEventFileOldestSeconds().set(age.toSeconds()); + } else { + metrics.getPreconsensusEventFileOldestGeneration().set(NO_MINIMUM_GENERATION); + metrics.getPreconsensusEventFileYoungestGeneration().set(NO_MINIMUM_GENERATION); + metrics.getPreconsensusEventFileOldestSeconds().set(0); + } + updateFileSizeMetrics(); + } + + /** + * Get the sequence number that should be allocated next. + * + * @return the sequence number that should be allocated next + */ + private long getNextSequenceNumber() { + if (files.getFileCount() == 0) { + return 0; + } + return files.getLastFile().getSequenceNumber() + 1; + } + + /** + * Register a discontinuity in the stream. + * + * @param newOriginRound the new origin for stream files written after this method is called + */ + public void registerDiscontinuity(final long newOriginRound) { + if (newOriginRound <= currentOrigin) { + throw new IllegalArgumentException("New origin round must be greater than the current origin round. " + + "Current origin round: " + currentOrigin + ", new origin round: " + newOriginRound); + } + + final PreconsensusEventFile lastFile = files.getFileCount() > 0 ? files.getLastFile() : null; + + logger.info( + STARTUP.getMarker(), + "Due to recent operations on this node, the local preconsensus event stream" + + " will have a discontinuity. The last file with the old origin round is {}. " + + "All future files will have an origin round of {}.", + lastFile, + newOriginRound); + + currentOrigin = newOriginRound; + } + + /** + * Create a new event file descriptor for the next event file, and start tracking it. (Note, this method doesn't + * actually open the file, it just permits the file to be opened by the caller.) + * + * @param minimumGeneration the minimum generation that can be stored in the file + * @param maximumGeneration the maximum generation that can be stored in the file + * @return a new event file descriptor + */ + public @NonNull PreconsensusEventFile getNextFileDescriptor( + final long minimumGeneration, final long maximumGeneration) { + + if (minimumGeneration > maximumGeneration) { + throw new IllegalArgumentException("minimum generation must be less than or equal to maximum generation"); + } + + final long minimumGenerationForFile; + final long maximumGenerationForFile; + + if (files.getFileCount() == 0) { + // This is the first file + minimumGenerationForFile = minimumGeneration; + maximumGenerationForFile = maximumGeneration; + } else { + // This is not the first file, min/max values are constrained to only increase + minimumGenerationForFile = + Math.max(minimumGeneration, files.getLastFile().getMinimumGeneration()); + maximumGenerationForFile = + Math.max(maximumGeneration, files.getLastFile().getMaximumGeneration()); + } + + final PreconsensusEventFile descriptor = PreconsensusEventFile.of( + time.now(), + getNextSequenceNumber(), + minimumGenerationForFile, + maximumGenerationForFile, + currentOrigin, + databaseDirectory); + + if (files.getFileCount() > 0) { + // There are never enough sanity checks. This is the same sanity check that is run when we parse + // the files from disk, so if it doesn't pass now it's not going to pass when we read the files. + final PreconsensusEventFile previousFile = files.getLastFile(); + PcesUtilities.fileSanityChecks( + false, + previousFile.getSequenceNumber(), + previousFile.getMinimumGeneration(), + previousFile.getMaximumGeneration(), + currentOrigin, + previousFile.getTimestamp(), + descriptor); + } + + files.addFile(descriptor); + metrics.getPreconsensusEventFileYoungestGeneration().set(descriptor.getMaximumGeneration()); + + return descriptor; + } + + /** + * The event file writer calls this method when it finishes writing an event file. + * + * @param file the file that has been completely written + */ + public void finishedWritingFile(@NonNull final PreconsensusEventMutableFile file) { + final long previousFileHighestGeneration; + if (files.getFileCount() == 1) { + previousFileHighestGeneration = 0; + } else { + previousFileHighestGeneration = + files.getFile(files.getFileCount() - 2).getMaximumGeneration(); + } + + // Compress the generational span of the file. Reduces overlap between files. + final PreconsensusEventFile compressedDescriptor = file.compressGenerationalSpan(previousFileHighestGeneration); + files.setFile(files.getFileCount() - 1, compressedDescriptor); + + // Update metrics + totalFileByteCount += file.fileSize(); + metrics.getPreconsensusEventFileRate().cycle(); + metrics.getPreconsensusEventAverageFileSpan().update(file.getGenerationalSpan()); + metrics.getPreconsensusEventAverageUnUtilizedFileSpan().update(file.getUnUtilizedGenerationalSpan()); + updateFileSizeMetrics(); + } + + /** + * Prune old event files. Files are pruned if they are too old AND if they do not contain events with high enough + * generations. + * + * @param minimumGeneration the minimum generation that we need to keep in the database. It's possible that this + * operation won't delete all files with events older than this value, but this operation + * is guaranteed not to delete any files that may contain events with a higher generation. + * @throws IOException if there is an error deleting files + */ + public void pruneOldFiles(final long minimumGeneration) throws IOException { + final Instant minimumTimestamp = time.now().minus(minimumRetentionPeriod); + + while (files.getFileCount() > 0 + && files.getFirstFile().getMaximumGeneration() < minimumGeneration + && files.getFirstFile().getTimestamp().isBefore(minimumTimestamp)) { + + final PreconsensusEventFile file = files.removeFirstFile(); + totalFileByteCount -= Files.size(file.getPath()); + file.deleteFile(databaseDirectory); + } + + if (files.getFileCount() > 0) { + metrics.getPreconsensusEventFileOldestGeneration() + .set(files.getFirstFile().getMinimumGeneration()); + final Duration age = Duration.between(files.getFirstFile().getTimestamp(), time.now()); + metrics.getPreconsensusEventFileOldestSeconds().set(age.toSeconds()); + } + + updateFileSizeMetrics(); + } + + /** + * Update metrics with the latest data on file size. + */ + private void updateFileSizeMetrics() { + metrics.getPreconsensusEventFileCount().set(files.getFileCount()); + metrics.getPreconsensusEventFileTotalSizeGB().set(totalFileByteCount * UnitConstants.BYTES_TO_GIBIBYTES); + + if (files.getFileCount() > 0) { + metrics.getPreconsensusEventFileAverageSizeMB() + .set(((double) totalFileByteCount) / files.getFileCount() * UnitConstants.BYTES_TO_MEBIBYTES); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileReader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileReader.java new file mode 100644 index 000000000000..0826f66772ce --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileReader.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.preconsensus; + +import static com.swirlds.logging.legacy.LogMarker.STARTUP; +import static com.swirlds.platform.event.preconsensus.PcesUtilities.compactPreconsensusEventFile; +import static com.swirlds.platform.event.preconsensus.PcesUtilities.fileSanityChecks; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.io.utility.RecycleBin; +import com.swirlds.common.utility.ValueReference; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * This class is responsible for reading event files from disk and adding them to the collection of tracked files. + */ +public class PcesFileReader { + private static final Logger logger = LogManager.getLogger(PcesFileReader.class); + + /** + * Hidden constructor. + */ + private PcesFileReader() {} + + /** + * Scan the file system for event files and add them to the collection of tracked files. + * + * @param platformContext the platform context + * @param recycleBin the recycle bin + * @param databaseDirectory the directory to scan for files + * @param startingRound the round to start reading from + * @param permitGaps if gaps are permitted in sequence number + * @return the files read from disk + * @throws IOException if there is an error reading the files + */ + public static PcesFileTracker readFilesFromDisk( + @NonNull final PlatformContext platformContext, + @NonNull final RecycleBin recycleBin, + @NonNull final Path databaseDirectory, + final long startingRound, + final boolean permitGaps) + throws IOException { + + Objects.requireNonNull(platformContext); + Objects.requireNonNull(databaseDirectory); + + final PcesFileTracker files = new PcesFileTracker(); + + try (final Stream fileStream = Files.walk(databaseDirectory)) { + fileStream + .filter(f -> !Files.isDirectory(f)) + .map(PcesUtilities::parseFile) + .filter(Objects::nonNull) + .sorted() + .forEachOrdered(buildFileHandler(files, permitGaps)); + } + + final PreconsensusEventStreamConfig preconsensusEventStreamConfig = + platformContext.getConfiguration().getConfigData(PreconsensusEventStreamConfig.class); + final boolean doInitialGenerationalCompaction = preconsensusEventStreamConfig.compactLastFileOnStartup(); + + if (files.getFileCount() != 0 && doInitialGenerationalCompaction) { + compactGenerationalSpanOfLastFile(files); + } + + resolveDiscontinuities(databaseDirectory, recycleBin, files, startingRound); + + return files; + } + + /** + * It's possible (if not probable) that the node was shut down prior to the last file being closed and having its + * generational span compaction. This method performs that compaction if necessary. + */ + private static void compactGenerationalSpanOfLastFile(@NonNull final PcesFileTracker files) { + Objects.requireNonNull(files); + + final PreconsensusEventFile lastFile = files.getFile(files.getFileCount() - 1); + + final long previousMaximumGeneration; + if (files.getFileCount() > 1) { + final PreconsensusEventFile secondToLastFile = files.getFile(files.getFileCount() - 2); + previousMaximumGeneration = secondToLastFile.getMaximumGeneration(); + } else { + previousMaximumGeneration = 0; + } + + final PreconsensusEventFile compactedFile = compactPreconsensusEventFile(lastFile, previousMaximumGeneration); + files.setFile(files.getFileCount() - 1, compactedFile); + } + + /** + * Build a handler for new files parsed from disk. Does basic sanity checks on the files, and adds them to the file + * list if they are valid. + * + * @param permitGaps if gaps are permitted in sequence number + * @return the handler + */ + @NonNull + private static Consumer buildFileHandler( + @NonNull final PcesFileTracker files, final boolean permitGaps) { + final ValueReference previousSequenceNumber = new ValueReference<>(-1L); + final ValueReference previousMinimumGeneration = new ValueReference<>(-1L); + final ValueReference previousMaximumGeneration = new ValueReference<>(-1L); + final ValueReference previousOrigin = new ValueReference<>(-1L); + final ValueReference previousTimestamp = new ValueReference<>(); + + return descriptor -> { + if (previousSequenceNumber.getValue() != -1) { + fileSanityChecks( + permitGaps, + previousSequenceNumber.getValue(), + previousMinimumGeneration.getValue(), + previousMaximumGeneration.getValue(), + previousOrigin.getValue(), + previousTimestamp.getValue(), + descriptor); + } + + previousSequenceNumber.setValue(descriptor.getSequenceNumber()); + previousMinimumGeneration.setValue(descriptor.getMinimumGeneration()); + previousMaximumGeneration.setValue(descriptor.getMaximumGeneration()); + previousTimestamp.setValue(descriptor.getTimestamp()); + + // If the sequence number is good then add it to the collection of tracked files + files.addFile(descriptor); + }; + } + + /** + * If there is a discontinuity in the stream after the location where we will begin streaming, delete all files that + * come after the discontinuity. + * + * @param databaseDirectory the directory where PCES files are stored + * @param recycleBin the recycle bin + * @param files the files that have been read from disk + * @param startingRound the round the system is starting from + * @throws IOException if there is an error deleting files + */ + private static void resolveDiscontinuities( + @NonNull final Path databaseDirectory, + @NonNull final RecycleBin recycleBin, + @NonNull final PcesFileTracker files, + final long startingRound) + throws IOException { + + final long initialOrigin = PcesUtilities.getInitialOrigin(files, startingRound); + + final int firstRelevantFileIndex = files.getFirstRelevantFileIndex(startingRound); + int firstIndexToDelete = firstRelevantFileIndex + 1; + for (; firstIndexToDelete < files.getFileCount(); firstIndexToDelete++) { + final PreconsensusEventFile file = files.getFile(firstIndexToDelete); + if (file.getOrigin() != initialOrigin) { + // as soon as we find a file that has a different origin, this and all subsequent files must be deleted + break; + } + } + + if (firstIndexToDelete == files.getFileCount()) { + // No discontinuities were detected + return; + } + + final PreconsensusEventFile lastUndeletedFile = + firstIndexToDelete > 0 ? files.getFile(firstIndexToDelete - 1) : null; + + logger.warn( + STARTUP.getMarker(), + """ + Discontinuity detected in the preconsensus event stream. Purging {} file(s). + Last undeleted file: {} + First deleted file: {} + Last deleted file: {}""", + files.getFileCount() - firstIndexToDelete, + lastUndeletedFile, + files.getFile(firstIndexToDelete), + files.getLastFile()); + + // Delete files in reverse order so that if we crash we don't leave gaps in the sequence number if we crash. + while (files.getFileCount() > firstIndexToDelete) { + files.removeLastFile().deleteFile(databaseDirectory, recycleBin); + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileTracker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileTracker.java new file mode 100644 index 000000000000..ddb60ca32dea --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesFileTracker.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.preconsensus; + +import static com.swirlds.logging.legacy.LogMarker.STARTUP; +import static com.swirlds.platform.event.preconsensus.PcesFileManager.NO_MINIMUM_GENERATION; + +import com.swirlds.common.utility.RandomAccessDeque; +import com.swirlds.common.utility.UnmodifiableIterator; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Tracks preconsensus event files currently on disk. + */ +public class PcesFileTracker { + private static final Logger logger = LogManager.getLogger(PcesFileTracker.class); + + /** + * The initial size of the ring buffer used to track event files. + */ + private static final int INITIAL_RING_BUFFER_SIZE = 1024; + + /** + * Tracks all files currently on disk. + */ + private final RandomAccessDeque files = new RandomAccessDeque<>(INITIAL_RING_BUFFER_SIZE); + + /** + * Get the first file in the file list. + * + * @return the first file in the file list + */ + public PreconsensusEventFile getFirstFile() { + return files.getFirst(); + } + + /** + * Get the last file in the file list. + * + * @return the last file in the file list + */ + public PreconsensusEventFile getLastFile() { + return files.getLast(); + } + + /** + * Remove the first file in the file list. + * + * @return the file that was removed + */ + public PreconsensusEventFile removeFirstFile() { + return files.removeFirst(); + } + + /** + * Remove the last file in the file list. + * + * @return the file that was removed + */ + public PreconsensusEventFile removeLastFile() { + return files.removeLast(); + } + + /** + * Get the number of files currently being tracked. + * + * @return the number of files currently being tracked + */ + public int getFileCount() { + return files.size(); + } + + /** + * Get the number of bytes in all files currently being tracked. + * + * @return the number of bytes in all files currently being tracked + * @throws IOException if there is an error reading the files + */ + public long getTotalFileByteCount() throws IOException { + long totalFileByteCount = 0; + + // Measure the size of each file. + for (final PreconsensusEventFile file : files) { + totalFileByteCount += Files.size(file.getPath()); + } + + return totalFileByteCount; + } + + /** + * Add a new files to the end of the file list. + * + * @param file the file to be added + */ + public void addFile(@NonNull final PreconsensusEventFile file) { + Objects.requireNonNull(file); + files.addLast(file); + } + + /** + * Get the file at a specified index. + * + * @param index the index of the file to get + * @return the file at the specified index + */ + public PreconsensusEventFile getFile(final int index) { + return files.get(index); + } + + /** + * Set the file at a specified index. + * + * @param index the index of the file to set + * @param file the file to set + */ + public void setFile(final int index, @NonNull final PreconsensusEventFile file) { + Objects.requireNonNull(file); + files.set(index, file); + } + + /** + * Get an iterator that walks over all events starting with a specified generation. + *

    + * Note: this method only works at system startup time, using this iterator after startup has undefined behavior. A + * future task will be to enable event iteration after startup. + * + * @param minimumGeneration the desired minimum generation, iterator is guaranteed to return all available events + * with a generation greater or equal to this value. No events with a smaller generation + * will be returned. A value of {@link PcesFileManager ::NO_MINIMUM_GENERATION} + * will cause the returned iterator to walk over all available events. + * @param startingRound the round to start iterating from + * @return an iterator that walks over events + */ + public @NonNull PreconsensusEventMultiFileIterator getEventIterator( + final long minimumGeneration, final long startingRound) { + return new PreconsensusEventMultiFileIterator( + minimumGeneration, getFileIterator(minimumGeneration, startingRound)); + } + + /** + * Get an iterator that walks over all event files currently being tracked, in order. + *

    + * Note: this method only works at system startup time, using this iterator after startup has undefined behavior. A + * future task will be to enable event iteration after startup. + * + * @param minimumGeneration the desired minimum generation, iterator is guaranteed to walk over all files that may + * contain events with a generation greater or equal to this value. A value of + * {@link PcesFileManager#NO_MINIMUM_GENERATION} will cause the returned + * iterator to walk over all available event files. + * @param startingRound the round to start iterating from + * @return an unmodifiable iterator that walks over event files in order + */ + public @NonNull Iterator getFileIterator( + final long minimumGeneration, final long startingRound) { + final int firstFileIndex = getFirstRelevantFileIndex(startingRound); + + // Edge case: we want all events regardless of generation + if (minimumGeneration == NO_MINIMUM_GENERATION) { + return new UnmodifiableIterator<>(files.iterator(firstFileIndex)); + } + + // Edge case: there are no files + if (files.size() == 0) { + logger.warn(STARTUP.getMarker(), "No preconsensus event files available"); + return Collections.emptyIterator(); + } + + // Edge case: our first file comes after the requested starting generation + if (files.get(firstFileIndex).getMinimumGeneration() >= minimumGeneration) { + // Unless we observe at least one file with a minimum generation less than the requested minimum, + // then we can't know for certain that we have all data for the requested minimum generation. + logger.warn( + STARTUP.getMarker(), + "The preconsensus event stream has insufficient data to guarantee that all events with the " + + "requested generation of {} are present, the first file has a minimum generation of {}", + minimumGeneration, + files.getFirst().getMinimumGeneration()); + + return new UnmodifiableIterator<>(files.iterator(firstFileIndex)); + } + + // Edge case: all of our data comes before the requested starting generation + if (files.getLast().getMaximumGeneration() < minimumGeneration) { + logger.warn( + STARTUP.getMarker(), + "The preconsensus event stream has insufficient data to guarantee that " + + "all events with the requested minimum generation of {} are present, " + + "the last file has a maximum generation of {}", + minimumGeneration, + files.getLast().getMaximumGeneration()); + return Collections.emptyIterator(); + } + + // Standard case: we need to stream data starting from a file somewhere in the middle of stream + final int fileCount = files.size(); + for (int index = firstFileIndex; index < fileCount; index++) { + final PreconsensusEventFile file = files.get(index); + if (file.getMaximumGeneration() >= minimumGeneration) { + // We have found the first file that may contain events at the requested generation. + return new UnmodifiableIterator<>(files.iterator(index)); + } + } + + // It should not be possible to reach this point. + throw new IllegalStateException("Failed to find a file that may contain events at the requested generation"); + } + + /** + * Get the index of the first file to consider given a certain starting round. This will be the file with the + * largest origin that does not exceed the starting round. + *

    + * If no file is compatible with the starting round, return -1. If there are no compatible files, that means there + * are either no files, or all files have an origin that exceeds the starting round. + * + * @param startingRound the round to start streaming from + * @return the index of the first file to consider for a given starting round + */ + public int getFirstRelevantFileIndex(final long startingRound) { + // When streaming from the preconsensus event stream, we need to start at + // the file with the largest origin that does not exceed the starting round. + + int candidateIndex = -1; + long candidateOrigin = -1; + + for (int index = 0; index < files.size(); index++) { + final long fileOrigin = files.get(index).getOrigin(); + + if (fileOrigin > startingRound) { + // Once we find the first file with an origin that exceeds the starting round, we can stop searching. + // File origins only increase, so we know that all files after this one will also exceed the round. + return candidateIndex; + } else if (fileOrigin > candidateOrigin) { + // We've discovered a higher legal origin. Keep track of the first file with this origin. + candidateIndex = index; + candidateOrigin = fileOrigin; + } + } + + // If all files have a legal origin, return the index of the first file with the highest index. + return candidateIndex; + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesUtilities.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesUtilities.java new file mode 100644 index 000000000000..5924e2268990 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesUtilities.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.preconsensus; + +import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; +import static com.swirlds.logging.legacy.LogMarker.STARTUP; + +import com.swirlds.common.config.StateConfig; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.io.IOIterator; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.event.GossipEvent; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Utilities for preconsensus events. + */ +public final class PcesUtilities { + + private static final Logger logger = LogManager.getLogger(PcesUtilities.class); + + private PcesUtilities() {} + + /** + * Compact the generational span of a PCES file. + * + * @param originalFile the file to compact + * @param previousMaximumGeneration the maximum generation of the previous PCES file, used to prevent using a + * smaller maximum generation than the previous file. + * @return the new compacted PCES file. + */ + @NonNull + public static PreconsensusEventFile compactPreconsensusEventFile( + @NonNull final PreconsensusEventFile originalFile, final long previousMaximumGeneration) { + + // Find the maximum generation in the file. + long maxGeneration = originalFile.getMinimumGeneration(); + try (final IOIterator iterator = new PreconsensusEventFileIterator(originalFile, 0)) { + + while (iterator.hasNext()) { + final GossipEvent next = iterator.next(); + maxGeneration = Math.max(maxGeneration, next.getGeneration()); + } + + } catch (final IOException e) { + logger.error(EXCEPTION.getMarker(), "Failed to read file {}", originalFile.getPath(), e); + return originalFile; + } + + // Important: do not decrease the maximum generation below the value of the previous file's maximum generation. + maxGeneration = Math.max(maxGeneration, previousMaximumGeneration); + + if (maxGeneration == originalFile.getMaximumGeneration()) { + // The file cannot have its span compacted any further. + logger.info(STARTUP.getMarker(), "No span compaction necessary for {}", originalFile.getPath()); + return originalFile; + } + + // Now, compact the generational span of the file using the newly discovered maximum generation. + final PreconsensusEventFile newFile = originalFile.buildFileWithCompressedSpan(maxGeneration); + try { + Files.move(originalFile.getPath(), newFile.getPath(), StandardCopyOption.ATOMIC_MOVE); + } catch (final IOException e) { + logger.error(EXCEPTION.getMarker(), "Failed to compact span of file {}", originalFile.getPath(), e); + return originalFile; + } + + logger.info( + STARTUP.getMarker(), + "Span compaction completed for {}, new maximum generation is {}", + originalFile.getPath(), + maxGeneration); + + return newFile; + } + + /** + * Parse a file into a PreConsensusEventFile wrapper object. + * + * @param path the path to the file + * @return the wrapper object, or null if the file can't be parsed + */ + @Nullable + public static PreconsensusEventFile parseFile(@NonNull final Path path) { + try { + return PreconsensusEventFile.of(path); + } catch (final IOException exception) { + // ignore any file that can't be parsed + logger.warn(EXCEPTION.getMarker(), "Failed to parse file: {}", path, exception); + return null; + } + } + + /** + * Compact all PCES files within a directory tree. + * + * @param rootPath the root of the directory tree + */ + public static void compactPreconsensusEventFiles(@NonNull final Path rootPath) { + final List files = new ArrayList<>(); + try (final Stream fileStream = Files.walk(rootPath)) { + fileStream + .filter(f -> !Files.isDirectory(f)) + .filter(f -> f.toString().endsWith(PreconsensusEventFile.EVENT_FILE_EXTENSION)) + .map(PcesUtilities::parseFile) + .filter(Objects::nonNull) + .sorted() + .forEachOrdered(files::add); + } catch (final IOException e) { + logger.error(EXCEPTION.getMarker(), "Failed to walk directory tree {}", rootPath, e); + } + + long previousMaximumGeneration = 0; + for (final PreconsensusEventFile file : files) { + final PreconsensusEventFile compactedFile = compactPreconsensusEventFile(file, previousMaximumGeneration); + previousMaximumGeneration = compactedFile.getMaximumGeneration(); + } + } + + /** + * Perform sanity checks on the properties of the next file in the sequence, to ensure that we maintain various + * invariants. + * + * @param permitGaps if gaps are permitted in sequence number + * @param previousSequenceNumber the sequence number of the previous file + * @param previousMinimumGeneration the minimum generation of the previous file + * @param previousMaximumGeneration the maximum generation of the previous file + * @param previousOrigin the origin round of the previous file + * @param previousTimestamp the timestamp of the previous file + * @param descriptor the descriptor of the next file + * @throws IllegalStateException if any of the required invariants are violated by the next file + */ + public static void fileSanityChecks( + final boolean permitGaps, + final long previousSequenceNumber, + final long previousMinimumGeneration, + final long previousMaximumGeneration, + final long previousOrigin, + @NonNull final Instant previousTimestamp, + @NonNull final PreconsensusEventFile descriptor) { + + // Sequence number should always monotonically increase + if (!permitGaps && previousSequenceNumber + 1 != descriptor.getSequenceNumber()) { + throw new IllegalStateException("Gap in preconsensus event files detected! Previous sequence number was " + + previousSequenceNumber + ", next sequence number is " + + descriptor.getSequenceNumber()); + } + + // Minimum generation may never decrease + if (descriptor.getMinimumGeneration() < previousMinimumGeneration) { + throw new IllegalStateException("Minimum generation must never decrease, file " + descriptor.getPath() + + " has a minimum generation that is less than the previous minimum generation of " + + previousMinimumGeneration); + } + + // Maximum generation may never decrease + if (descriptor.getMaximumGeneration() < previousMaximumGeneration) { + throw new IllegalStateException("Maximum generation must never decrease, file " + descriptor.getPath() + + " has a maximum generation that is less than the previous maximum generation of " + + previousMaximumGeneration); + } + + // Timestamp must never decrease + if (descriptor.getTimestamp().isBefore(previousTimestamp)) { + throw new IllegalStateException("Timestamp must never decrease, file " + descriptor.getPath() + + " has a timestamp that is less than the previous timestamp of " + + previousTimestamp); + } + + // Origin round must never decrease + if (descriptor.getOrigin() < previousOrigin) { + throw new IllegalStateException("Origin round must never decrease, file " + descriptor.getPath() + + " has an origin round that is less than the previous origin round of " + + previousOrigin); + } + } + + /** + * Get the directory where event files are stored. If that directory doesn't exist, create it. + * + * @param platformContext the platform context for this node + * @param selfId the ID of this node + * @return the directory where event files are stored + * @throws IOException if an error occurs while creating the directory + */ + @NonNull + public static Path getDatabaseDirectory( + @NonNull final PlatformContext platformContext, @NonNull final NodeId selfId) throws IOException { + + final StateConfig stateConfig = platformContext.getConfiguration().getConfigData(StateConfig.class); + final PreconsensusEventStreamConfig preconsensusEventStreamConfig = + platformContext.getConfiguration().getConfigData(PreconsensusEventStreamConfig.class); + + final Path savedStateDirectory = stateConfig.savedStateDirectory(); + final Path databaseDirectory = savedStateDirectory + .resolve(preconsensusEventStreamConfig.databaseDirectory()) + .resolve(Long.toString(selfId.id())); + + if (!Files.exists(databaseDirectory)) { + Files.createDirectories(databaseDirectory); + } + + return databaseDirectory; + } + + /** + * Get the initial origin round for the PCES files. This is the origin round of the first file that is compatible + * with the starting round, or the starting round itself if no file has an original that is compatible with the + * starting round. + * + * @param files the files that have been read from disk + * @param startingRound the round the system is starting from + * @return the initial origin round + */ + public static long getInitialOrigin(@NonNull final PcesFileTracker files, final long startingRound) { + final int firstRelevantFileIndex = files.getFirstRelevantFileIndex(startingRound); + if (firstRelevantFileIndex >= 0) { + // if there is a file with an origin that is compatible with the starting round, use that origin + return files.getFile(firstRelevantFileIndex).getOrigin(); + } else { + // if there is no file with an origin that is compatible with the starting round, use the starting round + // itself as the origin. + return startingRound; + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesWriter.java new file mode 100644 index 000000000000..3042e37cb713 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PcesWriter.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2016-2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.event.preconsensus; + +import static com.swirlds.common.units.DataUnit.UNIT_BYTES; +import static com.swirlds.common.units.DataUnit.UNIT_MEGABYTES; +import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.utility.LongRunningAverage; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.wiring.DoneStreamingPcesTrigger; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * This object is responsible for writing events to the database. + */ +public class PcesWriter { + + private static final Logger logger = LogManager.getLogger(PcesWriter.class); + + /** + * Keeps track of the event stream files on disk. + */ + private final PcesFileManager fileManager; + + /** + * The current file that is being written to. + */ + private PreconsensusEventMutableFile currentMutableFile; + + /** + * The current minimum generation required to be considered non-ancient. Only read and written on the handle + * thread. + */ + private long minimumGenerationNonAncient = 0; + + /** + * The desired file size, in megabytes. Is not a hard limit, it's possible that we may exceed this value by a small + * amount (we never stop in the middle of writing an event). It's also possible that we may create files that are + * smaller than this limit. + */ + private final int preferredFileSizeMegabytes; + + /** + * When creating a new file, make sure that it has at least this much generational capacity for events after the + * first event written to the file. + */ + private final int minimumGenerationalCapacity; + + /** + * The minimum generation that we are required to keep around. + */ + private long minimumGenerationToStore; + + /** + * A running average of the generational span utilization in each file. Generational span utilization is defined as + * the difference between the highest generation of all events in the file and the minimum legal generation for that + * file. Higher generational utilization is always better, as it means that we have a lower un-utilized generational + * span. Un-utilized generational span is defined as the difference between the highest legal generation in a file + * and the highest actual generation of all events in the file. The reason why we want to minimize un-utilized + * generational span is to reduce the generational overlap between files, which in turn makes it faster to search + * for events with particular generations. The purpose of this running average is to intelligently choose the + * maximum generation for each new file to minimize un-utilized generational span while still meeting file size + * requirements. + */ + private final LongRunningAverage averageGenerationalSpanUtilization; + + /** + * The previous generational span. Set to a constant at bootstrap time. + */ + private long previousGenerationalSpan; + + /** + * If true then use {@link #bootstrapGenerationalSpanOverlapFactor} to compute the maximum generation for new files. + * If false then use {@link #generationalSpanOverlapFactor} to compute the maximum generation for new files. + * Bootstrap mode is used until we create the first file that exceeds the preferred file size. + */ + private boolean bootstrapMode = true; + + /** + * During bootstrap mode, multiply this value by the running average when deciding the generation span for a new + * file (i.e. the difference between the maximum and the minimum legal generation). + */ + private final double bootstrapGenerationalSpanOverlapFactor; + + /** + * When not in boostrap mode, multiply this value by the running average when deciding the generation span for a new + * file (i.e. the difference between the maximum and the minimum legal generation). + */ + private final double generationalSpanOverlapFactor; + + /** + * The highest event sequence number that has been written to the stream (but possibly not yet flushed). + */ + private long lastWrittenEvent = -1; + + /** + * If true then all added events are new and need to be written to the stream. If false then all added events + * are already durable and do not need to be written to the stream. + */ + private boolean streamingNewEvents = false; + + /** + * Constructor + * + * @param platformContext the platform context + * @param fileManager manages all preconsensus event stream files currently on disk + */ + public PcesWriter(@NonNull final PlatformContext platformContext, @NonNull final PcesFileManager fileManager) { + + Objects.requireNonNull(platformContext, "platformContext must not be null"); + Objects.requireNonNull(fileManager, "fileManager must not be null"); + + final PreconsensusEventStreamConfig config = + platformContext.getConfiguration().getConfigData(PreconsensusEventStreamConfig.class); + + preferredFileSizeMegabytes = config.preferredFileSizeMegabytes(); + + averageGenerationalSpanUtilization = + new LongRunningAverage(config.generationalUtilizationSpanRunningAverageLength()); + previousGenerationalSpan = config.bootstrapGenerationalSpan(); + bootstrapGenerationalSpanOverlapFactor = config.bootstrapGenerationalSpanOverlapFactor(); + generationalSpanOverlapFactor = config.generationalSpanOverlapFactor(); + minimumGenerationalCapacity = config.minimumGenerationalCapacity(); + + this.fileManager = fileManager; + } + + /** + * Prior to this method being called, all events added to the preconsensus event stream are assumed to be events + * read from the preconsensus event stream on disk. The events from the stream on disk are not re-written to the + * disk, and are considered to be durable immediately upon ingest. + * + * @param ignored empty trigger object, to indicate that events are done being streamed + */ + public void beginStreamingNewEvents(final @NonNull DoneStreamingPcesTrigger ignored) { + if (streamingNewEvents) { + logger.error(EXCEPTION.getMarker(), "beginStreamingNewEvents() called while already streaming new events"); + } + streamingNewEvents = true; + } + + /** + * Write an event to the stream. + * + * @param event the event to be written + * @return the sequence number of the last event durably written to the stream, or null if this method call didn't + * result in any additional events being durably written to the stream + */ + @Nullable + public Long writeEvent(@NonNull final GossipEvent event) { + validateSequenceNumber(event); + + if (!streamingNewEvents) { + lastWrittenEvent = event.getStreamSequenceNumber(); + return lastWrittenEvent; + } + + if (event.getGeneration() < minimumGenerationNonAncient) { + event.setStreamSequenceNumber(GossipEvent.STALE_EVENT_STREAM_SEQUENCE_NUMBER); + return null; + } + + try { + final Long latestDurableSequenceNumberUpdate = prepareOutputStream(event); + currentMutableFile.writeEvent(event); + lastWrittenEvent = event.getStreamSequenceNumber(); + + return latestDurableSequenceNumberUpdate; + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Inform the preconsensus event writer that a discontinuity has occurred in the preconsensus event stream. + * + * @param newOriginRound the round of the state that the new stream will be starting from + * @return the sequence number of the last event durably written to the stream, or null if this method call didn't + * result in any additional events being durably written to the stream + */ + @Nullable + public Long registerDiscontinuity(final long newOriginRound) { + if (!streamingNewEvents) { + logger.error(EXCEPTION.getMarker(), "registerDiscontinuity() called while replaying events"); + } + + final Long latestDurableSequenceNumberUpdate; + if (currentMutableFile != null) { + closeFile(); + latestDurableSequenceNumberUpdate = lastWrittenEvent; + } else { + latestDurableSequenceNumberUpdate = null; + } + + fileManager.registerDiscontinuity(newOriginRound); + + return latestDurableSequenceNumberUpdate; + } + + /** + * Make sure that the event has a valid stream sequence number. + */ + private static void validateSequenceNumber(@NonNull final GossipEvent event) { + if (event.getStreamSequenceNumber() == GossipEvent.NO_STREAM_SEQUENCE_NUMBER + || event.getStreamSequenceNumber() == GossipEvent.STALE_EVENT_STREAM_SEQUENCE_NUMBER) { + throw new IllegalStateException("Event must have a valid stream sequence number"); + } + } + + /** + * Let the event writer know the minimum generation for non-ancient events. Ancient events will be ignored if added + * to the event writer. + * + * @param minimumGenerationNonAncient the minimum generation of a non-ancient event + * @return the sequence number of the last event durably written to the stream if this method call resulted in any + * additional events being durably written to the stream, otherwise null + */ + @Nullable + public Long setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { + if (minimumGenerationNonAncient < this.minimumGenerationNonAncient) { + throw new IllegalArgumentException("Minimum generation non-ancient cannot be decreased. Current = " + + this.minimumGenerationNonAncient + ", requested = " + minimumGenerationNonAncient); + } + + this.minimumGenerationNonAncient = minimumGenerationNonAncient; + + if (!streamingNewEvents || currentMutableFile == null) { + return null; + } + + try { + currentMutableFile.flush(); + return lastWrittenEvent; + } catch (final IOException e) { + throw new UncheckedIOException("unable to flush", e); + } + } + + /** + * Set the minimum generation needed to be kept on disk. + * + * @param minimumGenerationToStore the minimum generation required to be stored on disk + */ + public void setMinimumGenerationToStore(final long minimumGenerationToStore) { + this.minimumGenerationToStore = minimumGenerationToStore; + pruneOldFiles(); + } + + /** + * Delete old files from the disk. + */ + private void pruneOldFiles() { + if (!streamingNewEvents) { + // Don't attempt to prune files until we are done replaying the event stream (at start up). + // Files are being iterated on a different thread, and it isn't thread safe to prune files + // while they are being iterated. + return; + } + + try { + fileManager.pruneOldFiles(minimumGenerationToStore); + } catch (final IOException e) { + throw new UncheckedIOException("unable to prune old files", e); + } + } + + /** + * Close the output file. + *

    + * Should only be called if {@link #currentMutableFile} is not null. + */ + private void closeFile() { + try { + previousGenerationalSpan = currentMutableFile.getUtilizedGenerationalSpan(); + if (!bootstrapMode) { + averageGenerationalSpanUtilization.add(previousGenerationalSpan); + } + currentMutableFile.close(); + + fileManager.finishedWritingFile(currentMutableFile); + currentMutableFile = null; + + // Not strictly required here, but not a bad place to ensure we delete + // files incrementally (as opposed to deleting a bunch of files all at once). + pruneOldFiles(); + } catch (final IOException e) { + throw new UncheckedIOException("unable to prune files", e); + } + } + + /** + * Calculate the generation span for a new file that is about to be created. + */ + private long computeNewFileSpan(final long minimumFileGeneration, final long nextGenerationToWrite) { + + final long basisSpan = (bootstrapMode || averageGenerationalSpanUtilization.isEmpty()) + ? previousGenerationalSpan + : averageGenerationalSpanUtilization.getAverage(); + + final double overlapFactor = + bootstrapMode ? bootstrapGenerationalSpanOverlapFactor : generationalSpanOverlapFactor; + + final long desiredSpan = (long) (basisSpan * overlapFactor); + + final long minimumSpan = (nextGenerationToWrite + minimumGenerationalCapacity) - minimumFileGeneration; + + return Math.max(desiredSpan, minimumSpan); + } + + /** + * Prepare the output stream for a particular event. May create a new file/stream if needed. + * + * @param eventToWrite the event that is about to be written + * @return the latest sequence number durably written to disk, if preparing the output stream caused a file to be + * closed. Otherwise, null. + */ + private Long prepareOutputStream(@NonNull final GossipEvent eventToWrite) throws IOException { + Long latestDurableSequenceNumberUpdate = null; + if (currentMutableFile != null) { + final boolean fileCanContainEvent = currentMutableFile.canContain(eventToWrite.getGeneration()); + final boolean fileIsFull = + UNIT_BYTES.convertTo(currentMutableFile.fileSize(), UNIT_MEGABYTES) >= preferredFileSizeMegabytes; + + if (!fileCanContainEvent || fileIsFull) { + closeFile(); + latestDurableSequenceNumberUpdate = lastWrittenEvent; + } + + if (fileIsFull) { + bootstrapMode = false; + } + } + + if (currentMutableFile == null) { + final long maximumGeneration = minimumGenerationNonAncient + + computeNewFileSpan(minimumGenerationNonAncient, eventToWrite.getGeneration()); + + currentMutableFile = fileManager + .getNextFileDescriptor(minimumGenerationNonAncient, maximumGeneration) + .getMutableFile(); + } + + return latestDurableSequenceNumberUpdate; + } + + /** + * Close the current mutable file. + */ + public void closeCurrentMutableFile() { + if (currentMutableFile != null) { + try { + currentMutableFile.close(); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventFileManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventFileManager.java index 63192eba0424..0b2231e69bfd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventFileManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventFileManager.java @@ -53,6 +53,8 @@ *

    * This object is not thread safe. *

    + * + * Future work: This class will be deleted once the PCES migration to the new framework is complete. */ public class PreconsensusEventFileManager { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventUtilities.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventUtilities.java index f580c1db2565..8c0a245808a8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventUtilities.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventUtilities.java @@ -35,6 +35,8 @@ /** * Utilities for preconsensus events. + *

    + * Future work: This class will be deleted once the PCES migration to the new framework is complete. */ public final class PreconsensusEventUtilities { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventWriter.java index ef04180e1b5d..7774bfabc17a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/PreconsensusEventWriter.java @@ -28,6 +28,8 @@ /** * An object capable of writing preconsensus events to disk. + *

    + * Future work: This class will be deleted once the PCES migration to the new framework is complete. */ public interface PreconsensusEventWriter extends Startable, Stoppable { Logger logger = LogManager.getLogger(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/SyncPreconsensusEventWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/SyncPreconsensusEventWriter.java index 2414d1e0d0a9..d87e029094d8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/SyncPreconsensusEventWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/preconsensus/SyncPreconsensusEventWriter.java @@ -36,6 +36,8 @@ /** * This object is responsible for writing events to the database. + *

    + * Future work: This class will be deleted once the PCES migration to the new framework is complete. */ public class SyncPreconsensusEventWriter implements PreconsensusEventWriter, Startable, Stoppable { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/DoneStreamingPcesTrigger.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/DoneStreamingPcesTrigger.java new file mode 100644 index 000000000000..2dba9d8ed0d4 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/DoneStreamingPcesTrigger.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.wiring; + +/** + * Trigger to indicate that the platform is done streaming events from the PCES + */ +public class DoneStreamingPcesTrigger {} diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java new file mode 100644 index 000000000000..fbe6f141df75 --- /dev/null +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java @@ -0,0 +1,610 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.test.event.preconsensus; + +import static com.swirlds.common.units.DataUnit.UNIT_BYTES; +import static com.swirlds.common.units.DataUnit.UNIT_KILOBYTES; +import static com.swirlds.common.utility.CompareTo.isGreaterThanOrEqualTo; +import static com.swirlds.platform.event.preconsensus.PcesFileManager.NO_MINIMUM_GENERATION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.swirlds.base.test.fixtures.time.FakeTime; +import com.swirlds.common.config.TransactionConfig_; +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.context.DefaultPlatformContext; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.CryptographyHolder; +import com.swirlds.common.io.IOIterator; +import com.swirlds.common.io.utility.FileUtils; +import com.swirlds.common.metrics.Metrics; +import com.swirlds.common.metrics.noop.NoOpMetrics; +import com.swirlds.common.platform.NodeId; +import com.swirlds.common.test.fixtures.RandomUtils; +import com.swirlds.common.test.fixtures.TestRecycleBin; +import com.swirlds.common.test.fixtures.TransactionGenerator; +import com.swirlds.common.test.fixtures.io.FileManipulation; +import com.swirlds.config.api.Configuration; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.event.preconsensus.EventDurabilityNexus; +import com.swirlds.platform.event.preconsensus.PcesFileManager; +import com.swirlds.platform.event.preconsensus.PcesFileReader; +import com.swirlds.platform.event.preconsensus.PcesFileTracker; +import com.swirlds.platform.event.preconsensus.PcesUtilities; +import com.swirlds.platform.event.preconsensus.PcesWriter; +import com.swirlds.platform.event.preconsensus.PreconsensusEventFile; +import com.swirlds.platform.event.preconsensus.PreconsensusEventMultiFileIterator; +import com.swirlds.platform.event.preconsensus.PreconsensusEventStreamConfig_; +import com.swirlds.platform.event.preconsensus.PreconsensusEventStreamSequencer; +import com.swirlds.platform.system.transaction.ConsensusTransactionImpl; +import com.swirlds.platform.system.transaction.SwirldTransaction; +import com.swirlds.platform.test.fixtures.event.generator.StandardGraphGenerator; +import com.swirlds.platform.test.fixtures.event.source.StandardEventSource; +import com.swirlds.platform.wiring.DoneStreamingPcesTrigger; +import com.swirlds.test.framework.config.TestConfigBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.Set; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@DisplayName("PcesWriter Tests") +class PcesWriterTests { + + /** + * Temporary directory provided by JUnit + */ + @TempDir + Path testDirectory; + + private FakeTime time; + private final NodeId selfId = new NodeId(0); + private final int numEvents = 1_000; + private PlatformContext platformContext; + private StandardGraphGenerator generator; + private int generationsUntilAncient; + private PreconsensusEventStreamSequencer sequencer; + private PcesFileTracker pcesFiles; + private PcesWriter writer; + private EventDurabilityNexus eventDurabilityNexus; + + /** + * Perform verification on a stream written by a {@link PcesWriter}. + * + * @param events the events that were written to the stream + * @param platformContext the platform context + * @param truncatedFileCount the expected number of truncated files + */ + private void verifyStream( + @NonNull final List events, + @NonNull final PlatformContext platformContext, + final int truncatedFileCount) + throws IOException { + + long lastGeneration = Long.MIN_VALUE; + for (final GossipEvent event : events) { + lastGeneration = Math.max(lastGeneration, event.getGeneration()); + } + + final PcesFileTracker pcesFiles = PcesFileReader.readFilesFromDisk( + platformContext, + TestRecycleBin.getInstance(), + PcesUtilities.getDatabaseDirectory(platformContext, selfId), + 0, + false); + + // Verify that the events were written correctly + final PreconsensusEventMultiFileIterator eventsIterator = pcesFiles.getEventIterator(0, 0); + for (final GossipEvent event : events) { + assertTrue(eventsIterator.hasNext()); + assertEquals(event, eventsIterator.next()); + } + + assertFalse(eventsIterator.hasNext()); + assertEquals(truncatedFileCount, eventsIterator.getTruncatedFileCount()); + + // Make sure things look good when iterating starting in the middle of the stream that was written + final long startingGeneration = lastGeneration / 2; + final IOIterator eventsIterator2 = pcesFiles.getEventIterator(startingGeneration, 0); + for (final GossipEvent event : events) { + if (event.getGeneration() < startingGeneration) { + continue; + } + assertTrue(eventsIterator2.hasNext()); + assertEquals(event, eventsIterator2.next()); + } + assertFalse(eventsIterator2.hasNext()); + + // Iterating from a high generation should yield no events + final IOIterator eventsIterator3 = pcesFiles.getEventIterator(lastGeneration + 1, 0); + assertFalse(eventsIterator3.hasNext()); + + // Do basic validation on event files + final List files = new ArrayList<>(); + pcesFiles.getFileIterator(0, 0).forEachRemaining(files::add); + + // There should be at least 2 files. + // Certainly many more, but getting the heuristic right on this is non-trivial. + assertTrue(files.size() >= 2); + + // Sanity check each individual file + int nextSequenceNumber = 0; + Instant previousTimestamp = Instant.MIN; + long previousMinimum = Long.MIN_VALUE; + long previousMaximum = Long.MIN_VALUE; + for (final PreconsensusEventFile file : files) { + assertEquals(nextSequenceNumber, file.getSequenceNumber()); + nextSequenceNumber++; + assertTrue(isGreaterThanOrEqualTo(file.getTimestamp(), previousTimestamp)); + previousTimestamp = file.getTimestamp(); + assertTrue(file.getMinimumGeneration() <= file.getMaximumGeneration()); + assertTrue(file.getMinimumGeneration() >= previousMinimum); + previousMinimum = file.getMinimumGeneration(); + assertTrue(file.getMaximumGeneration() >= previousMaximum); + previousMaximum = file.getMaximumGeneration(); + + final IOIterator fileEvents = file.iterator(0); + while (fileEvents.hasNext()) { + final GossipEvent event = fileEvents.next(); + assertTrue(event.getGeneration() >= file.getMinimumGeneration()); + assertTrue(event.getGeneration() <= file.getMaximumGeneration()); + } + } + } + + /** + * Build a transaction generator. + */ + private static TransactionGenerator buildTransactionGenerator() { + + final int transactionCount = 10; + final int averageTransactionSizeInKb = 10; + final int transactionSizeStandardDeviationInKb = 5; + + return (final Random random) -> { + final ConsensusTransactionImpl[] transactions = new ConsensusTransactionImpl[transactionCount]; + for (int index = 0; index < transactionCount; index++) { + + final int transactionSize = (int) UNIT_KILOBYTES.convertTo( + Math.max( + 1, + averageTransactionSizeInKb + + random.nextDouble() * transactionSizeStandardDeviationInKb), + UNIT_BYTES); + final byte[] bytes = new byte[transactionSize]; + random.nextBytes(bytes); + + transactions[index] = new SwirldTransaction(bytes); + } + return transactions; + }; + } + + /** + * Build an event generator. + */ + static StandardGraphGenerator buildGraphGenerator(final Random random) { + final TransactionGenerator transactionGenerator = buildTransactionGenerator(); + + return new StandardGraphGenerator( + random.nextLong(), + new StandardEventSource().setTransactionGenerator(transactionGenerator), + new StandardEventSource().setTransactionGenerator(transactionGenerator), + new StandardEventSource().setTransactionGenerator(transactionGenerator), + new StandardEventSource().setTransactionGenerator(transactionGenerator)); + } + + @BeforeAll + static void beforeAll() throws ConstructableRegistryException { + ConstructableRegistry.getInstance().registerConstructables(""); + } + + @BeforeEach + void beforeEach() throws IOException { + FileUtils.deleteDirectory(testDirectory); + Files.createDirectories(testDirectory); + + platformContext = buildContext(); + + final Random random = RandomUtils.getRandomPrintSeed(); + generator = buildGraphGenerator(random); + generationsUntilAncient = random.nextInt(50, 100); + sequencer = new PreconsensusEventStreamSequencer(); + pcesFiles = new PcesFileTracker(); + + time = new FakeTime(Duration.ofMillis(1)); + final PcesFileManager fileManager = new PcesFileManager(platformContext, time, pcesFiles, selfId, 0); + writer = new PcesWriter(platformContext, fileManager); + eventDurabilityNexus = new EventDurabilityNexus(); + } + + @AfterEach + void afterEach() throws IOException { + FileUtils.deleteDirectory(testDirectory); + } + + private PlatformContext buildContext() { + final Configuration configuration = new TestConfigBuilder() + .withValue(PreconsensusEventStreamConfig_.DATABASE_DIRECTORY, testDirectory) + .withValue(PreconsensusEventStreamConfig_.PREFERRED_FILE_SIZE_MEGABYTES, 5) + .withValue(TransactionConfig_.MAX_TRANSACTION_BYTES_PER_EVENT, Integer.MAX_VALUE) + .withValue(TransactionConfig_.MAX_TRANSACTION_COUNT_PER_EVENT, Integer.MAX_VALUE) + .withValue(TransactionConfig_.TRANSACTION_MAX_BYTES, Integer.MAX_VALUE) + .withValue(TransactionConfig_.MAX_ADDRESS_SIZE_ALLOWED, Integer.MAX_VALUE) + .getOrCreateConfig(); + + final Metrics metrics = new NoOpMetrics(); + + return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get()); + } + + /** + * Pass the most recent durable sequence number to the durability nexus. + *

    + * The intention of this method is to simply pass the return value from any writer call that returns a sequence + * number. This simulates the components being wired together. + * + * @param mostRecentDurableSequenceNumber the most recent durable sequence number + */ + private void passValueToDurabilityNexus(@Nullable final Long mostRecentDurableSequenceNumber) { + if (mostRecentDurableSequenceNumber != null) { + eventDurabilityNexus.setLatestDurableSequenceNumber(mostRecentDurableSequenceNumber); + } + } + + @Test + @DisplayName("Standard Operation Test") + void standardOperationTest() throws IOException { + final List events = new LinkedList<>(); + for (int i = 0; i < numEvents; i++) { + events.add(generator.generateEvent().getBaseEvent()); + } + + writer.beginStreamingNewEvents(new DoneStreamingPcesTrigger()); + + final Collection rejectedEvents = new HashSet<>(); + + long minimumGenerationNonAncient = 0; + final Iterator iterator = events.iterator(); + while (iterator.hasNext()) { + final GossipEvent event = iterator.next(); + + sequencer.assignStreamSequenceNumber(event); + passValueToDurabilityNexus(writer.writeEvent(event)); + + minimumGenerationNonAncient = + Math.max(minimumGenerationNonAncient, event.getGeneration() - generationsUntilAncient); + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + + if (event.getGeneration() < minimumGenerationNonAncient) { + // Although it's not common, it's possible that the generator will generate + // an event that is ancient (since it isn't aware of what we consider to be ancient) + rejectedEvents.add(event); + iterator.remove(); + } + } + + events.forEach(event -> assertTrue(eventDurabilityNexus.isEventDurable(event))); + rejectedEvents.forEach(event -> assertFalse(eventDurabilityNexus.isEventDurable(event))); + + verifyStream(events, platformContext, 0); + + writer.closeCurrentMutableFile(); + } + + @Test + @DisplayName("Ancient Event Test") + void ancientEventTest() throws IOException { + // We will add this event at the very end, it should be ancient by then + final GossipEvent ancientEvent = generator.generateEvent().getBaseEvent(); + + final List events = new LinkedList<>(); + for (int i = 0; i < numEvents; i++) { + events.add(generator.generateEvent().getBaseEvent()); + } + + writer.beginStreamingNewEvents(new DoneStreamingPcesTrigger()); + + final Collection rejectedEvents = new HashSet<>(); + + long minimumGenerationNonAncient = 0; + final Iterator iterator = events.iterator(); + while (iterator.hasNext()) { + final GossipEvent event = iterator.next(); + + sequencer.assignStreamSequenceNumber(event); + passValueToDurabilityNexus(writer.writeEvent(event)); + + minimumGenerationNonAncient = + Math.max(minimumGenerationNonAncient, event.getGeneration() - generationsUntilAncient); + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + + if (event.getGeneration() < minimumGenerationNonAncient) { + // Although it's not common, it's actually possible that the generator will generate + // an event that is ancient (since it isn't aware of what we consider to be ancient) + rejectedEvents.add(event); + iterator.remove(); + } + } + + // Add the ancient event + sequencer.assignStreamSequenceNumber(ancientEvent); + if (minimumGenerationNonAncient > ancientEvent.getGeneration()) { + // This is probably not possible... but just in case make sure this event is ancient + try { + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(ancientEvent.getGeneration() + 1)); + } catch (final IllegalArgumentException e) { + // ignore, more likely than not this event is way older than the actual ancient generation + } + } + + passValueToDurabilityNexus(writer.writeEvent(ancientEvent)); + rejectedEvents.add(ancientEvent); + assertEquals(GossipEvent.STALE_EVENT_STREAM_SEQUENCE_NUMBER, ancientEvent.getStreamSequenceNumber()); + + events.forEach(event -> assertTrue(eventDurabilityNexus.isEventDurable(event))); + rejectedEvents.forEach(event -> assertFalse(eventDurabilityNexus.isEventDurable(event))); + + verifyStream(events, platformContext, 0); + + writer.closeCurrentMutableFile(); + } + + /** + * In this test, keep adding events without increasing the first non-ancient generation. This will force the + * preferred generations per file to eventually be reached and exceeded. + */ + @Test + @DisplayName("Overflow Test") + void overflowTest() throws IOException { + final List events = new LinkedList<>(); + for (int i = 0; i < numEvents; i++) { + events.add(generator.generateEvent().getBaseEvent()); + } + + writer.beginStreamingNewEvents(new DoneStreamingPcesTrigger()); + + for (final GossipEvent event : events) { + sequencer.assignStreamSequenceNumber(event); + passValueToDurabilityNexus(writer.writeEvent(event)); + } + + writer.closeCurrentMutableFile(); + + verifyStream(events, platformContext, 0); + + // Without advancing the first non-ancient generation, + // we should never be able to increase the minimum generation from 0. + for (final Iterator it = pcesFiles.getFileIterator(0, 0); it.hasNext(); ) { + final PreconsensusEventFile file = it.next(); + assertEquals(0, file.getMinimumGeneration()); + } + } + + @Test + @DisplayName("beginStreamingEvents() Test") + void beginStreamingEventsTest() { + final List events = new LinkedList<>(); + for (int i = 0; i < numEvents; i++) { + events.add(generator.generateEvent().getBaseEvent()); + } + + // We intentionally do not call writer.beginStreamingNewEvents(). This should cause all events + // passed into the writer to be more or less ignored. + + long minimumGenerationNonAncient = 0; + for (final GossipEvent event : events) { + sequencer.assignStreamSequenceNumber(event); + passValueToDurabilityNexus(writer.writeEvent(event)); + + minimumGenerationNonAncient = + Math.max(minimumGenerationNonAncient, event.getGeneration() - generationsUntilAncient); + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + } + + assertTrue(eventDurabilityNexus.isEventDurable(events.get(events.size() - 1))); + + // We shouldn't find any events in the stream. + assertFalse(() -> pcesFiles + .getFileIterator(PcesFileManager.NO_MINIMUM_GENERATION, 0) + .hasNext()); + + writer.closeCurrentMutableFile(); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisplayName("Discontinuity Test") + void discontinuityTest(final boolean truncateLastFile) throws IOException { + final List eventsBeforeDiscontinuity = new LinkedList<>(); + final List eventsAfterDiscontinuity = new LinkedList<>(); + for (int i = 0; i < numEvents; i++) { + final GossipEvent event = generator.generateEvent().getBaseEvent(); + if (i < numEvents / 2) { + eventsBeforeDiscontinuity.add(event); + } else { + eventsAfterDiscontinuity.add(event); + } + } + + writer.beginStreamingNewEvents(new DoneStreamingPcesTrigger()); + + final Collection rejectedEvents = new HashSet<>(); + + long minimumGenerationNonAncient = 0; + final Iterator iterator1 = eventsBeforeDiscontinuity.iterator(); + while (iterator1.hasNext()) { + final GossipEvent event = iterator1.next(); + + sequencer.assignStreamSequenceNumber(event); + passValueToDurabilityNexus(writer.writeEvent(event)); + + minimumGenerationNonAncient = + Math.max(minimumGenerationNonAncient, event.getGeneration() - generationsUntilAncient); + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + + if (event.getGeneration() < minimumGenerationNonAncient) { + // Although it's not common, it's actually possible that the generator will generate + // an event that is ancient (since it isn't aware of what we consider to be ancient) + rejectedEvents.add(event); + iterator1.remove(); + } + } + + eventsBeforeDiscontinuity.forEach(event -> assertTrue(eventDurabilityNexus.isEventDurable(event))); + + passValueToDurabilityNexus(writer.registerDiscontinuity(100)); + + if (truncateLastFile) { + // Remove a single byte from the last file. This will corrupt the last event that was written. + final Iterator it = pcesFiles.getFileIterator(NO_MINIMUM_GENERATION, 0); + while (it.hasNext()) { + final PreconsensusEventFile file = it.next(); + if (!it.hasNext()) { + FileManipulation.truncateNBytesFromFile(file.getPath(), 1); + } + } + + eventsBeforeDiscontinuity.remove(eventsBeforeDiscontinuity.size() - 1); + } + + final Iterator iterator2 = eventsAfterDiscontinuity.iterator(); + while (iterator2.hasNext()) { + final GossipEvent event = iterator2.next(); + + sequencer.assignStreamSequenceNumber(event); + passValueToDurabilityNexus(writer.writeEvent(event)); + + minimumGenerationNonAncient = + Math.max(minimumGenerationNonAncient, event.getGeneration() - generationsUntilAncient); + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + + if (event.getGeneration() < minimumGenerationNonAncient) { + // Although it's not common, it's actually possible that the generator will generate + // an event that is ancient (since it isn't aware of what we consider to be ancient) + rejectedEvents.add(event); + iterator2.remove(); + } + } + + assertTrue( + eventDurabilityNexus.isEventDurable(eventsAfterDiscontinuity.get(eventsAfterDiscontinuity.size() - 1))); + eventsAfterDiscontinuity.forEach(event -> assertTrue(eventDurabilityNexus.isEventDurable(event))); + rejectedEvents.forEach(event -> assertFalse(eventDurabilityNexus.isEventDurable(event))); + + verifyStream(eventsBeforeDiscontinuity, platformContext, truncateLastFile ? 1 : 0); + + writer.closeCurrentMutableFile(); + } + + /** + * In this test, increase the first non-ancient generation as events are added. When this happens, we + * should never have to include more than the preferred number of events in each file. + */ + @Test + @DisplayName("Advance Non Ancient Generation Test") + void advanceNonAncientGenerationTest() throws IOException { + final List events = new LinkedList<>(); + for (int i = 0; i < numEvents; i++) { + events.add(generator.generateEvent().getBaseEvent()); + } + + writer.beginStreamingNewEvents(new DoneStreamingPcesTrigger()); + + final Set rejectedEvents = new HashSet<>(); + + long minimumGenerationNonAncient = 0; + for (final GossipEvent event : events) { + sequencer.assignStreamSequenceNumber(event); + + passValueToDurabilityNexus(writer.writeEvent(event)); + assertFalse(eventDurabilityNexus.isEventDurable(event)); + + time.tick(Duration.ofSeconds(1)); + + if (event.getGeneration() < minimumGenerationNonAncient) { + // This event is ancient and will have been rejected. + rejectedEvents.add(event); + } + + minimumGenerationNonAncient = + Math.max(minimumGenerationNonAncient, event.getGeneration() - generationsUntilAncient); + passValueToDurabilityNexus(writer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + } + + // Remove the rejected events from the list + events.removeIf(rejectedEvents::contains); + + events.forEach(event -> assertTrue(eventDurabilityNexus.isEventDurable(event))); + rejectedEvents.forEach(event -> assertFalse(eventDurabilityNexus.isEventDurable(event))); + verifyStream(events, platformContext, 0); + + // Advance the time so that all files are GC eligible according to the clock. + time.tick(Duration.ofDays(1)); + + // Prune old files. + final long minimumGenerationToStore = events.get(events.size() - 1).getGeneration() / 2; + writer.setMinimumGenerationToStore(minimumGenerationToStore); + + // We shouldn't see any files that are incapable of storing events above the minimum + final PcesFileTracker pcesFiles = PcesFileReader.readFilesFromDisk( + platformContext, + TestRecycleBin.getInstance(), + PcesUtilities.getDatabaseDirectory(platformContext, selfId), + 0, + false); + + pcesFiles + .getFileIterator(NO_MINIMUM_GENERATION, 0) + .forEachRemaining(file -> assertTrue(file.getMaximumGeneration() >= minimumGenerationToStore)); + + writer.closeCurrentMutableFile(); + + // Since we were very careful to always advance the first non-ancient generation, we should + // find lots of files with a minimum generation exceeding 0. + boolean foundNonZeroMinimumGeneration = false; + for (final Iterator fileIterator = pcesFiles.getFileIterator(0, 0); + fileIterator.hasNext(); ) { + final PreconsensusEventFile file = fileIterator.next(); + if (file.getMinimumGeneration() > 0) { + foundNonZeroMinimumGeneration = true; + break; + } + } + assertTrue(foundNonZeroMinimumGeneration); + } +} From 0f965a48f30e073c53bfa21ba05be6eb1982290c Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:42:03 -0600 Subject: [PATCH 35/80] chore: update gradle to support Java 21 (#10513) Signed-off-by: Jeffrey Tang Signed-off-by: Nathan Klick Co-authored-by: Nathan Klick --- .../node-flow-build-application.yaml | 2 +- .../node-zxc-compile-application-code.yaml | 2 +- .../platform-flow-deploy-adhoc-release.yaml | 2 +- .../platform-flow-deploy-ea-release.yaml | 2 +- .../platform-flow-deploy-mc-release.yaml | 2 +- .../platform-zxc-release-internal.yaml | 2 +- .../platform-zxc-release-maven-central.yaml | 2 +- .github/workflows/zxc-jrs-regression.yaml | 2 +- build-logic/project-plugins/build.gradle.kts | 2 +- .../com.hedera.hashgraph.java.gradle.kts | 5 +- ...m.hedera.hashgraph.jpms-modules.gradle.kts | 4 - build-logic/settings-plugins/build.gradle.kts | 5 +- ...era.hashgraph.settings.settings.gradle.kts | 5 +- hedera-dependency-versions/build.gradle.kts | 5 +- hedera-node/cli-clients/build.gradle.kts | 1 - .../build.gradle.kts | 1 - hedera-node/hedera-evm/build.gradle.kts | 1 - .../hedera-file-service-impl/build.gradle.kts | 1 - .../mono/state/codec/DataBufferTest.java | 67 +++++++-------- .../virtual/ContractKeySerializerTest.java | 83 ++++++++++--------- .../mono/state/virtual/ContractKeyTest.java | 34 ++++---- .../mono/state/virtual/ContractValueTest.java | 28 ++----- .../state/virtual/EntityNumValueTest.java | 15 +++- .../EntityNumVirtualKeySerializerTest.java | 26 +++--- .../virtual/EntityNumVirtualKeyTest.java | 17 ++-- .../virtual/IterableContractValueTest.java | 40 ++++----- .../virtual/VirtualBlobKeySerializerTest.java | 25 +++--- .../state/virtual/VirtualBlobKeyTest.java | 18 ++-- .../state/virtual/VirtualBlobValueTest.java | 41 +++++---- ...eduleEqualityVirtualKeySerializerTest.java | 27 +++--- .../ScheduleEqualityVirtualKeyTest.java | 15 ++-- .../ScheduleEqualityVirtualValueTest.java | 45 +++++----- .../ScheduleSecondVirtualValueTest.java | 52 +++++++----- ...condSinceEpocVirtualKeySerializerTest.java | 20 ++--- .../SecondSinceEpocVirtualKeyTest.java | 15 +--- .../build.gradle.kts | 1 - .../build.gradle.kts | 1 - .../build.gradle.kts | 1 - .../build.gradle.kts | 1 - .../swirlds-jasperdb/build.gradle.kts | 1 - .../VirtualHashRecordSerializerTest.java | 13 ++- .../platform/test/system/SystemUtilsTest.java | 2 + 42 files changed, 322 insertions(+), 312 deletions(-) diff --git a/.github/workflows/node-flow-build-application.yaml b/.github/workflows/node-flow-build-application.yaml index 827d256ac531..d1ee12d89c71 100644 --- a/.github/workflows/node-flow-build-application.yaml +++ b/.github/workflows/node-flow-build-application.yaml @@ -57,7 +57,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string diff --git a/.github/workflows/node-zxc-compile-application-code.yaml b/.github/workflows/node-zxc-compile-application-code.yaml index 9f6ecd463ee1..640b5c5bd8d3 100644 --- a/.github/workflows/node-zxc-compile-application-code.yaml +++ b/.github/workflows/node-zxc-compile-application-code.yaml @@ -92,7 +92,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" node-version: description: "NodeJS Version:" type: string diff --git a/.github/workflows/platform-flow-deploy-adhoc-release.yaml b/.github/workflows/platform-flow-deploy-adhoc-release.yaml index 54356c6a8e94..10ef4c813d03 100644 --- a/.github/workflows/platform-flow-deploy-adhoc-release.yaml +++ b/.github/workflows/platform-flow-deploy-adhoc-release.yaml @@ -27,7 +27,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string diff --git a/.github/workflows/platform-flow-deploy-ea-release.yaml b/.github/workflows/platform-flow-deploy-ea-release.yaml index 90da8db6558e..06bb86388552 100644 --- a/.github/workflows/platform-flow-deploy-ea-release.yaml +++ b/.github/workflows/platform-flow-deploy-ea-release.yaml @@ -46,7 +46,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string diff --git a/.github/workflows/platform-flow-deploy-mc-release.yaml b/.github/workflows/platform-flow-deploy-mc-release.yaml index d0878d8a6ad5..d4e79023315d 100644 --- a/.github/workflows/platform-flow-deploy-mc-release.yaml +++ b/.github/workflows/platform-flow-deploy-mc-release.yaml @@ -31,7 +31,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string diff --git a/.github/workflows/platform-zxc-release-internal.yaml b/.github/workflows/platform-zxc-release-internal.yaml index fc2b3528308d..fbd752c65355 100644 --- a/.github/workflows/platform-zxc-release-internal.yaml +++ b/.github/workflows/platform-zxc-release-internal.yaml @@ -44,7 +44,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" gradle-version: description: "Gradle Version:" type: string diff --git a/.github/workflows/platform-zxc-release-maven-central.yaml b/.github/workflows/platform-zxc-release-maven-central.yaml index c302250d40b2..6960d28d0a29 100644 --- a/.github/workflows/platform-zxc-release-maven-central.yaml +++ b/.github/workflows/platform-zxc-release-maven-central.yaml @@ -36,7 +36,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.3" + default: "21.0.1" gradle-version: description: "Gradle Version:" type: string diff --git a/.github/workflows/zxc-jrs-regression.yaml b/.github/workflows/zxc-jrs-regression.yaml index 798dd0f33215..3cc157527502 100644 --- a/.github/workflows/zxc-jrs-regression.yaml +++ b/.github/workflows/zxc-jrs-regression.yaml @@ -82,7 +82,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17" + default: "21.0.1" gradle-version: description: "Gradle Version:" type: string diff --git a/build-logic/project-plugins/build.gradle.kts b/build-logic/project-plugins/build.gradle.kts index 9346e7c1f730..6a493352ef2b 100644 --- a/build-logic/project-plugins/build.gradle.kts +++ b/build-logic/project-plugins/build.gradle.kts @@ -23,7 +23,7 @@ plugins { dependencies { implementation("com.adarshr:gradle-test-logger-plugin:4.0.0") implementation("com.autonomousapps:dependency-analysis-gradle-plugin:1.26.0") - implementation("com.diffplug.spotless:spotless-plugin-gradle:6.22.0") + implementation("com.diffplug.spotless:spotless-plugin-gradle:6.23.3") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("com.google.protobuf:protobuf-gradle-plugin:0.9.4") implementation( diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts b/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts index 5311f8aff7f5..b981a45f30e6 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts +++ b/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.java.gradle.kts @@ -35,8 +35,11 @@ version = providers.fileContents(rootProject.layout.projectDirectory.versionTxt()).asText.get().trim() java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(21)) vendor.set(JvmVendorSpec.ADOPTIUM) } } diff --git a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts b/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts index 52abe5f66f1b..724f792da761 100644 --- a/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts +++ b/build-logic/project-plugins/src/main/kotlin/com.hedera.hashgraph.jpms-modules.gradle.kts @@ -367,10 +367,6 @@ extraJavaModuleInfo { exportAllPackages() requireAllDefinedDependencies() } - module("org.mockito:mockito-inline", "org.mockito.inline") { - exportAllPackages() - requireAllDefinedDependencies() - } module("uk.org.webcompere:system-stubs-core", "uk.org.webcompere.systemstubs.core") { exportAllPackages() requireAllDefinedDependencies() diff --git a/build-logic/settings-plugins/build.gradle.kts b/build-logic/settings-plugins/build.gradle.kts index fcdc40f5aea7..486e0aa19f41 100644 --- a/build-logic/settings-plugins/build.gradle.kts +++ b/build-logic/settings-plugins/build.gradle.kts @@ -16,4 +16,7 @@ plugins { `kotlin-dsl` } -dependencies { implementation("com.gradle:gradle-enterprise-gradle-plugin:3.15.1") } +dependencies { + implementation("org.gradle.toolchains:foojay-resolver:0.7.0") + implementation("com.gradle:gradle-enterprise-gradle-plugin:3.15.1") +} diff --git a/build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts b/build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts index a6c46c8efd5c..7c939ce73e63 100644 --- a/build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts +++ b/build-logic/settings-plugins/src/main/kotlin/com.hedera.hashgraph.settings.settings.gradle.kts @@ -22,7 +22,10 @@ pluginManagement { } } -plugins { id("com.gradle.enterprise") } +plugins { + id("com.gradle.enterprise") + id("org.gradle.toolchains.foojay-resolver-convention") +} // Enable Gradle Build Scan gradleEnterprise { diff --git a/hedera-dependency-versions/build.gradle.kts b/hedera-dependency-versions/build.gradle.kts index 8cce0e50e570..eeb856ef47fb 100644 --- a/hedera-dependency-versions/build.gradle.kts +++ b/hedera-dependency-versions/build.gradle.kts @@ -27,11 +27,11 @@ val grpcVersion = "1.54.1" val helidonVersion = "3.2.1" val jacksonVersion = "2.16.0" val log4jVersion = "2.21.1" -val mockitoVersion = "4.11.0" +val mockitoVersion = "5.8.0" val nettyVersion = "4.1.87.Final" val prometheusVersion = "0.16.0" val protobufVersion = "3.21.7" -val systemStubsVersion = "2.0.2" +val systemStubsVersion = "2.1.5" val testContainersVersion = "1.17.2" val tuweniVersion = "2.4.2" @@ -101,7 +101,6 @@ moduleInfo { version("org.junit.platform.engine", "1.9.1") version("org.junitpioneer", "2.0.1") version("org.mockito", mockitoVersion) - version("org.mockito.inline", mockitoVersion) version("org.mockito.junit.jupiter", mockitoVersion) version("org.opentest4j", "1.2.0") version("org.testcontainers", testContainersVersion) diff --git a/hedera-node/cli-clients/build.gradle.kts b/hedera-node/cli-clients/build.gradle.kts index c48819f7ae0a..22d0241eec58 100644 --- a/hedera-node/cli-clients/build.gradle.kts +++ b/hedera-node/cli-clients/build.gradle.kts @@ -25,7 +25,6 @@ testModuleInfo { requires("org.junit.jupiter.api") requires("org.mockito") requires("org.mockito.junit.jupiter") - runtimeOnly("org.mockito.inline") } tasks.shadowJar { diff --git a/hedera-node/hedera-consensus-service-impl/build.gradle.kts b/hedera-node/hedera-consensus-service-impl/build.gradle.kts index d4ea88dd5776..fb51be4ce79a 100644 --- a/hedera-node/hedera-consensus-service-impl/build.gradle.kts +++ b/hedera-node/hedera-consensus-service-impl/build.gradle.kts @@ -36,5 +36,4 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") - runtimeOnly("org.mockito.inline") } diff --git a/hedera-node/hedera-evm/build.gradle.kts b/hedera-node/hedera-evm/build.gradle.kts index b53efb66ac59..33c84875c04b 100644 --- a/hedera-node/hedera-evm/build.gradle.kts +++ b/hedera-node/hedera-evm/build.gradle.kts @@ -29,7 +29,6 @@ testModuleInfo { requires("org.junit.jupiter.api") requires("org.mockito") requires("org.mockito.junit.jupiter") - runtimeOnly("org.mockito.inline") } publishing { diff --git a/hedera-node/hedera-file-service-impl/build.gradle.kts b/hedera-node/hedera-file-service-impl/build.gradle.kts index dd228b854f50..5826f1b222d7 100644 --- a/hedera-node/hedera-file-service-impl/build.gradle.kts +++ b/hedera-node/hedera-file-service-impl/build.gradle.kts @@ -34,5 +34,4 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") - runtimeOnly("org.mockito.inline") } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/codec/DataBufferTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/codec/DataBufferTest.java index 206ff809c8b6..59155d18b059 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/codec/DataBufferTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/codec/DataBufferTest.java @@ -21,31 +21,15 @@ import com.hedera.pbj.runtime.io.buffer.BufferedData; import java.nio.ByteBuffer; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(MockitoExtension.class) class DataBufferTest { - @Mock - private ByteBuffer buffer; - - private BufferedData subject; - - @BeforeEach - void setUp() { - subject = BufferedData.wrap(buffer); - } @Test void readFullyDelegates() { final var b = new byte[] {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05}; - subject = BufferedData.wrap(ByteBuffer.wrap(b)); + final var subject = BufferedData.wrap(ByteBuffer.wrap(b)); final var bRead = new byte[b.length]; subject.readBytes(bRead); assertArrayEquals(b, bRead); @@ -53,19 +37,20 @@ void readFullyDelegates() { @Test void skipBytes() { + final var subject = BufferedData.wrap(ByteBuffer.wrap(new byte[] {})); final var skipped = subject.skip(32L); assertEquals(0, skipped); } @Test void canReadThings() { - buffer = ByteBuffer.wrap(new byte[] { + final var buffer = ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, }); - subject = BufferedData.wrap(buffer); + final var subject = BufferedData.wrap(buffer); // assertTrue(subject.readBoolean()); subject.readByte(); @@ -84,8 +69,8 @@ void canReadThings() { @Test @Disabled("Enable once reading boolean is supported") void canReadFalse() { - buffer = ByteBuffer.wrap(new byte[] {0}); - subject = BufferedData.wrap(buffer); + final var buffer = ByteBuffer.wrap(new byte[] {0}); + final var subject = BufferedData.wrap(buffer); // assertFalse(subject.readBoolean()); } @@ -106,34 +91,38 @@ void returnsDelegate() { @Test void delegatesWrites() { final var bytes = new byte[] {1, 2, 3, 4, 5}; - - InOrder inOrder = Mockito.inOrder(buffer); + final var buffer = ByteBuffer.allocate(40); + final var subject = BufferedData.wrap(buffer); subject.writeByte((byte) 12); subject.writeBytes(bytes); subject.writeBytes(bytes, 1, 3); - // subject.writeBoolean(true); - // subject.writeBoolean(false); subject.writeByte((byte) 42); - // subject.writeShort(13); - // subject.writeChar(14); subject.writeInt(15); subject.writeLong(16L); subject.writeFloat(17.0f); subject.writeDouble(18.0); - inOrder.verify(buffer).put((byte) 12); - inOrder.verify(buffer).put(bytes); - inOrder.verify(buffer).put(bytes, 1, 3); - // inOrder.verify(buffer).put((byte) 1); - // inOrder.verify(buffer).put((byte) 0); - inOrder.verify(buffer).put((byte) 42); - // inOrder.verify(buffer).putShort((short) 13); - // inOrder.verify(buffer).putChar((char) 14); - inOrder.verify(buffer).putInt(15); - inOrder.verify(buffer).putLong(16L); - inOrder.verify(buffer).putFloat(17.0f); - inOrder.verify(buffer).putDouble(18.0); + assertArrayEquals( + new byte[] { + 12, 1, 2, 3, 4, 5, 2, 3, 4, 42, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 16, 65, -120, 0, 0, 64, 50, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }, + buffer.array()); + + buffer.rewind(); + assertEquals(12, buffer.get()); + final var bytes5Read = new byte[5]; + buffer.get(bytes5Read); + assertArrayEquals(bytes, bytes5Read); + final var bytes3Read = new byte[3]; + buffer.get(bytes3Read); + assertArrayEquals(new byte[] {2, 3, 4}, bytes3Read); + assertEquals(42, buffer.get()); + assertEquals(15, buffer.getInt()); + assertEquals(16L, buffer.getLong()); + assertEquals(17.0f, buffer.getFloat()); + assertEquals(18.0, buffer.getDouble()); } @Test diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeySerializerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeySerializerTest.java index 10c7e0c31e11..4a156c709569 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeySerializerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeySerializerTest.java @@ -23,9 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; import java.io.IOException; import java.nio.ByteBuffer; @@ -54,12 +51,12 @@ void gettersWork() { @Test void deserializerWorks() throws IOException { - final var bin = mock(ByteBuffer.class); - given(bin.get()) - .willReturn(contractKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) - .willReturn((byte) (contractKey.getContractId() >> 8)) - .willReturn((byte) (contractKey.getContractId())) - .willReturn(contractKey.getUint256Byte(0)); + final ByteBuffer bin = ByteBuffer.allocate(4); + bin.put(contractKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.put((byte) (contractKey.getContractId() >> 8)); + bin.put((byte) (contractKey.getContractId())); + bin.put(contractKey.getUint256Byte(0)); + bin.rewind(); assertEquals(contractKey, subject.deserialize(bin, 1)); } @@ -69,27 +66,32 @@ void serializerWorks() throws IOException { final var contractIdNonZeroBytes = contractKey.getContractIdNonZeroBytes(); final var uint256KeyNonZeroBytes = contractKey.getUint256KeyNonZeroBytes(); - final var out = mock(ByteBuffer.class); - final var inOrder = inOrder(out); + final ByteBuffer out = ByteBuffer.allocate(4); subject.serialize(contractKey, out); + out.rewind(); - inOrder.verify(out).put(contractKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + final ByteBuffer byteBuffer = ByteBuffer.allocate(4); + + byteBuffer.put(contractKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); for (int b = contractIdNonZeroBytes - 1; b >= 0; b--) { - inOrder.verify(out).put((byte) (contractKey.getContractId() >> (b * 8))); + byteBuffer.put((byte) (contractKey.getContractId() >> (b * 8))); } for (int b = uint256KeyNonZeroBytes - 1; b >= 0; b--) { - inOrder.verify(out).put(contractKey.getUint256Byte(b)); + byteBuffer.put(contractKey.getUint256Byte(b)); } + byteBuffer.rewind(); + + assertEquals(out, byteBuffer); } @Test void deserializeKeySizeWorks() { final var contractIdNonZeroBytes = contractKey.getContractIdNonZeroBytes(); final var uint256KeyNonZeroBytes = contractKey.getUint256KeyNonZeroBytes(); - final var bin = mock(ByteBuffer.class); - - given(bin.get()).willReturn(contractKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + final ByteBuffer bin = ByteBuffer.allocate(1); + bin.put(contractKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) + .rewind(); assertEquals(1 + contractIdNonZeroBytes + uint256KeyNonZeroBytes, subject.deserializeKeySize(bin)); } @@ -98,12 +100,11 @@ void deserializeKeySizeWorks() { void equalsUsingByteBufferWorks() throws IOException { final var someKey = new ContractKey(0L, key); final var anIdenticalKey = new ContractKey(0L, key); - final var bin = mock(ByteBuffer.class); - - given(bin.get()) - .willReturn(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) - .willReturn((byte) (someKey.getContractId())) - .willReturn(someKey.getUint256Byte(0)); + final ByteBuffer bin = ByteBuffer.allocate(3); + bin.put(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.put((byte) someKey.getContractId()); + bin.put(someKey.getUint256Byte(0)); + bin.rewind(); assertTrue(subject.equals(bin, 1, anIdenticalKey)); } @@ -114,27 +115,31 @@ void equalsUsingByteBufferFailsAsExpected() throws IOException { final var someKeyForDiffContractButSameNonZeroBytes = new ContractKey(otherContractNum, key); final var someKeyForDiffContract = new ContractKey(Long.MAX_VALUE, key); final var someDiffKeyForSameContract = new ContractKey(contractNum, largeKey.toArray()); - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(4); - given(bin.get()) - .willReturn(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) - .willReturn((byte) (someKey.getContractId() >> 8)) - .willReturn((byte) (someKey.getContractId())) - .willReturn(someKey.getUint256Byte(0)); + bin.put(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.put((byte) (someKey.getContractId() >> 8)); + bin.put((byte) (someKey.getContractId())); + bin.put(someKey.getUint256Byte(0)); + bin.rewind(); assertFalse(subject.equals(bin, 1, someKeyForDiffContract)); - given(bin.get()) - .willReturn(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) - .willReturn((byte) (someKey.getContractId() >> 8)) - .willReturn((byte) (someKey.getContractId())) - .willReturn(someKey.getUint256Byte(0)); + bin.rewind(); + bin.put(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.put((byte) (someKey.getContractId() >> 8)); + bin.put((byte) (someKey.getContractId())); + bin.put(someKey.getUint256Byte(0)); + bin.rewind(); assertFalse(subject.equals(bin, 1, someKeyForDiffContractButSameNonZeroBytes)); - given(bin.get()) - .willReturn(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) - .willReturn((byte) (someKey.getContractId() >> 8)) - .willReturn((byte) (someKey.getContractId())) - .willReturn(someKey.getUint256Byte(0)); + bin.rewind(); + + bin.put(someKey.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.put((byte) (someKey.getContractId() >> 8)); + bin.put((byte) (someKey.getContractId())); + bin.put(someKey.getUint256Byte(0)); + bin.rewind(); + assertFalse(subject.equals(bin, 1, someDiffKeyForSameContract)); } } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeyTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeyTest.java index 79426e6befc4..2e6f45c5bec2 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeyTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractKeyTest.java @@ -154,20 +154,25 @@ void serializeWorks() throws IOException { void serializeUsingByteBufferWorks() throws IOException { subject = new ContractKey(contractNum, key_array); - final var out = mock(ByteBuffer.class); - final var inOrder = inOrder(out); + final ByteBuffer out = ByteBuffer.allocate(4); + final ByteBuffer inOrder = ByteBuffer.allocate(4); final var contractIdNonZeroBytes = subject.getContractIdNonZeroBytes(); final var uint256KeyNonZeroBytes = subject.getUint256KeyNonZeroBytes(); subject.serialize(out); - inOrder.verify(out).put(subject.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + out.rewind(); + + inOrder.put(subject.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); for (int b = contractIdNonZeroBytes - 1; b >= 0; b--) { - inOrder.verify(out).put((byte) (subject.getContractId() >> (b * 8))); + inOrder.put((byte) (subject.getContractId() >> (b * 8))); } for (int b = uint256KeyNonZeroBytes - 1; b >= 0; b--) { - inOrder.verify(out).put(subject.getUint256Byte(b)); + inOrder.put(subject.getUint256Byte(b)); } + inOrder.rewind(); + + assertEquals(inOrder, out); } @Test @@ -198,14 +203,15 @@ void deserializeWithByteBufferWorks() throws IOException { subject = new ContractKey(contractNum, key); final var testSubject = new ContractKey(); - final var bin = mock(ByteBuffer.class); - given(bin.get()) - .willReturn(subject.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()) - .willReturn((byte) (subject.getContractId() >> 8)) - .willReturn((byte) (subject.getContractId())) - .willReturn(subject.getUint256Byte(0)); + final ByteBuffer bin = ByteBuffer.allocate(4); + bin.put(subject.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.put((byte) (subject.getContractId() >> 8)); + bin.put((byte) (subject.getContractId())); + bin.put(subject.getUint256Byte(0)); + bin.rewind(); testSubject.deserialize(bin, 1); + bin.rewind(); assertEquals(subject, testSubject); } @@ -264,9 +270,9 @@ void readKeySizeWorks() { subject = new ContractKey(contractNum, key); final var contractIdNonZeroBytes = subject.getContractIdNonZeroBytes(); final var uint256KeyNonZeroBytes = subject.getUint256KeyNonZeroBytes(); - final var bin = mock(ByteBuffer.class); - - given(bin.get()).willReturn(subject.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + final ByteBuffer bin = ByteBuffer.allocate(8); + bin.put(subject.getContractIdNonZeroBytesAndUint256KeyNonZeroBytes()); + bin.rewind(); assertEquals(1 + contractIdNonZeroBytes + uint256KeyNonZeroBytes, readKeySize(bin)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractValueTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractValueTest.java index 877c899c164d..c8239f841de7 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractValueTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/ContractValueTest.java @@ -180,12 +180,10 @@ void serializeWorks() throws IOException { @Test void serializeUsingByteBufferWorks() throws IOException { - final var out = mock(ByteBuffer.class); - final var inOrder = inOrder(out); - + final ByteBuffer out = ByteBuffer.allocate(bytesValue.length); subject.serialize(out); - - inOrder.verify(out).put(subject.getValue()); + out.rewind(); + assertArrayEquals(out.array(), subject.getValue()); } @Test @@ -216,18 +214,12 @@ void deserializeThrowsOnInvalidLength() throws IOException { @Test void deserializeWithByteBufferWorks() throws IOException { subject = new ContractValue(); - final var byteBuffer = mock(ByteBuffer.class); - doAnswer(invocation -> { - subject.setValue(bytesValue); - return null; - }) - .when(byteBuffer) - .get(subject.getValue()); + final ByteBuffer byteBuffer = ByteBuffer.allocate(bytesValue.length); + byteBuffer.put(bytesValue).rewind(); subject.deserialize(byteBuffer, MERKLE_VERSION); - assertEquals(bytesValue, subject.getValue()); - verify(byteBuffer).get(defaultEmpty); + assertArrayEquals(bytesValue, subject.getValue()); } @Test @@ -245,13 +237,7 @@ void cannotDeserializeIntoAReadOnlyContractValue() throws IOException { assertThrows(IllegalStateException.class, () -> readOnly.deserialize(in, MERKLE_VERSION)); // and when - final var byteBuffer = mock(ByteBuffer.class); - doAnswer(invocation -> { - subject.setValue(bytesValue); - return null; - }) - .when(byteBuffer) - .get(subject.getValue()); + final ByteBuffer byteBuffer = ByteBuffer.allocate(4); assertThrows(IllegalStateException.class, () -> readOnly.deserialize(byteBuffer, MERKLE_VERSION)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumValueTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumValueTest.java index 43d20f022ac7..7d3de0c9e7f2 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumValueTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumValueTest.java @@ -57,11 +57,14 @@ void asReadOnlyWorks() { @Test void deserializeWorksWithBuffer() throws IOException { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(8); + bin.putLong(2L); + bin.rewind(); + final var expectedKey = new EntityNumValue(); - given(bin.getLong()).willReturn(2L); expectedKey.deserialize(bin, 1); + bin.rewind(); assertEquals(2L, expectedKey.num()); } @@ -79,11 +82,15 @@ void deserializeWorksWithStream() throws IOException { @Test void serializeWorksWithBuffer() throws IOException { - final var out = mock(ByteBuffer.class); + final ByteBuffer out = ByteBuffer.allocate(8); + final ByteBuffer verify = ByteBuffer.allocate(8); + verify.putLong(2L); + verify.rewind(); subject.serialize(out); + out.rewind(); - verify(out).putLong(2L); + assertEquals(verify, out); } @Test diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeySerializerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeySerializerTest.java index ee9c94fb4431..1d5495962d49 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeySerializerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeySerializerTest.java @@ -21,9 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import com.swirlds.common.io.streams.SerializableDataInputStream; import java.io.IOException; @@ -38,7 +35,7 @@ class EntityNumVirtualKeySerializerTest { @Test void gettersWork() { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.deserializeKeySize(bin)); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.getSerializedSize()); @@ -49,21 +46,28 @@ void gettersWork() { @Test void deserializeWorks() throws IOException { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); final var expectedKey = new EntityNumVirtualKey(longKey); - given(bin.getLong()).willReturn(longKey); + + bin.putLong(longKey); + bin.rewind(); assertEquals(expectedKey, subject.deserialize(bin, 1)); } @Test void serializeWorks() throws IOException { - final var out = mock(ByteBuffer.class); + final ByteBuffer out = ByteBuffer.allocate(subject.getSerializedSize()); + final ByteBuffer verify = ByteBuffer.allocate(subject.getSerializedSize()); + verify.putLong(longKey); + verify.rewind(); + final var virtualKey = new EntityNumVirtualKey(longKey); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.serialize(virtualKey, out)); + out.rewind(); - verify(out).putLong(longKey); + assertEquals(verify, out); } @Test @@ -71,10 +75,12 @@ void equalsUsingByteBufferWorks() throws IOException { final var someKey = new EntityNumVirtualKey(longKey); final var diffNum = new EntityNumVirtualKey(otherLongKey); - final var bin = mock(ByteBuffer.class); - given(bin.getLong()).willReturn(someKey.getKeyAsLong()); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); + bin.putLong(someKey.getKeyAsLong()); + bin.rewind(); assertTrue(subject.equals(bin, 1, someKey)); + bin.rewind(); assertFalse(subject.equals(bin, 1, diffNum)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeyTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeyTest.java index b0b3461ad1ad..d1d1f5f2b5f5 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeyTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/EntityNumVirtualKeyTest.java @@ -75,18 +75,25 @@ void objectContractMet() { @Test void serializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); + final ByteBuffer buffer = ByteBuffer.allocate(8); + final ByteBuffer verify = ByteBuffer.allocate(8); + + verify.putLong(longKey); + verify.limit(verify.position()); + verify.rewind(); subject.serialize(buffer); + buffer.rewind(); - verify(buffer).putLong(longKey); + assertEquals(buffer, verify); } @Test void deserializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - - given(buffer.getLong()).willReturn(longKey); + final ByteBuffer buffer = ByteBuffer.allocate(8); + buffer.putLong(longKey); + buffer.limit(buffer.position()); + buffer.rewind(); EntityNumVirtualKey key = new EntityNumVirtualKey(); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/IterableContractValueTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/IterableContractValueTest.java index d1a3f1059420..1cc6916741ca 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/IterableContractValueTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/IterableContractValueTest.java @@ -245,20 +245,23 @@ void serializeUsingByteBufferWorksWithBothKeys() throws IOException { final var rawPrevKey = prevUint256Key.toArrayUnsafe(); final var rawNextKey = nextUint256Key.toArrayUnsafe(); - final var out = mock(ByteBuffer.class); - final var inOrder = inOrder(out); + final ByteBuffer out = ByteBuffer.allocate(subject.getSerializedSize()); subject.serialize(out); - inOrder.verify(out).put(subject.getValue()); - inOrder.verify(out).put(numNonZeroBytesInPrev); + final ByteBuffer byteBuffer = ByteBuffer.allocate(subject.getSerializedSize()); + + byteBuffer.put(subject.getValue()); + byteBuffer.put(numNonZeroBytesInPrev); for (int i = 0, offset = 32 - numNonZeroBytesInPrev; i < numNonZeroBytesInPrev; i++) { - inOrder.verify(out).put(rawPrevKey[i + offset]); + byteBuffer.put(rawPrevKey[i + offset]); } - inOrder.verify(out).put(numNonZeroBytesInNext); + byteBuffer.put(numNonZeroBytesInNext); for (int i = 0, offset = 32 - numNonZeroBytesInNext; i < numNonZeroBytesInNext; i++) { - inOrder.verify(out).put(rawNextKey[i + offset]); + byteBuffer.put(rawNextKey[i + offset]); } + + assertEquals(out, byteBuffer); } @Test @@ -358,18 +361,15 @@ void deserializeThrowsOnInvalidLength() throws IOException { @Test void deserializeWithByteBufferWorks() throws IOException { subject = new IterableContractValue(); - final var byteBuffer = mock(ByteBuffer.class); - doAnswer(invocation -> { - subject.setValue(bytesValue); - return null; - }) - .when(byteBuffer) - .get(subject.getValue()); + final ByteBuffer byteBuffer = ByteBuffer.allocate(48); + byteBuffer.put(bytesValue); // for read uint256Value + byteBuffer.put(new byte[8]); // for read prevUint256Key + byteBuffer.put(new byte[8]); // for red nextUint256Key + byteBuffer.rewind(); subject.deserialize(byteBuffer, ITERABLE_VERSION); - assertEquals(bytesValue, subject.getValue()); - verify(byteBuffer).get(defaultEmpty); + assertArrayEquals(bytesValue, subject.getValue()); } @Test @@ -400,13 +400,7 @@ void cannotDeserializeIntoAReadOnlyContractValue() throws IOException { assertThrows(IllegalStateException.class, () -> readOnly.deserialize(in, ITERABLE_VERSION)); // and when - final var byteBuffer = mock(ByteBuffer.class); - doAnswer(invocation -> { - subject.setValue(bytesValue); - return null; - }) - .when(byteBuffer) - .get(subject.getValue()); + final ByteBuffer byteBuffer = ByteBuffer.allocate(bytesValue.length); assertThrows(IllegalStateException.class, () -> readOnly.deserialize(byteBuffer, ITERABLE_VERSION)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeySerializerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeySerializerTest.java index 838e10211b0c..5d679bc8f8d5 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeySerializerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeySerializerTest.java @@ -25,9 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import com.swirlds.common.io.streams.SerializableDataInputStream; import java.io.IOException; @@ -42,7 +40,7 @@ class VirtualBlobKeySerializerTest { @Test void gettersWork() { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.deserializeKeySize(bin)); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.getSerializedSize()); @@ -53,23 +51,21 @@ void gettersWork() { @Test void deserializeWorks() throws IOException { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); final var expectedKey = new VirtualBlobKey(FILE_DATA, entityNum); - given(bin.get()).willReturn((byte) FILE_DATA.ordinal()); - given(bin.getInt()).willReturn(entityNum); + bin.put((byte) FILE_DATA.ordinal()); + bin.putInt(entityNum); + bin.rewind(); assertEquals(expectedKey, subject.deserialize(bin, 1)); } @Test void serializeWorks() throws IOException { - final var out = mock(ByteBuffer.class); + final ByteBuffer out = ByteBuffer.allocate(subject.getSerializedSize()); final var virtualBlobKey = new VirtualBlobKey(FILE_DATA, entityNum); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.serialize(virtualBlobKey, out)); - - verify(out).put((byte) FILE_DATA.ordinal()); - verify(out).putInt(entityNum); } @Test @@ -78,12 +74,15 @@ void equalsUsingByteBufferWorks() throws IOException { final var sameTypeDiffNum = new VirtualBlobKey(FILE_DATA, otherEntityNum); final var diffTypeSameNum = new VirtualBlobKey(VirtualBlobKey.Type.FILE_METADATA, entityNum); - final var bin = mock(ByteBuffer.class); - given(bin.get()).willReturn((byte) someKey.getType().ordinal()); - given(bin.getInt()).willReturn(someKey.getEntityNumCode()); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); + bin.put((byte) someKey.getType().ordinal()); + bin.putInt(someKey.getEntityNumCode()); + bin.rewind(); assertTrue(subject.equals(bin, 1, someKey)); + bin.rewind(); assertFalse(subject.equals(bin, 1, sameTypeDiffNum)); + bin.rewind(); assertFalse(subject.equals(bin, 1, diffTypeSameNum)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeyTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeyTest.java index 47ee4cbf9193..8851c38e819f 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeyTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobKeyTest.java @@ -107,20 +107,24 @@ void fromPathThrowsOnInvalidType() { @Test void serializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); + final ByteBuffer buffer = ByteBuffer.allocate(16); + final ByteBuffer verify = ByteBuffer.allocate(16); + verify.put((byte) FILE_DATA.ordinal()); + verify.putInt(entityNum); + verify.rewind(); subject.serialize(buffer); + buffer.rewind(); - verify(buffer).put((byte) FILE_DATA.ordinal()); - verify(buffer).putInt(entityNum); + assertEquals(verify, buffer); } @Test void deserializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - - given(buffer.get()).willReturn((byte) FILE_DATA.ordinal()); - given(buffer.getInt()).willReturn(entityNum); + final ByteBuffer buffer = ByteBuffer.allocate(16); + buffer.put((byte) FILE_DATA.ordinal()); + buffer.putInt(entityNum); + buffer.rewind(); VirtualBlobKey blobKey = new VirtualBlobKey(); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobValueTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobValueTest.java index 95247a33510c..964eea4d8edf 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobValueTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/VirtualBlobValueTest.java @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -32,9 +31,6 @@ import java.nio.ByteBuffer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; class VirtualBlobValueTest { private VirtualBlobValue subject; @@ -89,30 +85,33 @@ void deserializeWorks() throws IOException { @Test void serializeWithByteBufferWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - final var inOrder = inOrder(buffer); + final ByteBuffer buffer = ByteBuffer.allocate(10); + final ByteBuffer inOrder = ByteBuffer.allocate(10); + subject.serialize(buffer); + buffer.rewind(); + + inOrder.putInt(data.length); + inOrder.put(data); + inOrder.rewind(); - inOrder.verify(buffer).putInt(data.length); - inOrder.verify(buffer).put(data); + assertEquals(buffer, inOrder); } @Test void deserializeWithByteBufferWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); + final ByteBuffer buffer = ByteBuffer.allocate(10); final var defaultSubject = new VirtualBlobValue(); - int len = data.length; - - given(buffer.getInt()).willReturn(len); - doAnswer(new Answer() { - @Override - public Object answer(final InvocationOnMock invocationOnMock) { - defaultSubject.setData(data); - return null; - } - }) - .when(buffer) - .get(ArgumentMatchers.any()); + + final ByteBuffer inOrder = ByteBuffer.allocate(10); + + subject.serialize(buffer); + buffer.rewind(); + + inOrder.putInt(data.length); + inOrder.put(data); + inOrder.limit(inOrder.position()); + inOrder.rewind(); defaultSubject.deserialize(buffer, VirtualBlobValue.CURRENT_VERSION); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeySerializerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeySerializerTest.java index e722ad554bde..119f22640ba9 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeySerializerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeySerializerTest.java @@ -21,9 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import com.swirlds.common.io.streams.SerializableDataInputStream; import java.io.IOException; @@ -38,7 +35,7 @@ class ScheduleEqualityVirtualKeySerializerTest { @Test void gettersWork() { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(Long.BYTES); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.deserializeKeySize(bin)); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.getSerializedSize()); @@ -49,21 +46,26 @@ void gettersWork() { @Test void deserializeWorks() throws IOException { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(Long.BYTES); final var expectedKey = new ScheduleEqualityVirtualKey(longKey); - given(bin.getLong()).willReturn(longKey); + bin.putLong(longKey); + bin.rewind(); assertEquals(expectedKey, subject.deserialize(bin, 1)); } @Test void serializeWorks() throws IOException { - final var out = mock(ByteBuffer.class); - final var virtualKey = new ScheduleEqualityVirtualKey(longKey); + final ByteBuffer out = ByteBuffer.allocate(Long.BYTES); + final ByteBuffer verify = ByteBuffer.allocate(Long.BYTES); + final var virtualKey = new ScheduleEqualityVirtualKey(longKey); + verify.putLong(longKey); + verify.rewind(); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.serialize(virtualKey, out)); + out.rewind(); - verify(out).putLong(longKey); + assertEquals(verify, out); } @Test @@ -71,10 +73,13 @@ void equalsUsingByteBufferWorks() throws IOException { final var someKey = new ScheduleEqualityVirtualKey(longKey); final var diffNum = new ScheduleEqualityVirtualKey(otherLongKey); - final var bin = mock(ByteBuffer.class); - given(bin.getLong()).willReturn(someKey.getKeyAsLong()); + final ByteBuffer bin = ByteBuffer.allocate(Long.BYTES); + bin.putLong(someKey.getKeyAsLong()); + bin.rewind(); assertTrue(subject.equals(bin, 1, someKey)); + bin.rewind(); + assertFalse(subject.equals(bin, 1, diffNum)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeyTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeyTest.java index 87d840571741..53232657748a 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeyTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualKeyTest.java @@ -73,18 +73,23 @@ void objectContractMet() { @Test void serializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); + final ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + final ByteBuffer verify = ByteBuffer.allocate(Long.BYTES); + + verify.putLong(longKey); + verify.rewind(); subject.serialize(buffer); + buffer.rewind(); - verify(buffer).putLong(longKey); + assertEquals(buffer, verify); } @Test void deserializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - - given(buffer.getLong()).willReturn(longKey); + final ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(longKey); + buffer.rewind(); ScheduleEqualityVirtualKey key = new ScheduleEqualityVirtualKey(); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualValueTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualValueTest.java index a3a2e3f0d5d6..480aa909b377 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualValueTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleEqualityVirtualValueTest.java @@ -133,42 +133,41 @@ void deserializeWorks() throws IOException { @Test void serializeWithByteBufferWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - final var inOrder = inOrder(buffer); + final ByteBuffer buffer = ByteBuffer.allocate(1024); subject.serialize(buffer); - inOrder.verify(buffer).putInt(2); + final ByteBuffer inOrder = ByteBuffer.allocate(buffer.limit()); + inOrder.putInt(2); - inOrder.verify(buffer).putInt(3); - inOrder.verify(buffer).put("foo".getBytes(StandardCharsets.UTF_8)); - inOrder.verify(buffer).putLong(1L); + inOrder.putInt(3); + inOrder.put("foo".getBytes(StandardCharsets.UTF_8)); + inOrder.putLong(1L); - inOrder.verify(buffer).putInt(5); - inOrder.verify(buffer).put("truck".getBytes(StandardCharsets.UTF_8)); - inOrder.verify(buffer).putLong(2L); - inOrder.verify(buffer).putLong(3L); + inOrder.putInt(5); + inOrder.put("truck".getBytes(StandardCharsets.UTF_8)); + inOrder.putLong(2L); + inOrder.putLong(3L); - inOrder.verifyNoMoreInteractions(); + assertEquals(buffer, inOrder); } @Test void deserializeWithByteBufferWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); final var defaultSubject = new ScheduleEqualityVirtualValue(); - given(buffer.getInt()).willReturn(2, 3, 5); + final ByteBuffer buffer = ByteBuffer.allocate(1024); + buffer.putInt(2); - given(buffer.get()).willReturn((byte) 1); - given(buffer.getLong()).willReturn(1L, 2L, 3L); + buffer.putInt(3); + buffer.put("foo".getBytes(StandardCharsets.UTF_8)); + buffer.putLong(1L); - doAnswer(invocationOnMock -> null).when(buffer).get(ArgumentMatchers.argThat(b -> { - if (b.length == 3) { - System.arraycopy("foo".getBytes(StandardCharsets.UTF_8), 0, b, 0, 3); - } else { - System.arraycopy("truck".getBytes(StandardCharsets.UTF_8), 0, b, 0, 5); - } - return true; - })); + buffer.putInt(5); + buffer.put("truck".getBytes(StandardCharsets.UTF_8)); + buffer.putLong(2L); + buffer.putLong(3L); + buffer.limit(buffer.position()); + buffer.rewind(); defaultSubject.deserialize(buffer, ScheduleEqualityVirtualValue.CURRENT_VERSION); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleSecondVirtualValueTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleSecondVirtualValueTest.java index 81244bd5b044..f1d61d8d845e 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleSecondVirtualValueTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleSecondVirtualValueTest.java @@ -128,36 +128,48 @@ void deserializeWorks() throws IOException { @Test void serializeWithByteBufferWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - final var inOrder = inOrder(buffer); + final ByteBuffer buffer = ByteBuffer.allocate(256); subject.serialize(buffer); - inOrder.verify(buffer).putInt(2); + final ByteBuffer inOrder = ByteBuffer.allocate(256); + inOrder.putInt(2); - inOrder.verify(buffer).putInt(3); - inOrder.verify(buffer).putLong(50L); - inOrder.verify(buffer).putLong(60L); - inOrder.verify(buffer).putLong(70L); - inOrder.verify(buffer).putLong(10L); - inOrder.verify(buffer).putInt(20); + inOrder.putInt(3); + inOrder.putLong(50L); + inOrder.putLong(60L); + inOrder.putLong(70L); + inOrder.putLong(10L); + inOrder.putInt(20); - inOrder.verify(buffer).putInt(1); - inOrder.verify(buffer).putLong(80L); - inOrder.verify(buffer).putLong(30L); - inOrder.verify(buffer).putInt(40); - inOrder.verify(buffer).putLong(3L); + inOrder.putInt(1); + inOrder.putLong(80L); + inOrder.putLong(30L); + inOrder.putInt(40); + inOrder.putLong(3L); - inOrder.verifyNoMoreInteractions(); + assertEquals(buffer, inOrder); } @Test void deserializeWithByteBufferWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - final var defaultSubject = new ScheduleSecondVirtualValue(); + final ByteBuffer buffer = ByteBuffer.allocate(256); + buffer.putInt(2); + + buffer.putInt(3); + buffer.putLong(50L); + buffer.putLong(60L); + buffer.putLong(70L); + buffer.putLong(10L); + buffer.putInt(20); + + buffer.putInt(1); + buffer.putLong(80L); + buffer.putLong(30L); + buffer.putInt(40); + buffer.putLong(3L); + buffer.rewind(); - given(buffer.getInt()).willReturn(2, 3, 20, 1, 40); - given(buffer.get()).willReturn((byte) 1); - given(buffer.getLong()).willReturn(50L, 60L, 70L, 10L, 80L, 30L, 3L); + final var defaultSubject = new ScheduleSecondVirtualValue(); defaultSubject.deserialize(buffer, ScheduleSecondVirtualValue.CURRENT_VERSION); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeySerializerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeySerializerTest.java index 07a01c60824d..335bf5293be9 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeySerializerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeySerializerTest.java @@ -21,9 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import com.swirlds.common.io.streams.SerializableDataInputStream; import java.io.IOException; @@ -38,7 +35,7 @@ class SecondSinceEpocVirtualKeySerializerTest { @Test void gettersWork() { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(subject.getSerializedSize()); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.deserializeKeySize(bin)); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.getSerializedSize()); @@ -49,21 +46,22 @@ void gettersWork() { @Test void deserializeWorks() throws IOException { - final var bin = mock(ByteBuffer.class); + final ByteBuffer bin = ByteBuffer.allocate(100); + bin.putLong(longKey).rewind(); final var expectedKey = new SecondSinceEpocVirtualKey(longKey); - given(bin.getLong()).willReturn(longKey); assertEquals(expectedKey, subject.deserialize(bin, 1)); } @Test void serializeWorks() throws IOException { - final var out = mock(ByteBuffer.class); + final ByteBuffer out = ByteBuffer.allocate(100); + final var virtualKey = new SecondSinceEpocVirtualKey(longKey); assertEquals(BYTES_IN_SERIALIZED_FORM, subject.serialize(virtualKey, out)); - verify(out).putLong(longKey); + out.putLong(longKey); } @Test @@ -71,10 +69,12 @@ void equalsUsingByteBufferWorks() throws IOException { final var someKey = new SecondSinceEpocVirtualKey(longKey); final var diffNum = new SecondSinceEpocVirtualKey(otherLongKey); - final var bin = mock(ByteBuffer.class); - given(bin.getLong()).willReturn(someKey.getKeyAsLong()); + final ByteBuffer bin = ByteBuffer.allocate(Long.SIZE); + bin.putLong(someKey.getKeyAsLong()).rewind(); assertTrue(subject.equals(bin, 1, someKey)); + + bin.rewind(); assertFalse(subject.equals(bin, 1, diffNum)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeyTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeyTest.java index 513572a53f00..790a9f78fbf6 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeyTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/virtual/temporal/SecondSinceEpocVirtualKeyTest.java @@ -71,20 +71,11 @@ void objectContractMet() { assertFalse(forcedEqualsCheck, "forcing equals on two different class types."); } - @Test - void serializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - - subject.serialize(buffer); - - verify(buffer).putLong(longKey); - } - @Test void deserializeWorks() throws IOException { - final var buffer = mock(ByteBuffer.class); - - given(buffer.getLong()).willReturn(longKey); + final ByteBuffer buffer = ByteBuffer.allocate(100); + buffer.putLong(longKey); + buffer.rewind(); SecondSinceEpocVirtualKey key = new SecondSinceEpocVirtualKey(); diff --git a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts index 05fd24ed3010..5445b5173d7e 100644 --- a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts +++ b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts @@ -33,5 +33,4 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") - runtimeOnly("org.mockito.inline") } diff --git a/hedera-node/hedera-schedule-service-impl/build.gradle.kts b/hedera-node/hedera-schedule-service-impl/build.gradle.kts index 460a5a008b6f..65813fb7e7ee 100644 --- a/hedera-node/hedera-schedule-service-impl/build.gradle.kts +++ b/hedera-node/hedera-schedule-service-impl/build.gradle.kts @@ -32,5 +32,4 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") - runtimeOnly("org.mockito.inline") } diff --git a/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts b/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts index 9f26ba38137d..1214eee0aba8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts +++ b/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts @@ -32,5 +32,4 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") - runtimeOnly("org.mockito.inline") } diff --git a/hedera-node/hedera-token-service-impl/build.gradle.kts b/hedera-node/hedera-token-service-impl/build.gradle.kts index 78964191db22..23afb2403fe9 100644 --- a/hedera-node/hedera-token-service-impl/build.gradle.kts +++ b/hedera-node/hedera-token-service-impl/build.gradle.kts @@ -36,7 +36,6 @@ testModuleInfo { requires("org.mockito") requires("org.mockito.junit.jupiter") requiresStatic("com.github.spotbugs.annotations") - runtimeOnly("org.mockito.inline") requires("com.google.protobuf") requires("com.swirlds.common") } diff --git a/platform-sdk/swirlds-jasperdb/build.gradle.kts b/platform-sdk/swirlds-jasperdb/build.gradle.kts index bbf0140e378d..7536e4575b19 100644 --- a/platform-sdk/swirlds-jasperdb/build.gradle.kts +++ b/platform-sdk/swirlds-jasperdb/build.gradle.kts @@ -34,5 +34,4 @@ testModuleInfo { requires("org.junit.jupiter.api") requires("org.junit.jupiter.params") requires("org.mockito") - runtimeOnly("org.mockito.inline") } diff --git a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/VirtualHashRecordSerializerTest.java b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/VirtualHashRecordSerializerTest.java index 3e10dc67a551..afce5f42771a 100644 --- a/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/VirtualHashRecordSerializerTest.java +++ b/platform-sdk/swirlds-jasperdb/src/test/java/com/swirlds/merkledb/VirtualHashRecordSerializerTest.java @@ -19,9 +19,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import com.swirlds.common.crypto.DigestType; import com.swirlds.common.crypto.Hash; @@ -53,7 +50,7 @@ void deserializeEnforcesCurrentVersion() { @Test void serializeEnforcesDefaultDigest() { - final ByteBuffer bbuf = mock(ByteBuffer.class); + final ByteBuffer bbuf = ByteBuffer.allocate(subject.getSerializedSize()); final Hash nonDefaultHash = new Hash(DigestType.SHA_512); final VirtualHashRecord data = new VirtualHashRecord(1L, nonDefaultHash); assertEquals(Long.BYTES, subject.getHeaderSize(), "Header size should be 8 bytes"); @@ -69,13 +66,15 @@ void serializeEnforcesDefaultDigest() { @Test void deserializeHappyPath() throws IOException { - final ByteBuffer bb = mock(ByteBuffer.class); + final ByteBuffer bb = ByteBuffer.allocate(subject.getSerializedSize()); final Hash validHash = new Hash(DigestType.SHA_384); final VirtualHashRecord expectedData = new VirtualHashRecord(42L, validHash); - when(bb.getLong()).thenReturn(42L); - when(bb.get(any())).thenReturn(bb); + bb.putLong(42L); + bb.rewind(); + final DataItemHeader expectedHeader = new DataItemHeader(56, 42L); assertEquals(expectedHeader, subject.deserializeHeader(bb), "Deserialized header should match serialized"); + bb.rewind(); assertEquals(expectedData, subject.deserialize(bb, 1L), "Deserialized data should match serialized"); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/system/SystemUtilsTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/system/SystemUtilsTest.java index 14e94e49f6f9..3f7eee3b04eb 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/system/SystemUtilsTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/system/SystemUtilsTest.java @@ -20,10 +20,12 @@ import com.swirlds.platform.system.SystemExitCode; import com.swirlds.platform.system.SystemExitUtils; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; class SystemUtilsTest { @Test + @Disabled("Not longer supported in Java 21") void checkDefaultCharsetTest() { // System.setSecurityManager() is deprecated, but they do intend on providing an alternative in future release // for blocking System.exit(). Mentioned here: https://openjdk.java.net/jeps/411 From 0ca35f5feef254480800d0312afc0138cfadc344 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:32:49 -0600 Subject: [PATCH 36/80] chore: Misc contract cleanup and fixes (#10634) Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../exec/ContextTransactionProcessor.java | 10 +- .../impl/exec/TransactionProcessor.java | 102 +++++++++--------- .../impl/exec/failure/AbortException.java | 61 +++++++++++ .../impl/exec/gas/CustomGasCharging.java | 67 ++++++------ .../handlers/EthereumTransactionHandler.java | 17 +-- .../impl/hevm/HederaEvmTransactionResult.java | 8 ++ .../infra/HevmStaticTransactionFactory.java | 10 +- .../impl/infra/HevmTransactionFactory.java | 9 +- .../EthereumTransactionRecordBuilder.java | 2 +- .../impl/state/DispatchingEvmFrameState.java | 5 + .../contract/impl/state/EvmFrameState.java | 8 ++ .../contract/impl/state/HederaEvmAccount.java | 7 ++ .../contract/impl/state/ProxyEvmAccount.java | 5 + .../contract/impl/state/TokenEvmAccount.java | 8 ++ .../contract/impl/utils/ConversionUtils.java | 79 +++++++++++++- .../exec/ContextTransactionProcessorTest.java | 48 +++++++-- .../test/exec/TransactionProcessorTest.java | 57 ++-------- .../test/exec/gas/CustomGasChargingTest.java | 25 ++++- .../EthereumTransactionHandlerTest.java | 11 +- .../hevm/HederaEvmTransactionResultTest.java | 39 +++++-- .../bdd/suites/ethereum/EthereumSuite.java | 41 +++---- 21 files changed, 400 insertions(+), 219 deletions(-) create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index 9f003f98fa94..09f2dd0b159f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -20,6 +20,7 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; +import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; @@ -107,11 +108,10 @@ public CallOutcome call() { result.finalStatus(), result.recipientId(), result.gasPrice()); - } catch (HandleException abort) { - // try to resolve the sender if it is an alias - var sender = feesOnlyUpdater.get().getHederaAccount(hevmTransaction.senderId()); - var senderId = sender != null ? sender.hederaId() : hevmTransaction.senderId(); - final var result = HederaEvmTransactionResult.fromAborted(senderId, hevmTransaction, abort.getStatus()); + } catch (AbortException e) { + // Commit any HAPI fees that were charged before aborting + rootProxyWorldUpdater.commit(); + final var result = HederaEvmTransactionResult.fromAborted(e.senderId(), hevmTransaction, e.getStatus()); return new CallOutcome( result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), result.finalStatus(), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index b1c3f1130d6e..a981cbd8bc5c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -18,18 +18,19 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; +import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; +import static com.hedera.node.app.service.contract.impl.exec.failure.AbortException.validateTrueOrAbort; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult.resourceExhaustionFrom; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isEvmAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isLongZeroAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjToBesuAddress; -import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.sponsorCustomizedCreation; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.base.Duration; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; -import com.hedera.hapi.node.state.token.Account; +import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; @@ -39,7 +40,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; -import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; @@ -76,8 +77,8 @@ public TransactionProcessor( /** * Records the two or three parties involved in a transaction. * - * @param sender the externally-operated account that signed the transaction (AKA the "origin") - * @param relayer if non-null, the account relayed an Ethereum transaction on behalf of the sender + * @param sender the externally-operated account that signed the transaction (AKA the "origin") + * @param relayer if non-null, the account relayed an Ethereum transaction on behalf of the sender * @param receiverAddress the address of the account receiving the top-level call */ private record InvolvedParties( @@ -92,13 +93,14 @@ AccountID senderId() { * Process the given transaction, returning the result of running it to completion * and committing to the given updater. * - * @param transaction the transaction to process - * @param updater the world updater to commit to + * @param transaction the transaction to process + * @param updater the world updater to commit to * @param feesOnlyUpdater if base commit fails, a fees-only updater - * @param context the context to use - * @param tracer the tracer to use - * @param config the node configuration + * @param context the context to use + * @param tracer the tracer to use + * @param config the node configuration * @return the result of running the transaction to completion + * @throws AbortException if processing failed before initiating the EVM transaction */ public HederaEvmTransactionResult processTransaction( @NonNull final HederaEvmTransaction transaction, @@ -107,8 +109,23 @@ public HederaEvmTransactionResult processTransaction( @NonNull final HederaEvmContext context, @NonNull final ActionSidecarContentTracer tracer, @NonNull final Configuration config) { - // Setup for the EVM transaction; thrown HandleException's will propagate back to the workflow final var parties = computeInvolvedParties(transaction, updater, config); + try { + return processTransactionWithParties( + transaction, updater, feesOnlyUpdater, context, tracer, config, parties); + } catch (HandleException e) { + throw new AbortException(e.getStatus(), parties.senderId()); + } + } + + private HederaEvmTransactionResult processTransactionWithParties( + @NonNull final HederaEvmTransaction transaction, + @NonNull final HederaWorldUpdater updater, + @NonNull final Supplier feesOnlyUpdater, + @NonNull final HederaEvmContext context, + @NonNull final ActionSidecarContentTracer tracer, + @NonNull final Configuration config, + @NonNull final InvolvedParties parties) { final var gasCharges = gasCharging.chargeForGas(parties.sender(), parties.relayer(), context, updater, transaction); final var initialFrame = frameBuilder.buildInitialFrameWith( @@ -121,13 +138,8 @@ public HederaEvmTransactionResult processTransaction( gasCharges.intrinsicGas()); // Compute the result of running the frame to completion - final HederaEvmTransactionResult result; - try { - result = frameRunner.runToCompletion( - transaction.gasLimit(), parties.senderId(), initialFrame, tracer, messageCall, contractCreation); - } catch (ResourceExhaustedException e) { - return commitResourceExhaustion(transaction, feesOnlyUpdater.get(), context, e.getStatus(), config); - } + final var result = frameRunner.runToCompletion( + transaction.gasLimit(), parties.senderId(), initialFrame, tracer, messageCall, contractCreation); // Maybe refund some of the charged fees before committing gasCharging.maybeRefundGiven( @@ -168,9 +180,12 @@ private HederaEvmTransactionResult commitResourceExhaustion( @NonNull final HederaEvmContext context, @NonNull final ResponseCodeEnum reason, @NonNull final Configuration config) { - // Note these calls cannot fail, or processTransaction() above would have aborted right away + // Note that computing involved parties and charging for gas are guaranteed to succeed here, + // or processTransaction() would have aborted right away final var parties = computeInvolvedParties(transaction, updater, config); gasCharging.chargeForGas(parties.sender(), parties.relayer(), context, updater, transaction); + // (FUTURE) Once fee charging is more consumable in the HandleContext, we will also want + // to re-charge top-level HAPI fees in this edge case (not only gas); not urgent though updater.commit(); return resourceExhaustionFrom(parties.senderId(), transaction.gasLimit(), context.gasPrice(), reason); } @@ -189,8 +204,8 @@ private HederaEvmTransactionResult commitResourceExhaustion( * {@link HederaWorldUpdater#setupAliasedTopLevelCreate(ContractCreateTransactionBody, Address)} * * @param transaction the transaction to set up - * @param updater the updater for the transaction - * @param config the current node configuration + * @param updater the updater for the transaction + * @param config the current node configuration * @return the involved parties determined while setting up the transaction */ private InvolvedParties computeInvolvedParties( @@ -198,44 +213,22 @@ private InvolvedParties computeInvolvedParties( @NonNull final HederaWorldUpdater updater, @NonNull final Configuration config) { final var sender = updater.getHederaAccount(transaction.senderId()); - validateTrue(sender != null, INVALID_ACCOUNT_ID); + validateTrueOrAbort(sender != null, INVALID_ACCOUNT_ID, transaction.senderId()); + final var senderId = sender.hederaId(); HederaEvmAccount relayer = null; if (transaction.isEthereumTransaction()) { relayer = updater.getHederaAccount(requireNonNull(transaction.relayerId())); - validateTrue(relayer != null, INVALID_ACCOUNT_ID); + validateTrueOrAbort(relayer != null, INVALID_ACCOUNT_ID, senderId); } final InvolvedParties parties; if (transaction.isCreate()) { final Address to; + final var op = requireNonNull(transaction.hapiCreation()); if (transaction.isEthereumTransaction()) { - final ReadableAccountStore accountStore = - updater.enhancement().nativeOperations().readableAccountStore(); - var createBody = requireNonNull(transaction.hapiCreation()); - final var modifiedBodyBuilder = createBody.copyBuilder(); - - final Account sponsor = requireNonNull(accountStore.getAccountById(transaction.senderId())); - if (sponsor.memo() != null) { - modifiedBodyBuilder.memo(sponsor.memo()); - } - if (sponsor.autoRenewAccountId() != null) { - modifiedBodyBuilder.autoRenewAccountId(sponsor.autoRenewAccountId()); - } - if (sponsor.stakedAccountId() != null) { - modifiedBodyBuilder.stakedAccountId(sponsor.stakedAccountId()); - } - if (sponsor.autoRenewSeconds() > 0) { - modifiedBodyBuilder.autoRenewPeriod(Duration.newBuilder() - .seconds(sponsor.autoRenewSeconds()) - .build()); - } - modifiedBodyBuilder.maxAutomaticTokenAssociations(sponsor.maxAutoAssociations()); - modifiedBodyBuilder.declineReward(sponsor.declineReward()); - - final var modifiedBody = modifiedBodyBuilder.build(); to = Address.contractAddress(sender.getAddress(), sender.getNonce()); - updater.setupAliasedTopLevelCreate(modifiedBody, to); + updater.setupAliasedTopLevelCreate(sponsorCustomizedCreation(op, sender.toNativeAccount()), to); } else { - to = updater.setupTopLevelCreate(transaction.hapiCreation()); + to = updater.setupTopLevelCreate(op); } parties = new InvolvedParties(sender, relayer, to); } else { @@ -243,22 +236,23 @@ private InvolvedParties computeInvolvedParties( if (maybeLazyCreate(transaction, to, config)) { // Presumably these checks _could_ be done later as part of the message // call, but historically we have failed fast when they do not pass - validateTrue(transaction.hasValue(), INVALID_CONTRACT_ID); + validateTrueOrAbort(transaction.hasValue(), INVALID_CONTRACT_ID, senderId); final var alias = transaction.contractIdOrThrow().evmAddressOrThrow(); - validateTrue(isEvmAddress(alias), INVALID_CONTRACT_ID); + validateTrueOrAbort(isEvmAddress(alias), INVALID_CONTRACT_ID, senderId); // do not attempt to lazy create account with alias that is a long zero address - validateTrue(!isLongZeroAddress(alias.toByteArray()), INVALID_CONTRACT_ID); + validateTrueOrAbort(!isLongZeroAddress(alias.toByteArray()), INVALID_CONTRACT_ID, senderId); parties = new InvolvedParties(sender, relayer, pbjToBesuAddress(alias)); updater.setupTopLevelLazyCreate(parties.receiverAddress); } else { - validateTrue(to != null, INVALID_CONTRACT_ID); + validateTrueOrAbort(to != null, INVALID_CONTRACT_ID, senderId); parties = new InvolvedParties(sender, relayer, requireNonNull(to).getAddress()); } } if (transaction.isEthereumTransaction()) { + validateTrueOrAbort(transaction.nonce() == parties.sender().getNonce(), WRONG_NONCE, senderId); parties.sender().incrementNonce(); } return parties; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java new file mode 100644 index 000000000000..911c46f91cc0 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.failure; + +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.node.app.spi.workflows.HandleException; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * An exception thrown when a transaction is aborted before entering the EVM. + * + *

    Includes the effective Hedera id of the sender. + */ +public class AbortException extends HandleException { + private final AccountID senderId; + + public AbortException(@NonNull final ResponseCodeEnum status, @NonNull final AccountID senderId) { + super(status); + this.senderId = requireNonNull(senderId); + } + + /** + * Returns the effective Hedera id of the sender. + * + * @return the effective Hedera id of the sender + */ + public AccountID senderId() { + return senderId; + } + + /** + * Throws an {@code AbortException} if the given flag is {@code false}. + * + * @param flag the flag to check + * @param status the status to use if the flag is {@code false} + * @param senderId the effective Hedera id of the sender + */ + public static void validateTrueOrAbort( + final boolean flag, @NonNull final ResponseCodeEnum status, @NonNull final AccountID senderId) { + if (!flag) { + throw new AbortException(status, senderId); + } + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCharging.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCharging.java index 220aff534fe9..53249800ca7d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCharging.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCharging.java @@ -95,7 +95,11 @@ public void maybeRefundGiven( * Tries to charge gas for the given transaction based on the pre-fetched sender and relayer accounts, * within the given context and world updater. * - *

    Even if there are gas charges, still returns the intrinsic gas cost of the transaction. + *

    IMPORTANT: Applies any charges only if all charges will succeed. This lets us + * avoid reverting the root updater in the case of insufficient balances; which is nice since this + * updater will contain non-gas fees we want to keep intact (for operations other than {@code ContractCall}). + * + *

    Even if there are no gas charges, still returns the intrinsic gas cost of the transaction. * * @param sender the sender account * @param relayer the relayer account @@ -111,6 +115,11 @@ public GasCharges chargeForGas( @NonNull final HederaEvmContext context, @NonNull final HederaWorldUpdater worldUpdater, @NonNull final HederaEvmTransaction transaction) { + requireNonNull(sender); + requireNonNull(context); + requireNonNull(worldUpdater); + requireNonNull(transaction); + final var intrinsicGas = gasCalculator.transactionIntrinsicGasCost(transaction.evmPayload(), transaction.isCreate()); if (context.isNoopGasContext()) { @@ -118,8 +127,8 @@ public GasCharges chargeForGas( } validateTrue(transaction.gasLimit() >= intrinsicGas, INSUFFICIENT_GAS); if (transaction.isEthereumTransaction()) { - final var allowanceUsed = - chargeWithRelayer(sender, requireNonNull(relayer), context, worldUpdater, transaction); + requireNonNull(relayer); + final var allowanceUsed = chargeWithRelayer(sender, relayer, context, worldUpdater, transaction); return new GasCharges(intrinsicGas, allowanceUsed); } else { chargeWithOnlySender(sender, context, worldUpdater, transaction); @@ -132,11 +141,10 @@ private void chargeWithOnlySender( @NonNull final HederaEvmContext context, @NonNull final HederaWorldUpdater worldUpdater, @NonNull final HederaEvmTransaction transaction) { - final var gasCost = transaction.gasCostGiven(context.gasPrice()); - final var upfrontCost = transaction.upfrontCostGiven(context.gasPrice()); - // We validate up-front cost here just for consistency with existing code - validateTrue(sender.getBalance().toLong() >= upfrontCost, INSUFFICIENT_PAYER_BALANCE); - validateAndCharge(gasCost, sender, worldUpdater); + validateTrue( + sender.getBalance().toLong() >= transaction.upfrontCostGiven(context.gasPrice()), + INSUFFICIENT_PAYER_BALANCE); + worldUpdater.collectFee(sender.hederaId(), transaction.gasCostGiven(context.gasPrice())); } private long chargeWithRelayer( @@ -146,37 +154,24 @@ private long chargeWithRelayer( @NonNull final HederaWorldUpdater worldUpdater, @NonNull final HederaEvmTransaction transaction) { final var gasCost = transaction.gasCostGiven(context.gasPrice()); + final long senderGasCost; + final long relayerGasCost; if (transaction.requiresFullRelayerAllowance()) { - validateTrue(transaction.maxGasAllowance() >= gasCost, INSUFFICIENT_TX_FEE); - validateAndCharge(gasCost, requireNonNull(relayer), worldUpdater); - return gasCost; + senderGasCost = 0L; + relayerGasCost = gasCost; } else if (transaction.offeredGasPrice() >= context.gasPrice()) { - validateAndCharge(gasCost, sender, worldUpdater); - return 0L; + senderGasCost = gasCost; + relayerGasCost = 0L; } else { - final var relayerGasCost = gasCost - transaction.offeredGasCost(); - validateTrue(transaction.maxGasAllowance() >= relayerGasCost, INSUFFICIENT_TX_FEE); - validateAndCharge( - transaction.offeredGasCost(), relayerGasCost, sender, requireNonNull(relayer), worldUpdater); - return relayerGasCost; + senderGasCost = transaction.offeredGasCost(); + relayerGasCost = gasCost - transaction.offeredGasCost(); } - } - - private void validateAndCharge( - final long amount, @NonNull final HederaEvmAccount payer, @NonNull final HederaWorldUpdater worldUpdater) { - validateTrue(payer.getBalance().toLong() >= amount, INSUFFICIENT_PAYER_BALANCE); - worldUpdater.collectFee(payer.hederaId(), amount); - } - - private void validateAndCharge( - final long aAmount, - final long bAmount, - @NonNull final HederaEvmAccount aPayer, - @NonNull final HederaEvmAccount bPayer, - @NonNull final HederaWorldUpdater worldUpdater) { - validateTrue(aPayer.getBalance().toLong() >= aAmount, INSUFFICIENT_PAYER_BALANCE); - validateTrue(bPayer.getBalance().toLong() >= bAmount, INSUFFICIENT_PAYER_BALANCE); - worldUpdater.collectFee(aPayer.hederaId(), aAmount); - worldUpdater.collectFee(bPayer.hederaId(), bAmount); + // Ensure all up-front charges are payable (including any to-be-collected value sent with the initial frame) + validateTrue(transaction.maxGasAllowance() >= relayerGasCost, INSUFFICIENT_TX_FEE); + validateTrue(relayer.getBalance().toLong() >= relayerGasCost, INSUFFICIENT_PAYER_BALANCE); + validateTrue(sender.getBalance().toLong() >= senderGasCost + transaction.value(), INSUFFICIENT_PAYER_BALANCE); + worldUpdater.collectFee(relayer.hederaId(), relayerGasCost); + worldUpdater.collectFee(sender.hederaId(), senderGasCost); + return relayerGasCost; } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index 34e7f698da84..f57b5fdca614 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -19,10 +19,8 @@ import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; -import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.throwIfUnsuccessful; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; -import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; import static java.util.Objects.requireNonNull; @@ -35,7 +33,6 @@ import com.hedera.node.app.service.contract.impl.records.EthereumTransactionRecordBuilder; import com.hedera.node.app.service.file.ReadableFileStore; import com.hedera.node.app.service.mono.fees.calculation.ethereum.txns.EthereumTransactionResourceUsage; -import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; @@ -46,7 +43,6 @@ import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Objects; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; @@ -93,12 +89,6 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Create the transaction-scoped component final var component = provider.get().create(context, ETHEREUM_TRANSACTION); - final var hevmTransactionFactory = component.contextTransactionProcessor().hevmTransactionFactory; - final var hevmTransaction = hevmTransactionFactory.fromHapiTransaction(context.body()); - - final var accountStore = context.readableStore(ReadableAccountStore.class); - final var sender = accountStore.getAccountById(Objects.requireNonNull(hevmTransaction.senderId())); - // Assemble the appropriate top-level record for the result final var ethTxData = requireNonNull(requireNonNull(component.hydratedEthTxData()).ethTxData()); @@ -108,9 +98,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var recordBuilder = context.recordBuilder(EthereumTransactionRecordBuilder.class) .ethereumHash(Bytes.wrap(ethTxData.getEthereumHash())) - .status(outcome.status()) - .feeChargedToPayer(outcome.tinybarGasCost()); - + .status(outcome.status()); if (ethTxData.hasToAddress()) { // The Ethereum transaction was a top-level MESSAGE_CALL recordBuilder.contractID(outcome.recipientId()).contractCallResult(outcome.result()); @@ -118,8 +106,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException // The Ethereum transaction was a top-level CONTRACT_CREATION recordBuilder.contractID(outcome.recipientIdIfCreated()).contractCreateResult(outcome.result()); } - - validateTrue(sender.ethereumNonce() == ethTxData.nonce(), WRONG_NONCE); + recordBuilder.withTinybarGasFee(outcome.tinybarGasCost()); throwIfUnsuccessful(outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java index f98177de6754..c3007d2faef8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java @@ -18,12 +18,14 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CONTRACT_STORAGE_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_STORAGE_IN_PRICE_REGIME_HAS_BEEN_USED; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.errorMessageFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; @@ -77,6 +79,8 @@ public record HederaEvmTransactionResult( private static final Bytes INVALID_CONTRACT_REASON = Bytes.wrap(INVALID_CONTRACT_ID.name()); private static final Bytes MAX_CHILD_RECORDS_EXCEEDED_REASON = Bytes.wrap(MAX_CHILD_RECORDS_EXCEEDED.name()); private static final Bytes INSUFFICIENT_TX_FEE_REASON = Bytes.wrap(INSUFFICIENT_TX_FEE.name()); + private static final Bytes INSUFFICIENT_PAYER_BALANCE_REASON = Bytes.wrap(INSUFFICIENT_PAYER_BALANCE.name()); + private static final Bytes WRONG_NONCE_REASON = Bytes.wrap(WRONG_NONCE.name()); /** * Converts this result to a {@link ContractFunctionResult} for a transaction based on the given @@ -145,6 +149,10 @@ public ResponseCodeEnum finalStatus() { return MAX_CHILD_RECORDS_EXCEEDED; } else if (revertReason.equals(INSUFFICIENT_TX_FEE_REASON)) { return INSUFFICIENT_TX_FEE; + } else if (revertReason.equals(WRONG_NONCE_REASON)) { + return WRONG_NONCE; + } else if (revertReason.equals(INSUFFICIENT_PAYER_BALANCE_REASON)) { + return INSUFFICIENT_PAYER_BALANCE; } else { return CONTRACT_REVERT_EXECUTED; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmStaticTransactionFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmStaticTransactionFactory.java index b80c07b56f16..8b475f5e610f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmStaticTransactionFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmStaticTransactionFactory.java @@ -19,13 +19,12 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_GAS_LIMIT_EXCEEDED; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction.NOT_APPLICABLE; -import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.EVM_ADDRESS_LENGTH_AS_LONG; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asPriorityId; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static java.util.Objects.requireNonNull; import static org.apache.tuweni.bytes.Bytes.EMPTY; import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.contract.ContractCallLocalQuery; import com.hedera.hapi.node.transaction.Query; import com.hedera.node.app.service.contract.impl.annotations.QueryScope; @@ -70,13 +69,8 @@ public HederaEvmTransaction fromHapiQuery(@NonNull final Query query) { final var op = query.contractCallLocalOrThrow(); assertValidCall(op); final var senderId = op.hasSenderId() ? op.senderIdOrThrow() : payerId; - var targetId = op.contractIDOrThrow(); // For mono-service fidelity, allow calls using 0.0.X id even to contracts with a priority EVM address - final var maybeContract = - context.createStore(ReadableAccountStore.class).getContractById(targetId); - if (maybeContract != null && maybeContract.alias().length() == EVM_ADDRESS_LENGTH_AS_LONG) { - targetId = ContractID.newBuilder().evmAddress(maybeContract.alias()).build(); - } + final var targetId = asPriorityId(op.contractIDOrThrow(), context.createStore(ReadableAccountStore.class)); return new HederaEvmTransaction( senderId, null, targetId, NOT_APPLICABLE, op.functionParameters(), null, 0L, op.gas(), 1L, 0L, null); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java index 42eb897e80f9..89d42c1b92a0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/infra/HevmTransactionFactory.java @@ -36,6 +36,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_CHAIN_ID; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction.NOT_APPLICABLE; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asChainIdBytes; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asPriorityId; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthEthTxCreation; import static com.hedera.node.app.spi.key.KeyUtils.isEmpty; import static com.hedera.node.app.spi.validation.ExpiryMeta.NA; @@ -168,7 +169,7 @@ private HederaEvmTransaction fromHapiCall( return new HederaEvmTransaction( payer, null, - body.contractIDOrThrow(), + asPriorityId(body.contractIDOrThrow(), accountStore), NOT_APPLICABLE, body.functionParameters(), null, @@ -198,7 +199,11 @@ private HederaEvmTransaction fromHapiEthereum( return new HederaEvmTransaction( senderId, relayerId, - ContractID.newBuilder().evmAddress(Bytes.wrap(ethTxData.to())).build(), + asPriorityId( + ContractID.newBuilder() + .evmAddress(Bytes.wrap(ethTxData.to())) + .build(), + accountStore), ethTxData.nonce(), ethTxData.hasCallData() ? Bytes.wrap(ethTxData.callData()) : Bytes.EMPTY, Bytes.wrap(ethTxData.chainId()), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java index d9d28278e77e..598ce361b206 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java @@ -23,7 +23,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -public interface EthereumTransactionRecordBuilder { +public interface EthereumTransactionRecordBuilder extends GasFeeRecordBuilder { /** * Tracks the final status of a HAPI Ethereum transaction. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java index 4d038f1c2e77..a8ad3ab75748 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java @@ -218,6 +218,11 @@ public long getNonce(final long number) { return validatedAccount(number).ethereumNonce(); } + @Override + public com.hedera.hapi.node.state.token.Account getNativeAccount(final long number) { + return validatedAccount(number); + } + /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java index e2935e83ad2e..1de3a6f14712 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java @@ -188,6 +188,14 @@ Optional tryTransfer( @NonNull Hash getTokenRedirectCodeHash(@NonNull Address address); + /** + * Returns the native account with the given number. + * + * @param number the account number + * @return the native account + */ + com.hedera.hapi.node.state.token.Account getNativeAccount(long number); + /** * Returns the nonce for the account with the given number. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java index 92a61b335b05..17fab7734a18 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/HederaEvmAccount.java @@ -24,6 +24,13 @@ import org.hyperledger.besu.evm.account.MutableAccount; public interface HederaEvmAccount extends MutableAccount { + /** + * Returns a native Hedera account representation of this account. + * + * @return the native Hedera account + */ + com.hedera.hapi.node.state.token.Account toNativeAccount(); + /** * Returns whether this account is an ERC-20/ERC-721 facade for a Hedera token. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java index 33423c40c119..0bf290a0e721 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java @@ -57,6 +57,11 @@ public Address getAddress() { return state.getAddress(number); } + @Override + public com.hedera.hapi.node.state.token.Account toNativeAccount() { + return state.getNativeAccount(number); + } + @Override public long getNonce() { return state.getNonce(number); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java index 863f96eba18d..f1e13db11b28 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/TokenEvmAccount.java @@ -95,6 +95,14 @@ public UInt256 getOriginalStorageValue(@NonNull final UInt256 key) { return UInt256.ZERO; } + /** + * Since a token is not actually a native Hedera account, always throws {@link UnsupportedOperationException}. + */ + @Override + public com.hedera.hapi.node.state.token.Account toNativeAccount() { + throw new UnsupportedOperationException(); + } + @Override public boolean isEmpty() { return false; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java index afc0c8ffea5d..5cc21054540c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java @@ -26,8 +26,10 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.Duration; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.contract.ContractCreateTransactionBody; import com.hedera.hapi.node.contract.ContractLoginfo; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.ExchangeRate; @@ -37,6 +39,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.service.contract.impl.state.StorageAccesses; +import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.spi.workflows.HandleException; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -127,6 +130,7 @@ public static long asExactLongValueOrZero(@NonNull final BigInteger value) { /** * Given a {@link AccountID}, returns its address as a headlong address. + * * @param accountID the account id * @return the headlong address */ @@ -140,6 +144,7 @@ public static com.esaulpaugh.headlong.abi.Address headlongAddressOf(@NonNull fin /** * Given a {@link ContractID}, returns its address as a headlong address. + * * @param contractId the contract id * @return the headlong address */ @@ -153,6 +158,7 @@ public static com.esaulpaugh.headlong.abi.Address headlongAddressOf(@NonNull fin /** * Given a {@link TokenID}, returns its address as a headlong address. + * * @param tokenId * @return */ @@ -172,6 +178,26 @@ public static Address priorityAddressOf(@NonNull final Account account) { return Address.wrap(Bytes.wrap(explicitAddressOf(account))); } + /** + * Given a contract id, returns its "priority" form if the id refers to an extant contract with an + * EVM address outside the long-zero subspace. + * + *

    If there is no such contract; or if the id refers to a contract with an EVM address within the + * long-zero subspace; then returns the given contract id. + * + * @param contractID the contract id + * @param accountStore the account store + * @return the priority form of the contract id + */ + public static ContractID asPriorityId( + @NonNull final ContractID contractID, @NonNull final ReadableAccountStore accountStore) { + final var maybeContract = accountStore.getContractById(contractID); + if (maybeContract != null && maybeContract.alias().length() == EVM_ADDRESS_LENGTH_AS_LONG) { + return ContractID.newBuilder().evmAddress(maybeContract.alias()).build(); + } + return contractID; + } + /** * Given an account, returns its "priority" address as a headlong address. * @@ -307,7 +333,7 @@ public static long hederaIdNumOfOriginatorIn(@NonNull final MessageFrame frame) /** * Given a {@link MessageFrame}, returns the id number of the given address's Hedera id. * - * @param frame the {@link MessageFrame} + * @param frame the {@link MessageFrame} * @param address the address to get the id number of * @return the id number of the given address's Hedera id */ @@ -333,7 +359,7 @@ public static Address longZeroAddressOfRecipient(@NonNull final MessageFrame fra * if the address does not correspond to a known Hedera entity; or {@link HederaNativeOperations#NON_CANONICAL_REFERENCE_NUMBER} * if the address references an account by its "non-priority" long-zero address. * - * @param address the EVM address + * @param address the EVM address * @param nativeOperations the {@link HandleHederaNativeOperations} to use for resolving aliases * @return the number of the corresponding Hedera entity, if it exists and has this priority address */ @@ -360,7 +386,7 @@ public static long accountNumberForEvmReference( * within the given {@link HandleHederaNativeOperations}; or {@link HederaNativeOperations#MISSING_ENTITY_NUMBER} * if the address is not long-zero and does not correspond to a known Hedera entity. * - * @param address the EVM address + * @param address the EVM address * @param nativeOperations the {@link HandleHederaNativeOperations} to use for resolving aliases * @return the number of the corresponding Hedera entity, if it exists */ @@ -682,12 +708,59 @@ public static Address fromHeadlongAddress(@NonNull final com.esaulpaugh.headlong return Address.fromHexString(address.toString()); } + /** + * Given an exchange rate and a tinycent amount, returns the equivalent tinybar amount. + * + * @param exchangeRate the exchange rate + * @param tinycents the tinycent amount + * @return the equivalent tinybar amount + */ public static long fromTinycentsToTinybars(final ExchangeRate exchangeRate, final long tinycents) { return fromAToB(BigInteger.valueOf(tinycents), exchangeRate.hbarEquiv(), exchangeRate.centEquiv()) .longValueExact(); } + /** + * Given an amount in one unit and its conversion rate to another unit, returns the equivalent amount + * in the other unit. + * + * @param aAmount the amount in one unit + * @param bEquiv the numerator of the conversion rate + * @param aEquiv the denominator of the conversion rate + * @return the equivalent amount in the other unit + */ public static @NonNull BigInteger fromAToB(@NonNull final BigInteger aAmount, final int bEquiv, final int aEquiv) { return aAmount.multiply(BigInteger.valueOf(bEquiv)).divide(BigInteger.valueOf(aEquiv)); } + + /** + * Given a {@link ContractCreateTransactionBody} and a sponsor {@link Account}, returns a creation body + * fully customized with the sponsor's properties. + * + * @param op the creation body + * @param sponsor the sponsor + * @return the fully customized creation body + */ + public static @NonNull ContractCreateTransactionBody sponsorCustomizedCreation( + @NonNull final ContractCreateTransactionBody op, @NonNull final Account sponsor) { + requireNonNull(op); + requireNonNull(sponsor); + final var builder = op.copyBuilder(); + if (sponsor.memo() != null) { + builder.memo(sponsor.memo()); + } + if (sponsor.autoRenewAccountId() != null) { + builder.autoRenewAccountId(sponsor.autoRenewAccountId()); + } + if (sponsor.stakedAccountId() != null) { + builder.stakedAccountId(sponsor.stakedAccountId()); + } + if (sponsor.autoRenewSeconds() > 0) { + builder.autoRenewPeriod( + Duration.newBuilder().seconds(sponsor.autoRenewSeconds()).build()); + } + return builder.maxAutomaticTokenAssociations(sponsor.maxAutoAssociations()) + .declineReward(sponsor.declineReward()) + .build(); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java index fd8160fafb0c..d9dce7b1ddf5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java @@ -16,20 +16,24 @@ package com.hedera.node.app.service.contract.impl.test.exec; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_038; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_TO_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.HEVM_CREATION; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SUCCESS_RESULT; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.assertFailsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.CallOutcome; import com.hedera.node.app.service.contract.impl.exec.ContextTransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; +import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -67,7 +71,7 @@ class ContextTransactionProcessorTest { private TransactionProcessor processor; @Mock - private RootProxyWorldUpdater baseProxyWorldUpdater; + private RootProxyWorldUpdater rootProxyWorldUpdater; @Mock private Supplier feesOnlyUpdater; @@ -83,7 +87,7 @@ void callsComponentInfraAsExpectedForValidEthTx() { CONFIGURATION, hederaEvmContext, tracer, - baseProxyWorldUpdater, + rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, processors); @@ -92,10 +96,10 @@ void callsComponentInfraAsExpectedForValidEthTx() { given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) .willReturn(HEVM_CREATION); given(processor.processTransaction( - HEVM_CREATION, baseProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) + HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willReturn(SUCCESS_RESULT); - final var protoResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, baseProxyWorldUpdater); + final var protoResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, rootProxyWorldUpdater); final var expectedResult = new CallOutcome(protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice()); assertEquals(expectedResult, subject.call()); @@ -112,7 +116,7 @@ void callsComponentInfraAsExpectedForNonEthTx() { CONFIGURATION, hederaEvmContext, tracer, - baseProxyWorldUpdater, + rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, processors); @@ -121,15 +125,43 @@ void callsComponentInfraAsExpectedForNonEthTx() { given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) .willReturn(HEVM_CREATION); given(processor.processTransaction( - HEVM_CREATION, baseProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) + HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willReturn(SUCCESS_RESULT); - final var protoResult = SUCCESS_RESULT.asProtoResultOf(null, baseProxyWorldUpdater); + final var protoResult = SUCCESS_RESULT.asProtoResultOf(null, rootProxyWorldUpdater); final var expectedResult = new CallOutcome(protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice()); assertEquals(expectedResult, subject.call()); } + @Test + void stillChargesHapiFeesOnAbort() { + final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); + final var processors = Map.of(VERSION_038, processor); + final var subject = new ContextTransactionProcessor( + null, + context, + contractsConfig, + CONFIGURATION, + hederaEvmContext, + tracer, + rootProxyWorldUpdater, + hevmTransactionFactory, + feesOnlyUpdater, + processors); + + given(context.body()).willReturn(TransactionBody.DEFAULT); + given(hevmTransactionFactory.fromHapiTransaction(TransactionBody.DEFAULT)) + .willReturn(HEVM_CREATION); + given(processor.processTransaction( + HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) + .willThrow(new AbortException(INVALID_CONTRACT_ID, SENDER_ID)); + + subject.call(); + + verify(rootProxyWorldUpdater).commit(); + } + @Test void failsImmediatelyIfEthTxInvalid() { final var contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); @@ -141,7 +173,7 @@ void failsImmediatelyIfEthTxInvalid() { CONFIGURATION, hederaEvmContext, tracer, - baseProxyWorldUpdater, + rootProxyWorldUpdater, hevmTransactionFactory, feesOnlyUpdater, processors); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java index 0a9f1a8ed186..33c90b53811a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java @@ -19,7 +19,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_BALANCES_FOR_RENEWAL_FEES; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALL_DATA; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CHARGING_RESULT; @@ -220,7 +219,7 @@ void lazyCreationAttemptWithValidAddress() { expectedToAddress, CHARGING_RESULT.intrinsicGas())) .willReturn(initialFrame); - given(senderAccount.hederaId()).willReturn(SENDER_ID); + given(senderAccount.getNonce()).willReturn(NONCE); given(frameRunner.runToCompletion( transaction.gasLimit(), SENDER_ID, @@ -275,7 +274,7 @@ void ethCreateHappyPathAsExpected() { expectedToAddress, CHARGING_RESULT.intrinsicGas())) .willReturn(initialFrame); - given(senderAccount.hederaId()).willReturn(SENDER_ID); + given(senderAccount.getNonce()).willReturn(NONCE); given(frameRunner.runToCompletion( transaction.gasLimit(), SENDER_ID, @@ -284,12 +283,9 @@ void ethCreateHappyPathAsExpected() { messageCallProcessor, contractCreationProcessor)) .willReturn(SUCCESS_RESULT); - given(worldUpdater.enhancement()).willReturn(enhancement); - given(enhancement.nativeOperations()).willReturn(nativeOperations); - given(nativeOperations.readableAccountStore()).willReturn(readableAccountStore); final var parsedAccount = Account.newBuilder().accountId(senderAccount.hederaId()).build(); - given(readableAccountStore.getAccountById(SENDER_ID)).willReturn(parsedAccount); + given(senderAccount.toNativeAccount()).willReturn(parsedAccount); given(initialFrame.getSelfDestructs()).willReturn(Set.of(NON_SYSTEM_LONG_ZERO_ADDRESS)); final var result = @@ -353,7 +349,6 @@ void hapiCreateHappyPathAsExpected() { NON_SYSTEM_LONG_ZERO_ADDRESS, CHARGING_RESULT.intrinsicGas())) .willReturn(initialFrame); - given(senderAccount.hederaId()).willReturn(SENDER_ID); given(frameRunner.runToCompletion( transaction.gasLimit(), SENDER_ID, @@ -410,6 +405,7 @@ void ethCallHappyPathAsExpected() { given(gasCharging.chargeForGas(senderAccount, relayerAccount, context, worldUpdater, transaction)) .willReturn(CHARGING_RESULT); given(senderAccount.getAddress()).willReturn(EIP_1014_ADDRESS); + given(senderAccount.getNonce()).willReturn(NONCE); given(receiverAccount.getAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); given(frameBuilder.buildInitialFrameWith( transaction, @@ -420,7 +416,6 @@ void ethCallHappyPathAsExpected() { NON_SYSTEM_LONG_ZERO_ADDRESS, CHARGING_RESULT.intrinsicGas())) .willReturn(initialFrame); - given(senderAccount.hederaId()).willReturn(SENDER_ID); given(frameRunner.runToCompletion( eq(transaction.gasLimit()), eq(SENDER_ID), @@ -480,7 +475,7 @@ void ethCallAsExpectedWithResourceExhaustionInCommit() { given(gasCharging.chargeForGas(senderAccount, relayerAccount, context, worldUpdater, transaction)) .willReturn(CHARGING_RESULT); given(senderAccount.getAddress()).willReturn(EIP_1014_ADDRESS); - given(senderAccount.hederaId()).willReturn(SENDER_ID); + given(senderAccount.getNonce()).willReturn(NONCE); given(receiverAccount.getAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); given(frameBuilder.buildInitialFrameWith( transaction, @@ -513,47 +508,6 @@ void ethCallAsExpectedWithResourceExhaustionInCommit() { verify(feesOnlyUpdater).commit(); } - @Test - @SuppressWarnings("unchecked") - void resourceExhaustionResultAsExpected() { - givenSenderAccount(); - givenRelayerAccount(); - givenReceiverAccount(); - givenFeeOnlyParties(); - - final var context = wellKnownContextWith(blocks, tinybarValues, systemContractGasCalculator); - final var transaction = wellKnownRelayedHapiCall(0); - - given(gasCharging.chargeForGas(senderAccount, relayerAccount, context, worldUpdater, transaction)) - .willReturn(CHARGING_RESULT); - given(senderAccount.getAddress()).willReturn(EIP_1014_ADDRESS); - given(receiverAccount.getAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); - given(frameBuilder.buildInitialFrameWith( - transaction, - worldUpdater, - context, - config, - EIP_1014_ADDRESS, - NON_SYSTEM_LONG_ZERO_ADDRESS, - CHARGING_RESULT.intrinsicGas())) - .willReturn(initialFrame); - given(senderAccount.hederaId()).willReturn(SENDER_ID); - willThrow(new ResourceExhaustedException(MAX_CHILD_RECORDS_EXCEEDED)) - .given(frameRunner) - .runToCompletion( - eq(transaction.gasLimit()), - eq(SENDER_ID), - eq(initialFrame), - eq(tracer), - any(), - eq(contractCreationProcessor)); - - final var result = - subject.processTransaction(transaction, worldUpdater, () -> feesOnlyUpdater, context, tracer, config); - - assertResourceExhaustion(MAX_CHILD_RECORDS_EXCEEDED, result); - } - private void assertResourceExhaustion( @NonNull final ResponseCodeEnum reason, @NonNull final HederaEvmTransactionResult result) { assertFalse(result.isSuccess()); @@ -588,6 +542,7 @@ private void givenFeeOnlyParties() { private void givenSenderAccount() { given(worldUpdater.getHederaAccount(SENDER_ID)).willReturn(senderAccount); + given(senderAccount.hederaId()).willReturn(SENDER_ID); } private void givenRelayerAccount() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java index 3dbc8e9e0ed1..88e17347a3bb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java @@ -259,6 +259,8 @@ void chargesRelayerOnlyIfUserOfferedPriceIsZero() { final var gasCost = transaction.gasCostGiven(NETWORK_GAS_PRICE); given(relayer.getBalance()).willReturn(Wei.of(gasCost)); given(relayer.hederaId()).willReturn(RELAYER_ID); + given(sender.getBalance()).willReturn(Wei.of(transaction.value())); + given(sender.hederaId()).willReturn(SENDER_ID); final var chargingResult = subject.chargeForGas( sender, relayer, @@ -274,8 +276,10 @@ void chargesSenderOnlyIfUserOfferedPriceIsAtLeastNetworkPrice() { givenWellKnownIntrinsicGasCost(); final var transaction = wellKnownRelayedHapiCallWithUserGasPriceAndMaxAllowance(NETWORK_GAS_PRICE, 0); final var gasCost = transaction.gasCostGiven(NETWORK_GAS_PRICE); - given(sender.getBalance()).willReturn(Wei.of(gasCost)); + given(sender.getBalance()).willReturn(Wei.of(gasCost + transaction.value())); given(sender.hederaId()).willReturn(SENDER_ID); + given(relayer.hederaId()).willReturn(RELAYER_ID); + given(relayer.getBalance()).willReturn(Wei.ZERO); final var chargingResult = subject.chargeForGas( sender, relayer, @@ -286,12 +290,30 @@ void chargesSenderOnlyIfUserOfferedPriceIsAtLeastNetworkPrice() { verify(worldUpdater).collectFee(SENDER_ID, gasCost); } + @Test + void requiresSenderToCoverGasCost() { + givenWellKnownIntrinsicGasCost(); + final var transaction = wellKnownRelayedHapiCallWithUserGasPriceAndMaxAllowance(NETWORK_GAS_PRICE, 0); + final var gasCost = transaction.gasCostGiven(NETWORK_GAS_PRICE); + given(sender.getBalance()).willReturn(Wei.of(gasCost + transaction.value() - 1)); + given(relayer.getBalance()).willReturn(Wei.ZERO); + assertFailsWith( + INSUFFICIENT_PAYER_BALANCE, + () -> subject.chargeForGas( + sender, + relayer, + wellKnownContextWith(blocks, tinybarValues, systemContractGasCalculator), + worldUpdater, + transaction)); + } + @Test void rejectsIfSenderCannotCoverOfferedGasCost() { givenWellKnownIntrinsicGasCost(); final var transaction = wellKnownRelayedHapiCallWithUserGasPriceAndMaxAllowance(NETWORK_GAS_PRICE / 2, Long.MAX_VALUE); given(sender.getBalance()).willReturn(Wei.of(transaction.offeredGasCost() - 1)); + given(relayer.getBalance()).willReturn(Wei.of(Long.MAX_VALUE)); assertFailsWith( INSUFFICIENT_PAYER_BALANCE, () -> subject.chargeForGas( @@ -307,7 +329,6 @@ void rejectsIfRelayerCannotCoverRemainingGasCost() { givenWellKnownIntrinsicGasCost(); final var transaction = wellKnownRelayedHapiCallWithUserGasPriceAndMaxAllowance(NETWORK_GAS_PRICE / 2, Long.MAX_VALUE); - given(sender.getBalance()).willReturn(Wei.of(transaction.offeredGasCost())); given(relayer.getBalance()).willReturn(Wei.ZERO); assertFailsWith( INSUFFICIENT_PAYER_BALANCE, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index aab524374339..bdb18b7d4d81 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -32,9 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; -import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.contract.EthereumTransactionBody; -import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.CallOutcome; import com.hedera.node.app.service.contract.impl.exec.ContextTransactionProcessor; @@ -140,11 +138,6 @@ void setUpTransactionProcessing() { given(component.contextTransactionProcessor()).willReturn(contextTransactionProcessor); given(hevmTransactionFactory.fromHapiTransaction(handleContext.body())).willReturn(HEVM_CREATION); - final AccountID senderId = HEVM_CREATION.senderId(); - final var parsedAccount = Account.newBuilder().accountId(senderId).build(); - - given(handleContext.readableStore(ReadableAccountStore.class)).willReturn(readableAccountStore); - given(readableAccountStore.getAccountById(HEVM_CREATION.senderId())).willReturn(parsedAccount); given(transactionProcessor.processTransaction( HEVM_CREATION, baseProxyWorldUpdater, @@ -170,7 +163,7 @@ void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITH_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); - given(recordBuilder.feeChargedToPayer(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); + given(recordBuilder.withTinybarGasFee(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @@ -192,7 +185,7 @@ void delegatesToCreatedComponentAndExposesEthTxDataCreateWithoutToAddress() { given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITHOUT_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); - given(recordBuilder.feeChargedToPayer(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); + given(recordBuilder.withTinybarGasFee(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java index f4164dd7a363..242afd0cd5b5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java @@ -17,8 +17,10 @@ package com.hedera.node.app.service.contract.impl.test.hevm; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hedera.hapi.node.base.ResponseCodeEnum.OBTAINER_SAME_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.SELF_DESTRUCT_TO_SELF; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.BESU_LOGS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_EVM_ADDRESS; @@ -33,6 +35,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SOME_STORAGE_ACCESSES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.TWO_STORAGE_ACCESSES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.WEI_NETWORK_GAS_PRICE; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.wellKnownHapiCall; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.bloomForAll; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjLogsFrom; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjToTuweniBytes; @@ -54,7 +57,6 @@ import java.util.Optional; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -77,14 +79,9 @@ class HederaEvmTransactionResultTest { @Mock private StorageAccessTracker accessTracker; - @BeforeEach - void setUp() { - given(frame.getMessageFrameStack()).willReturn(stack); - given(stack.isEmpty()).willReturn(true); - } - @Test void finalStatusFromHaltUsesCorrespondingStatusIfFromCustom() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(SELF_DESTRUCT_TO_SELF)); final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); @@ -95,6 +92,7 @@ void finalStatusFromHaltUsesCorrespondingStatusIfFromCustom() { @Test void finalStatusFromHaltUsesCorrespondingStatusIfFromStandard() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); @@ -105,6 +103,7 @@ void finalStatusFromHaltUsesCorrespondingStatusIfFromStandard() { @Test void finalStatusFromInsufficientGasHaltImplemented() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); @@ -113,6 +112,7 @@ void finalStatusFromInsufficientGasHaltImplemented() { @Test void finalStatusFromMissingAddressHaltImplemented() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()) .willReturn(Optional.of(CustomExceptionalHaltReason.INVALID_SOLIDITY_ADDRESS)); @@ -120,8 +120,22 @@ void finalStatusFromMissingAddressHaltImplemented() { assertEquals(ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS, subject.finalStatus()); } + @Test + void finalStatusFromWrongNonceAbortTranslated() { + final var subject = HederaEvmTransactionResult.fromAborted(SENDER_ID, wellKnownHapiCall(), WRONG_NONCE); + assertEquals(WRONG_NONCE, subject.finalStatus()); + } + + @Test + void finalStatusFromIpbAbortTranslated() { + final var subject = + HederaEvmTransactionResult.fromAborted(SENDER_ID, wellKnownHapiCall(), INSUFFICIENT_PAYER_BALANCE); + assertEquals(INSUFFICIENT_PAYER_BALANCE, subject.finalStatus()); + } + @Test void givenAccessTrackerIncludesFullContractStorageChangesAndNonNullNoncesOnSuccess() { + withFrameSetup(); given(frame.getContextVariable(FrameUtils.TRACKER_CONTEXT_VARIABLE)).willReturn(accessTracker); given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); final var pendingWrites = List.of(TWO_STORAGE_ACCESSES); @@ -155,6 +169,7 @@ void givenAccessTrackerIncludesFullContractStorageChangesAndNonNullNoncesOnSucce @Test void givenEthTxDataIncludesSpecialFields() { + withFrameSetup(); given(frame.getContextVariable(FrameUtils.TRACKER_CONTEXT_VARIABLE)).willReturn(accessTracker); given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); final var pendingWrites = List.of(TWO_STORAGE_ACCESSES); @@ -193,6 +208,7 @@ void givenEthTxDataIncludesSpecialFields() { @Test void givenAccessTrackerIncludesReadStorageAccessesOnlyOnFailure() { + withFrameSetup(); given(frame.getContextVariable(FrameUtils.TRACKER_CONTEXT_VARIABLE)).willReturn(accessTracker); given(accessTracker.getJustReads()).willReturn(SOME_STORAGE_ACCESSES); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); @@ -205,6 +221,7 @@ void givenAccessTrackerIncludesReadStorageAccessesOnlyOnFailure() { @Test void withoutAccessTrackerReturnsNullStateChanges() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getOutputData()).willReturn(pbjToTuweniBytes(OUTPUT_DATA)); @@ -216,6 +233,7 @@ void withoutAccessTrackerReturnsNullStateChanges() { @Test void QueryResultOnSuccess() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getLogs()).willReturn(BESU_LOGS); given(frame.getOutputData()).willReturn(pbjToTuweniBytes(OUTPUT_DATA)); @@ -234,6 +252,7 @@ void QueryResultOnSuccess() { @Test void QueryResultOnHalt() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(ExceptionalHaltReason.INVALID_OPERATION)); @@ -244,6 +263,7 @@ void QueryResultOnHalt() { @Test void QueryResultOnRevert() { + withFrameSetup(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getRevertReason()).willReturn(Optional.of(SOME_REVERT_REASON)); @@ -251,4 +271,9 @@ void QueryResultOnRevert() { final var protoResult = result.asQueryResult(); assertEquals(SOME_REVERT_REASON.toString(), protoResult.errorMessage()); } + + private void withFrameSetup() { + given(frame.getMessageFrameStack()).willReturn(stack); + given(stack.isEmpty()).willReturn(true); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java index d81948d85c45..2a0533168f3f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java @@ -29,6 +29,7 @@ import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith; import static com.hedera.services.bdd.spec.assertions.ContractLogAsserts.logWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; +import static com.hedera.services.bdd.spec.assertions.TransferListAsserts.includingDeduction; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType.THRESHOLD; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo; @@ -108,7 +109,6 @@ import org.apache.tuweni.bytes.Bytes; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; @HapiTestSuite @@ -165,7 +165,7 @@ public List getSpecsInSuite() { .toList(); } - @Disabled("Failing or intermittently failing HAPI Test") + @HapiTest HapiSpec sendingLargerBalanceThanAvailableFailsGracefully() { final AtomicReference

    tokenCreateContractAddress = new AtomicReference<>(); @@ -191,22 +191,26 @@ HapiSpec sendingLargerBalanceThanAvailableFailsGracefully() { .via("deployTokenCreateContract"), getContractInfo(TOKEN_CREATE_CONTRACT) .exposingEvmAddress(cb -> tokenCreateContractAddress.set(asHeadlongAddress(cb)))) - .then(withOpContext((spec, opLog) -> { - var call = ethereumCall( - TOKEN_CREATE_CONTRACT, - "createNonFungibleTokenPublic", - tokenCreateContractAddress.get()) - .type(EthTxData.EthTransactionType.EIP1559) - .signingWith(SECP_256K1_SOURCE_KEY) - .payingWith(RELAYER) - .nonce(1) - .gasPrice(10L) - .sending(ONE_HUNDRED_HBARS) - .gasLimit(1_000_000L) - .via("createTokenTxn") - .hasKnownStatus(INSUFFICIENT_PAYER_BALANCE); - allRunFor(spec, call); - })); + .then( + withOpContext((spec, opLog) -> { + var call = ethereumCall( + TOKEN_CREATE_CONTRACT, + "createNonFungibleTokenPublic", + tokenCreateContractAddress.get()) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .nonce(1) + .gasPrice(10L) + .sending(ONE_HUNDRED_HBARS) + .gasLimit(1_000_000L) + .via("createTokenTxn") + .hasKnownStatus(INSUFFICIENT_PAYER_BALANCE); + allRunFor(spec, call); + }), + // Quick assertion to verify top-level HAPI fees were still charged after aborting + getTxnRecord("createTokenTxn") + .hasPriority(recordWith().transfers(includingDeduction("HAPI fees", RELAYER)))); } @HapiTest @@ -433,6 +437,7 @@ HapiSpec invalidTxData() { .then(); } + @HapiTest HapiSpec etx014ContractCreateInheritsSignerProperties() { final AtomicReference contractID = new AtomicReference<>(); final String MEMO = "memo"; From 9f2841bab5adf50ec005ec204666be93af51036f Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Fri, 22 Dec 2023 15:35:43 -0600 Subject: [PATCH 37/80] fix: emergency fix for several workflows with nested default java versions which are incorrect Signed-off-by: Nathan Klick --- .github/workflows/flow-artifact-determinism.yaml | 4 ++-- .github/workflows/node-flow-build-application.yaml | 2 +- .github/workflows/node-flow-deploy-adhoc-artifact.yaml | 4 ++-- .../workflows/node-flow-fsts-custom-regression.yaml | 4 ++-- .github/workflows/node-flow-pull-request-checks.yaml | 2 +- .github/workflows/node-zxc-build-release-artifact.yaml | 2 +- .github/workflows/node-zxf-snyk-monitor.yaml | 2 +- .../workflows/platform-flow-jrs-custom-regression.yaml | 4 ++-- .../platform-pull-request-extended-checks.yaml | 10 +++++----- .../workflows/zxc-verify-gradle-build-determinism.yaml | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/flow-artifact-determinism.yaml b/.github/workflows/flow-artifact-determinism.yaml index ab007d6aee8e..4edfc21317d6 100644 --- a/.github/workflows/flow-artifact-determinism.yaml +++ b/.github/workflows/flow-artifact-determinism.yaml @@ -32,7 +32,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.9" + default: "21.0.1" push: branches: - develop @@ -55,7 +55,7 @@ jobs: with: ref: ${{ github.event.inputs.ref || '' }} java-distribution: ${{ inputs.java-distribution || 'temurin' }} - java-version: ${{ inputs.java-version || '17.0.9' }} + java-version: ${{ inputs.java-version || '21.0.1' }} secrets: gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} diff --git a/.github/workflows/node-flow-build-application.yaml b/.github/workflows/node-flow-build-application.yaml index d1ee12d89c71..354b2e12a067 100644 --- a/.github/workflows/node-flow-build-application.yaml +++ b/.github/workflows/node-flow-build-application.yaml @@ -78,7 +78,7 @@ jobs: name: Code uses: ./.github/workflows/node-zxc-compile-application-code.yaml with: - java-version: ${{ github.event.inputs.java-version || '17.0.3' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} enable-unit-tests: ${{ github.event_name == 'push' || github.event.inputs.enable-unit-tests == 'true' }} enable-sonar-analysis: ${{ github.event_name == 'push' || github.event.inputs.enable-sonar-analysis == 'true' }} diff --git a/.github/workflows/node-flow-deploy-adhoc-artifact.yaml b/.github/workflows/node-flow-deploy-adhoc-artifact.yaml index c254d6b91086..5d372282c64c 100644 --- a/.github/workflows/node-flow-deploy-adhoc-artifact.yaml +++ b/.github/workflows/node-flow-deploy-adhoc-artifact.yaml @@ -27,7 +27,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.8" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string @@ -53,7 +53,7 @@ jobs: trigger-env-deploy: none sdk-release-profile: AdhocCommit dry-run-enabled: ${{ github.event.inputs.dry-run-enabled == 'true' }} - java-version: ${{ github.event.inputs.java-version || '17.0.8' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} diff --git a/.github/workflows/node-flow-fsts-custom-regression.yaml b/.github/workflows/node-flow-fsts-custom-regression.yaml index e20255a1d719..2319bf330561 100644 --- a/.github/workflows/node-flow-fsts-custom-regression.yaml +++ b/.github/workflows/node-flow-fsts-custom-regression.yaml @@ -37,7 +37,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string @@ -67,7 +67,7 @@ jobs: branch-name: ${{ github.ref_name }} slack-results-channel: ${{ github.event.inputs.slack-results-channel }} slack-summary-channel: ${{ github.event.inputs.slack-summary-channel }} - java-version: ${{ github.event.inputs.java-version || '17' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} use-branch-for-slack-channel: false diff --git a/.github/workflows/node-flow-pull-request-checks.yaml b/.github/workflows/node-flow-pull-request-checks.yaml index 3e9a1f36e37b..ac736e483990 100644 --- a/.github/workflows/node-flow-pull-request-checks.yaml +++ b/.github/workflows/node-flow-pull-request-checks.yaml @@ -272,7 +272,7 @@ jobs: with: ref: ${{ github.event.inputs.ref || '' }} java-distribution: temurin - java-version: 17.0.9 + java-version: 21.0.1 secrets: gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} diff --git a/.github/workflows/node-zxc-build-release-artifact.yaml b/.github/workflows/node-zxc-build-release-artifact.yaml index 17e185ea365b..39f5f5432c12 100644 --- a/.github/workflows/node-zxc-build-release-artifact.yaml +++ b/.github/workflows/node-zxc-build-release-artifact.yaml @@ -59,7 +59,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.8" + default: "21.0.1" gradle-version: description: "Gradle Version:" type: string diff --git a/.github/workflows/node-zxf-snyk-monitor.yaml b/.github/workflows/node-zxf-snyk-monitor.yaml index 3f6e4a99bb27..f722c1f251f5 100644 --- a/.github/workflows/node-zxf-snyk-monitor.yaml +++ b/.github/workflows/node-zxf-snyk-monitor.yaml @@ -38,7 +38,7 @@ jobs: uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 with: distribution: temurin - java-version: 17.0.9 + java-version: 21.0.1 - name: Setup Gradle uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0 diff --git a/.github/workflows/platform-flow-jrs-custom-regression.yaml b/.github/workflows/platform-flow-jrs-custom-regression.yaml index 17fe6ad7580d..434e71e96c0e 100644 --- a/.github/workflows/platform-flow-jrs-custom-regression.yaml +++ b/.github/workflows/platform-flow-jrs-custom-regression.yaml @@ -37,7 +37,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string @@ -66,7 +66,7 @@ jobs: branch-name: ${{ github.ref_name }} slack-results-channel: ${{ github.event.inputs.slack-results-channel }} slack-summary-channel: ${{ github.event.inputs.slack-summary-channel }} - java-version: ${{ github.event.inputs.java-version || '17' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} use-branch-for-slack-channel: false diff --git a/.github/workflows/platform-pull-request-extended-checks.yaml b/.github/workflows/platform-pull-request-extended-checks.yaml index d5e79e570218..79aee91fb14c 100644 --- a/.github/workflows/platform-pull-request-extended-checks.yaml +++ b/.github/workflows/platform-pull-request-extended-checks.yaml @@ -31,7 +31,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17" + default: "21.0.1" java-distribution: description: "Java JDK Distribution:" type: string @@ -61,7 +61,7 @@ jobs: branch-name: ${{ github.ref_name }} slack-results-channel: ${{ github.event.inputs.slack-results-channel }} slack-summary-channel: ${{ github.event.inputs.slack-summary-channel }} - java-version: ${{ github.event.inputs.java-version || '17' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} use-branch-for-slack-channel: false @@ -84,7 +84,7 @@ jobs: branch-name: ${{ github.ref_name }} slack-results-channel: ${{ github.event.inputs.slack-results-channel }} slack-summary-channel: ${{ github.event.inputs.slack-summary-channel }} - java-version: ${{ github.event.inputs.java-version || '17' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} use-branch-for-slack-channel: false @@ -107,7 +107,7 @@ jobs: branch-name: ${{ github.ref_name }} slack-results-channel: ${{ github.event.inputs.slack-results-channel }} slack-summary-channel: ${{ github.event.inputs.slack-summary-channel }} - java-version: ${{ github.event.inputs.java-version || '17' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} use-branch-for-slack-channel: false @@ -130,7 +130,7 @@ jobs: branch-name: ${{ github.ref_name }} slack-results-channel: ${{ github.event.inputs.slack-results-channel }} slack-summary-channel: ${{ github.event.inputs.slack-summary-channel }} - java-version: ${{ github.event.inputs.java-version || '17' }} + java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} gradle-version: ${{ github.event.inputs.gradle-version || 'wrapper' }} use-branch-for-slack-channel: false diff --git a/.github/workflows/zxc-verify-gradle-build-determinism.yaml b/.github/workflows/zxc-verify-gradle-build-determinism.yaml index 288060891bd1..e1fc0df5f1d0 100644 --- a/.github/workflows/zxc-verify-gradle-build-determinism.yaml +++ b/.github/workflows/zxc-verify-gradle-build-determinism.yaml @@ -32,7 +32,7 @@ on: description: "Java JDK Version:" type: string required: false - default: "17.0.9" + default: "21.0.1" secrets: gradle-cache-username: From 7e263dbf4ff7cc96bb308a16b16af2b526b27c31 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Fri, 22 Dec 2023 20:45:14 -0600 Subject: [PATCH 38/80] fix: Add a feature flag for token balances and token relationships and enable them (#10638) --- .../configuration/dev/bootstrap.properties | 2 +- hedera-node/data/config/bootstrap.properties | 1 + .../hedera/node/config/data/TokensConfig.java | 4 +- .../mono/context/primitives/StateView.java | 24 ++- .../properties/BootstrapProperties.java | 7 +- .../properties/GlobalDynamicProperties.java | 7 + .../context/properties/PropertyNames.java | 1 + .../queries/GetContractInfoResourceUsage.java | 7 +- .../queries/GetAccountInfoResourceUsage.java | 11 +- .../contract/GetContractInfoAnswer.java | 15 +- .../crypto/GetAccountBalanceAnswer.java | 24 ++- .../queries/crypto/GetAccountInfoAnswer.java | 13 +- .../src/main/resources/bootstrap.properties | 1 + .../context/primitives/StateViewTest.java | 156 ++++++++++++++++-- .../properties/BootstrapPropertiesTest.java | 4 +- .../GlobalDynamicPropertiesTest.java | 2 + .../GetContractInfoResourceUsageTest.java | 7 +- .../GetAccountInfoResourceUsageTest.java | 18 +- .../contract/GetContractInfoAnswerTest.java | 32 +++- .../crypto/GetAccountBalanceAnswerTest.java | 37 ++++- .../crypto/GetAccountInfoAnswerTest.java | 53 +++++- .../src/test/resources/bootstrap.properties | 1 + .../resources/bootstrap/standard.properties | 1 + .../impl/handlers/ContractGetInfoHandler.java | 21 ++- .../CryptoGetAccountBalanceHandler.java | 55 +++++- .../handlers/CryptoGetAccountInfoHandler.java | 15 +- .../CryptoGetAccountBalanceHandlerTest.java | 45 ++++- .../CryptoGetAccountInfoHandlerTest.java | 68 +++++++- .../token/api/AccountSummariesApi.java | 82 +++++++++ .../queries/contract/HapiGetContractInfo.java | 1 + 30 files changed, 644 insertions(+), 71 deletions(-) diff --git a/hedera-node/configuration/dev/bootstrap.properties b/hedera-node/configuration/dev/bootstrap.properties index 2948c9d8e149..7af8c9ed2c27 100644 --- a/hedera-node/configuration/dev/bootstrap.properties +++ b/hedera-node/configuration/dev/bootstrap.properties @@ -5,4 +5,4 @@ tokens.nfts.useVirtualMerkle=true accounts.blocklist.enabled=false accounts.blocklist.path= -contracts.knownBlockHash= +contracts.knownBlockHash= \ No newline at end of file diff --git a/hedera-node/data/config/bootstrap.properties b/hedera-node/data/config/bootstrap.properties index 2e3a0a67dbc5..90ee2989827b 100644 --- a/hedera-node/data/config/bootstrap.properties +++ b/hedera-node/data/config/bootstrap.properties @@ -12,3 +12,4 @@ tokens.nfts.useVirtualMerkle=true records.useConsolidatedFcq=true cache.cryptoTransfer.warmThreads=30 contracts.maxNumWithHapiSigsAccess=0 +tokens.balancesInQueries.enabled=true diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java index d22f064c5815..e4e8ad345d84 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java @@ -48,4 +48,6 @@ public record TokensConfig( @ConfigProperty(value = "nfts.useVirtualMerkle", defaultValue = "true") @NetworkProperty boolean nftsUseVirtualMerkle, @ConfigProperty(value = "autoCreations.isEnabled", defaultValue = "true") @NetworkProperty - boolean autoCreationsIsEnabled) {} + boolean autoCreationsIsEnabled, + @ConfigProperty(value = "balancesInQueries.enabled", defaultValue = "true") @NetworkProperty + boolean balancesInQueriesEnabled) {} diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/primitives/StateView.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/primitives/StateView.java index 4d3b542b4d29..bded3ee19ad5 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/primitives/StateView.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/primitives/StateView.java @@ -392,7 +392,11 @@ private Optional getFileInfo(final FileID id) { } public Optional infoForAccount( - final AccountID id, final AliasManager aliasManager, final RewardCalculator rewardCalculator) { + final AccountID id, + final AliasManager aliasManager, + final int maxTokensForAccountInfo, + final RewardCalculator rewardCalculator, + final boolean areTokenBalancesEnabledInQueries) { final var accountNum = id.getAlias().isEmpty() ? fromAccountId(id) : aliasManager.lookupIdBy(id.getAlias()); final var account = accounts().get(accountNum); if (account == null) { @@ -418,6 +422,12 @@ public Optional infoForAccount( if (!isOfEvmAddressSize(account.getAlias())) { info.setAlias(account.getAlias()); } + if (areTokenBalancesEnabledInQueries) { + final var tokenRels = tokenRels(this, account, maxTokensForAccountInfo); + if (!tokenRels.isEmpty()) { + info.addAllTokenRelationships(tokenRels); + } + } info.setStakingInfo(stakingInfo(account, rewardCalculator)); return Optional.of(info.build()); @@ -539,7 +549,11 @@ public long numNftsOwnedBy(AccountID target) { } public Optional infoForContract( - final ContractID id, final AliasManager aliasManager, final RewardCalculator rewardCalculator) { + final ContractID id, + final AliasManager aliasManager, + final int maxTokensForAccountInfo, + final RewardCalculator rewardCalculator, + final boolean areTokenBalancesEnabledInQueries) { final var contractId = EntityIdUtils.unaliased(id, aliasManager); final var contract = contracts().get(contractId); if (contract == null) { @@ -568,6 +582,12 @@ public Optional infoForContract( } else { info.setContractAccountID(asHexedEvmAddress(mirrorId)); } + if (areTokenBalancesEnabledInQueries) { + final var tokenRels = tokenRels(this, contract, maxTokensForAccountInfo); + if (!tokenRels.isEmpty()) { + info.addAllTokenRelationships(tokenRels); + } + } info.setStakingInfo(stakingInfo(contract, rewardCalculator)); try { diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/BootstrapProperties.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/BootstrapProperties.java index 6e73de242202..c21f734714df 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/BootstrapProperties.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/BootstrapProperties.java @@ -231,6 +231,7 @@ import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_USE_TREASURY_WILD_CARDS; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_USE_VIRTUAL_MERKLE; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_STORE_RELS_ON_DISK; +import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKEN_BALANCES_ENABLED_IN_QUERIES; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOPICS_MAX_NUM; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MAX_EXPORTS_PER_CONS_SEC; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MIN_FREE_TO_USED_GAS_THROTTLE_RATIO; @@ -569,7 +570,8 @@ public String getRawValue(final String name) { TOKENS_AUTO_CREATIONS_ENABLED, ACCOUNTS_BLOCKLIST_ENABLED, ACCOUNTS_BLOCKLIST_PATH, - CACHE_CRYPTO_TRANSFER_WARM_THREADS); + CACHE_CRYPTO_TRANSFER_WARM_THREADS, + TOKEN_BALANCES_ENABLED_IN_QUERIES); static final Set NODE_PROPS = Set.of( DEV_ONLY_DEFAULT_NODE_LISTENS, @@ -826,5 +828,6 @@ public static Function transformFor(final String prop) { entry(ACCOUNTS_BLOCKLIST_ENABLED, AS_BOOLEAN), entry(ACCOUNTS_BLOCKLIST_PATH, AS_STRING), entry(CACHE_CRYPTO_TRANSFER_WARM_THREADS, AS_INT), - entry(CONFIG_VERSION, AS_INT)); + entry(CONFIG_VERSION, AS_INT), + entry(TOKEN_BALANCES_ENABLED_IN_QUERIES, AS_BOOLEAN)); } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicProperties.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicProperties.java index 8fc8390117ef..8f654a1c5535 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicProperties.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicProperties.java @@ -138,6 +138,7 @@ import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_MAX_QUERY_RANGE; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_MINT_THORTTLE_SCALE_FACTOR; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_USE_TREASURY_WILDCARDS; +import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKEN_BALANCES_ENABLED_IN_QUERIES; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOPICS_MAX_NUM; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MAX_EXPORTS_PER_CONS_SEC; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MIN_FREE_TO_USED_GAS_THROTTLE_RATIO; @@ -303,6 +304,7 @@ public class GlobalDynamicProperties implements EvmProperties { private int sumOfConsensusWeights; private int cacheWarmThreads; private int configVersion; + private boolean tokenBalancesEnabledInQueries; @Inject public GlobalDynamicProperties(final HederaNumbers hederaNums, @CompositeProps final PropertySource properties) { @@ -457,6 +459,7 @@ public void reload() { maxAutoAssociations = properties.getIntProperty(LEDGER_MAX_AUTO_ASSOCIATIONS); sumOfConsensusWeights = properties.getIntProperty(STAKING_SUM_OF_CONSENSUS_WEIGHTS); cacheWarmThreads = properties.getIntProperty(CACHE_CRYPTO_TRANSFER_WARM_THREADS); + tokenBalancesEnabledInQueries = properties.getBooleanProperty(TOKEN_BALANCES_ENABLED_IN_QUERIES); } public int sumOfConsensusWeights() { @@ -992,4 +995,8 @@ public long maxStakeRewarded() { public long stakingRewardBalanceThreshold() { return stakingRewardBalanceThreshold; } + + public boolean areTokenBalancesEnabledInQueries() { + return tokenBalancesEnabledInQueries; + } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/PropertyNames.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/PropertyNames.java index 8ad85b6d7fee..fdc50cae9e18 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/PropertyNames.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/properties/PropertyNames.java @@ -215,6 +215,7 @@ private PropertyNames() { public static final String HEDERA_RECORD_STREAM_COMPRESS_FILES_ON_CREATION = "hedera.recordStream.compressFilesOnCreation"; public static final String TOKENS_AUTO_CREATIONS_ENABLED = "tokens.autoCreations.isEnabled"; + public static final String TOKEN_BALANCES_ENABLED_IN_QUERIES = "tokens.balancesInQueries.enabled"; /* ---- Node properties ----- */ public static final String DEV_ONLY_DEFAULT_NODE_LISTENS = "dev.onlyDefaultNodeListens"; diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsage.java index b21fbdea6ab0..a509cf6f7ed6 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsage.java @@ -59,7 +59,12 @@ public boolean applicableTo(final Query query) { @Override public FeeData usageGiven(final Query query, final StateView view, @Nullable final Map queryCtx) { final var op = query.getContractGetInfo(); - final var tentativeInfo = view.infoForContract(op.getContractID(), aliasManager, rewardCalculator); + final var tentativeInfo = view.infoForContract( + op.getContractID(), + aliasManager, + dynamicProperties.maxTokensRelsPerInfoQuery(), + rewardCalculator, + dynamicProperties.areTokenBalancesEnabledInQueries()); if (tentativeInfo.isPresent()) { final var info = tentativeInfo.get(); putIfNotNull(queryCtx, CONTRACT_INFO_CTX_KEY, info); diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsage.java index 228b8e72f11f..2b1d922addba 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsage.java @@ -22,6 +22,7 @@ import com.hedera.node.app.hapi.fees.usage.crypto.CryptoOpsUsage; import com.hedera.node.app.hapi.fees.usage.crypto.ExtantCryptoContext; import com.hedera.node.app.service.mono.context.primitives.StateView; +import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.fees.calculation.QueryResourceUsageEstimator; import com.hedera.node.app.service.mono.ledger.accounts.AliasManager; import com.hedera.node.app.service.mono.ledger.accounts.staking.RewardCalculator; @@ -36,15 +37,18 @@ public final class GetAccountInfoResourceUsage implements QueryResourceUsageEstimator { private final CryptoOpsUsage cryptoOpsUsage; private final AliasManager aliasManager; + private final GlobalDynamicProperties dynamicProperties; private final RewardCalculator rewardCalculator; @Inject public GetAccountInfoResourceUsage( final CryptoOpsUsage cryptoOpsUsage, final AliasManager aliasManager, + final GlobalDynamicProperties dynamicProperties, final RewardCalculator rewardCalculator) { this.cryptoOpsUsage = cryptoOpsUsage; this.aliasManager = aliasManager; + this.dynamicProperties = dynamicProperties; this.rewardCalculator = rewardCalculator; } @@ -58,7 +62,12 @@ public FeeData usageGiven(final Query query, final StateView view, final Map opAnswer.addTokenBalances(TokenBalance.newBuilder() + .setTokenId(token.grpcId()) + .setDecimals(token.decimals()) + .setBalance(rel.getBalance()) + .build())); + } } return Response.newBuilder().setCryptogetAccountBalance(opAnswer).build(); diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswer.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswer.java index 47b890b62949..c8c76ca67a03 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswer.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswer.java @@ -22,6 +22,7 @@ import static com.hederahashgraph.api.proto.java.ResponseType.COST_ANSWER; import com.hedera.node.app.service.mono.context.primitives.StateView; +import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.ledger.accounts.AliasManager; import com.hedera.node.app.service.mono.ledger.accounts.staking.RewardCalculator; import com.hedera.node.app.service.mono.queries.AnswerService; @@ -52,14 +53,17 @@ public class GetAccountInfoAnswer implements AnswerService { private final OptionValidator optionValidator; private final AliasManager aliasManager; private final RewardCalculator rewardCalculator; + private final GlobalDynamicProperties dynamicProperties; @Inject public GetAccountInfoAnswer( final OptionValidator optionValidator, final AliasManager aliasManager, + final GlobalDynamicProperties dynamicProperties, final RewardCalculator rewardCalculator) { this.optionValidator = optionValidator; this.aliasManager = aliasManager; + this.dynamicProperties = dynamicProperties; this.rewardCalculator = rewardCalculator; } @@ -85,8 +89,13 @@ public Response responseGiven( response.setHeader(costAnswerHeader(OK, cost)); } else { final AccountID id = op.getAccountID(); - final var optionalInfo = - Objects.requireNonNull(view).infoForAccount(id, aliasManager, rewardCalculator); + final var optionalInfo = Objects.requireNonNull(view) + .infoForAccount( + id, + aliasManager, + dynamicProperties.maxTokensRelsPerInfoQuery(), + rewardCalculator, + dynamicProperties.areTokenBalancesEnabledInQueries()); if (optionalInfo.isPresent()) { response.setHeader(answerOnlyHeader(OK)); response.setAccountInfo(optionalInfo.get()); diff --git a/hedera-node/hedera-mono-service/src/main/resources/bootstrap.properties b/hedera-node/hedera-mono-service/src/main/resources/bootstrap.properties index 736ebcd5c7c3..1bc616eeab47 100644 --- a/hedera-node/hedera-mono-service/src/main/resources/bootstrap.properties +++ b/hedera-node/hedera-mono-service/src/main/resources/bootstrap.properties @@ -229,3 +229,4 @@ accounts.blocklist.path= staking.sumOfConsensusWeights=500 cache.cryptoTransfer.warmThreads=30 hedera.config.version=0 +tokens.balancesInQueries.enabled=true diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/primitives/StateViewTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/primitives/StateViewTest.java index 5ee226e723b7..0b7714e0cdc5 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/primitives/StateViewTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/primitives/StateViewTest.java @@ -18,6 +18,7 @@ import static com.hedera.node.app.service.evm.utils.EthSigsUtils.recoverAddressFromPubKey; import static com.hedera.node.app.service.mono.context.BasicTransactionContext.EMPTY_KEY; +import static com.hedera.node.app.service.mono.context.primitives.StateView.REMOVED_TOKEN; import static com.hedera.node.app.service.mono.state.submerkle.EntityId.MISSING_ENTITY_ID; import static com.hedera.node.app.service.mono.state.submerkle.RichInstant.fromJava; import static com.hedera.node.app.service.mono.txns.crypto.helpers.AllowanceHelpers.getCryptoGrantedAllowancesList; @@ -52,6 +53,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.argThat; import static org.mockito.BDDMockito.given; @@ -125,6 +127,7 @@ import com.hederahashgraph.api.proto.java.TokenFreezeStatus; import com.hederahashgraph.api.proto.java.TokenID; import com.hederahashgraph.api.proto.java.TokenKycStatus; +import com.hederahashgraph.api.proto.java.TokenRelationship; import com.hederahashgraph.api.proto.java.TransactionBody; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.utility.CommonUtils; @@ -153,6 +156,7 @@ class StateViewTest { private final RichInstant now = RichInstant.fromGrpc(Timestamp.newBuilder().setNanos(123123213).build()); private final int maxTokensFprAccountInfo = 10; + private final boolean aretokenBalancesEnabledInQueries = true; private final long expiry = 2_000_000L; private final byte[] data = "SOMETHING".getBytes(); private final byte[] expectedBytecode = "A Supermarket in California".getBytes(); @@ -615,10 +619,20 @@ void getsContractInfo() throws Exception { final var expectedTotalStorage = StateView.BYTES_PER_EVM_KEY_VALUE_PAIR * wellKnownNumKvPairs; given(networkInfo.ledgerId()).willReturn(ledgerId); + List rels = List.of(TokenRelationship.newBuilder() + .setTokenId(TokenID.newBuilder().setTokenNum(123L)) + .setFreezeStatus(TokenFreezeStatus.FreezeNotApplicable) + .setKycStatus(TokenKycStatus.KycNotApplicable) + .setBalance(321L) + .build()); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, contract, maxTokensFprAccountInfo)) + .thenReturn(rels); - final var info = - subject.infoForContract(cid, aliasManager, rewardCalculator).get(); + final var info = subject.infoForContract( + cid, aliasManager, maxTokensFprAccountInfo, rewardCalculator, aretokenBalancesEnabledInQueries) + .get(); assertEquals(cid, info.getContractID()); assertEquals(asAccount(cid), info.getAccountID()); @@ -629,6 +643,7 @@ void getsContractInfo() throws Exception { assertEquals(CommonUtils.hex(create2Address.toByteArray()), info.getContractAccountID()); assertEquals(contract.getExpiry(), info.getExpirationTime().getSeconds()); assertEquals(EntityId.fromIdentityCode(4), contract.getAutoRenewAccount()); + assertEquals(rels, info.getTokenRelationshipsList()); assertEquals(ledgerId, info.getLedgerId()); assertTrue(info.getDeleted()); assertEquals(expectedTotalStorage, info.getStorage()); @@ -638,15 +653,26 @@ void getsContractInfo() throws Exception { @Test void getsContractInfoWithoutCreate2Address() throws Exception { + final var target = EntityNum.fromContractId(cid); given(contracts.get(EntityNum.fromContractId(cid))).willReturn(contract); contract.setAlias(ByteString.EMPTY); final var expectedTotalStorage = StateView.BYTES_PER_EVM_KEY_VALUE_PAIR * wellKnownNumKvPairs; given(networkInfo.ledgerId()).willReturn(ledgerId); + List rels = List.of(TokenRelationship.newBuilder() + .setTokenId(TokenID.newBuilder().setTokenNum(123L)) + .setFreezeStatus(TokenFreezeStatus.FreezeNotApplicable) + .setKycStatus(TokenKycStatus.KycNotApplicable) + .setBalance(321L) + .build()); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, contract, maxTokensFprAccountInfo)) + .thenReturn(rels); - final var info = - subject.infoForContract(cid, aliasManager, rewardCalculator).get(); + final var info = subject.infoForContract( + cid, aliasManager, maxTokensFprAccountInfo, rewardCalculator, aretokenBalancesEnabledInQueries) + .get(); assertEquals(cid, info.getContractID()); assertEquals(asAccount(cid), info.getAccountID()); @@ -656,12 +682,44 @@ void getsContractInfoWithoutCreate2Address() throws Exception { assertEquals(contract.getBalance(), info.getBalance()); assertEquals(EntityIdUtils.asHexedEvmAddress(asAccount(cid)), info.getContractAccountID()); assertEquals(contract.getExpiry(), info.getExpirationTime().getSeconds()); + assertEquals(rels, info.getTokenRelationshipsList()); assertEquals(ledgerId, info.getLedgerId()); assertTrue(info.getDeleted()); assertEquals(expectedTotalStorage, info.getStorage()); mockedStatic.close(); } + @Test + void getTokenRelationship() { + given(tokens.getOrDefault(tokenNum, REMOVED_TOKEN)).willReturn(token); + given(tokens.getOrDefault(EntityNum.fromTokenId(nftTokenId), REMOVED_TOKEN)) + .willReturn(nft); + + List expectedRels = List.of( + TokenRelationship.newBuilder() + .setTokenId(tokenId) + .setSymbol("UnfrozenToken") + .setBalance(123L) + .setKycStatus(TokenKycStatus.Granted) + .setFreezeStatus(TokenFreezeStatus.Unfrozen) + .setAutomaticAssociation(true) + .setDecimals(1) + .build(), + TokenRelationship.newBuilder() + .setTokenId(nftTokenId) + .setSymbol("UnfrozenToken") + .setBalance(2L) + .setKycStatus(TokenKycStatus.Granted) + .setFreezeStatus(TokenFreezeStatus.Unfrozen) + .setAutomaticAssociation(false) + .setDecimals(1) + .build()); + + final var actualRels = StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo); + + assertEquals(expectedRels, actualRels); + } + @Test void getInfoForNftMissing() { final var nftID = @@ -701,6 +759,9 @@ void infoForRegularAccount() { given(contracts.get(EntityNum.fromAccountId(tokenAccountId))).willReturn(tokenAccount); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo)) + .thenReturn(Collections.emptyList()); given(networkInfo.ledgerId()).willReturn(ledgerId); final var expectedResponse = CryptoGetInfoResponse.AccountInfo.newBuilder() @@ -724,7 +785,12 @@ void infoForRegularAccount() { .build()) .build(); - final var actualResponse = subject.infoForAccount(tokenAccountId, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + tokenAccountId, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); assertEquals(expectedResponse, actualResponse.get()); mockedStatic.close(); @@ -745,6 +811,9 @@ void stakingInfoReturnedCorrectly() { given(contracts.get(EntityNum.fromAccountId(tokenAccountId))).willReturn(tokenAccount); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo)) + .thenReturn(Collections.emptyList()); given(networkInfo.ledgerId()).willReturn(ledgerId); final var expectedResponse = StakingInfo.newBuilder() @@ -753,7 +822,12 @@ void stakingInfoReturnedCorrectly() { .setStakePeriodStart(Timestamp.newBuilder().setSeconds(1_234_567L)) .build(); - final var actualResponse = subject.infoForAccount(tokenAccountId, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + tokenAccountId, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); assertEquals(expectedResponse, actualResponse.get().getStakingInfo()); mockedStatic.close(); @@ -767,6 +841,9 @@ void infoForExternallyOperatedAccount() { final var expectedAddress = CommonUtils.hex(recoverAddressFromPubKey(ecdsaKey)); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo)) + .thenReturn(Collections.emptyList()); given(networkInfo.ledgerId()).willReturn(ledgerId); final var expectedResponse = CryptoGetInfoResponse.AccountInfo.newBuilder() @@ -790,7 +867,12 @@ void infoForExternallyOperatedAccount() { .build()) .build(); - final var actualResponse = subject.infoForAccount(tokenAccountId, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + tokenAccountId, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); mockedStatic.close(); assertEquals(expectedResponse, actualResponse.get()); @@ -805,6 +887,9 @@ void infoForHollowAccount() { final var expectedAddress = CommonUtils.hex(pretendAddress); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo)) + .thenReturn(Collections.emptyList()); given(networkInfo.ledgerId()).willReturn(ledgerId); final var expectedResponse = CryptoGetInfoResponse.AccountInfo.newBuilder() @@ -827,7 +912,12 @@ void infoForHollowAccount() { .build()) .build(); - final var actualResponse = subject.infoForAccount(tokenAccountId, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + tokenAccountId, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); mockedStatic.close(); assertEquals(expectedResponse, actualResponse.get()); @@ -872,6 +962,9 @@ void infoForAccountWithAlias() { given(aliasManager.lookupIdBy(any())).willReturn(EntityNum.fromAccountId(tokenAccountId)); given(contracts.get(EntityNum.fromAccountId(tokenAccountId))).willReturn(tokenAccount); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo)) + .thenReturn(Collections.emptyList()); given(networkInfo.ledgerId()).willReturn(ledgerId); final var expectedResponse = CryptoGetInfoResponse.AccountInfo.newBuilder() @@ -895,7 +988,12 @@ void infoForAccountWithAlias() { .build()) .build(); - final var actualResponse = subject.infoForAccount(accountWithAlias, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + accountWithAlias, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); mockedStatic.close(); assertEquals(expectedResponse, actualResponse.get()); } @@ -906,6 +1004,9 @@ void infoForAccountWithEVMAddressAlias() { given(aliasManager.lookupIdBy(any())).willReturn(EntityNum.fromAccountId(tokenAccountId)); given(contracts.get(EntityNum.fromAccountId(tokenAccountId))).willReturn(tokenAccount); mockedStatic = mockStatic(StateView.class); + mockedStatic + .when(() -> StateView.tokenRels(subject, tokenAccount, maxTokensFprAccountInfo)) + .thenReturn(Collections.emptyList()); given(networkInfo.ledgerId()).willReturn(ledgerId); tokenAccount.setAlias(ByteStringUtils.wrapUnsafely(pretendAddress)); @@ -929,7 +1030,12 @@ void infoForAccountWithEVMAddressAlias() { .build()) .build(); - final var actualResponse = subject.infoForAccount(accountWithAlias, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + accountWithAlias, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); mockedStatic.close(); assertEquals(expectedResponse, actualResponse.get()); } @@ -945,7 +1051,12 @@ void numNftsOwnedWorksForExisting() { void infoForMissingAccount() { given(contracts.get(EntityNum.fromAccountId(tokenAccountId))).willReturn(null); - final var actualResponse = subject.infoForAccount(tokenAccountId, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + tokenAccountId, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); assertEquals(Optional.empty(), actualResponse); } @@ -957,7 +1068,12 @@ void infoForMissingAccountWithAlias() { given(aliasManager.lookupIdBy(any())).willReturn(mockedEntityNum); given(contracts.get(mockedEntityNum)).willReturn(null); - final var actualResponse = subject.infoForAccount(accountWithAlias, aliasManager, rewardCalculator); + final var actualResponse = subject.infoForAccount( + accountWithAlias, + aliasManager, + maxTokensFprAccountInfo, + rewardCalculator, + aretokenBalancesEnabledInQueries); assertEquals(Optional.empty(), actualResponse); } @@ -1024,7 +1140,9 @@ void getAliasesFromChildren() { void returnsEmptyOptionalIfContractMissing() { given(contracts.get(any())).willReturn(null); - assertTrue(subject.infoForContract(cid, aliasManager, rewardCalculator).isEmpty()); + assertTrue(subject.infoForContract( + cid, aliasManager, maxTokensFprAccountInfo, rewardCalculator, aretokenBalancesEnabledInQueries) + .isEmpty()); } @Test @@ -1032,10 +1150,12 @@ void handlesNullKey() { given(contracts.get(EntityNum.fromContractId(cid))).willReturn(contract); given(networkInfo.ledgerId()).willReturn(ledgerId); mockedStatic = mockStatic(StateView.class); + mockedStatic.when(() -> StateView.tokenRels(any(), any(), anyInt())).thenReturn(Collections.emptyList()); contract.setAccountKey(null); - final var info = - subject.infoForContract(cid, aliasManager, rewardCalculator).get(); + final var info = subject.infoForContract( + cid, aliasManager, maxTokensFprAccountInfo, rewardCalculator, aretokenBalancesEnabledInQueries) + .get(); assertFalse(info.hasAdminKey()); mockedStatic.close(); @@ -1046,10 +1166,12 @@ void handlesNullAutoRenewAccount() { given(contracts.get(EntityNum.fromContractId(cid))).willReturn(contract); given(networkInfo.ledgerId()).willReturn(ledgerId); mockedStatic = mockStatic(StateView.class); + mockedStatic.when(() -> StateView.tokenRels(any(), any(), anyInt())).thenReturn(Collections.emptyList()); contract.setAutoRenewAccount(null); - final var info = - subject.infoForContract(cid, aliasManager, rewardCalculator).get(); + final var info = subject.infoForContract( + cid, aliasManager, maxTokensFprAccountInfo, rewardCalculator, aretokenBalancesEnabledInQueries) + .get(); assertFalse(info.hasAutoRenewAccountId()); mockedStatic.close(); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/BootstrapPropertiesTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/BootstrapPropertiesTest.java index 2d8d90187563..1f904af587b1 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/BootstrapPropertiesTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/BootstrapPropertiesTest.java @@ -231,6 +231,7 @@ import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_USE_TREASURY_WILD_CARDS; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_USE_VIRTUAL_MERKLE; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_STORE_RELS_ON_DISK; +import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKEN_BALANCES_ENABLED_IN_QUERIES; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOPICS_MAX_NUM; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MAX_EXPORTS_PER_CONS_SEC; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MIN_FREE_TO_USED_GAS_THROTTLE_RATIO; @@ -558,7 +559,8 @@ class BootstrapPropertiesTest { entry(STAKING_SUM_OF_CONSENSUS_WEIGHTS, 500), entry(CACHE_CRYPTO_TRANSFER_WARM_THREADS, 30), entry(CONFIG_VERSION, 10), - entry(RECORDS_USE_CONSOLIDATED_FCQ, true)); + entry(RECORDS_USE_CONSOLIDATED_FCQ, true), + entry(TOKEN_BALANCES_ENABLED_IN_QUERIES, true)); @Test void containsProperty() { diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicPropertiesTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicPropertiesTest.java index f9513e8ff4d1..324ce2ba24fc 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicPropertiesTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/context/properties/GlobalDynamicPropertiesTest.java @@ -132,6 +132,7 @@ import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_MAX_METADATA_BYTES; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_MAX_QUERY_RANGE; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKENS_NFTS_MINT_THORTTLE_SCALE_FACTOR; +import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOKEN_BALANCES_ENABLED_IN_QUERIES; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TOPICS_MAX_NUM; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MAX_EXPORTS_PER_CONS_SEC; import static com.hedera.node.app.service.mono.context.properties.PropertyNames.TRACEABILITY_MIN_FREE_TO_USED_GAS_THROTTLE_RATIO; @@ -698,6 +699,7 @@ private void givenPropsWithSeed(final int i) { given(properties.getLongProperty(STAKING_MAX_STAKE_REWARDED)).willReturn(i + 100L); given(properties.getLongProperty(STAKING_REWARD_BALANCE_THRESHOLD)).willReturn(i + 101L); given(properties.getBooleanProperty(HEDERA_TXN_EIP2930_ENABLED)).willReturn(i % 2 == 0); + given(properties.getBooleanProperty(TOKEN_BALANCES_ENABLED_IN_QUERIES)).willReturn((i + 102) % 2 == 0); } private Set typesFor(final int i) { diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsageTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsageTest.java index c8e9f06715dc..4bd5b7ae450c 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsageTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/contract/queries/GetContractInfoResourceUsageTest.java @@ -90,7 +90,9 @@ void setup() { view = mock(StateView.class); given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokensPerContractInfo); - given(view.infoForContract(target, aliasManager, rewardCalculator)).willReturn(Optional.of(info)); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); + given(view.infoForContract(target, aliasManager, maxTokensPerContractInfo, rewardCalculator, true)) + .willReturn(Optional.of(info)); estimator = mock(ContractGetInfoUsage.class); mockedStatic = mockStatic(ContractGetInfoUsage.class); @@ -142,7 +144,8 @@ void setsInfoInQueryCxtIfPresent() { @Test void onlySetsContractInfoInQueryCxtIfFound() { final var queryCtx = new HashMap(); - given(view.infoForContract(target, aliasManager, rewardCalculator)).willReturn(Optional.empty()); + given(view.infoForContract(target, aliasManager, maxTokensPerContractInfo, rewardCalculator, true)) + .willReturn(Optional.empty()); final var actual = subject.usageGiven(satisfiableAnswerOnly, view, queryCtx); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsageTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsageTest.java index 274e7d44b521..690ccc01c4f0 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsageTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/fees/calculation/crypto/queries/GetAccountInfoResourceUsageTest.java @@ -95,7 +95,7 @@ class GetAccountInfoResourceUsageTest { @BeforeEach void setup() { - subject = new GetAccountInfoResourceUsage(cryptoOpsUsage, aliasManager, rewardCalculator); + subject = new GetAccountInfoResourceUsage(cryptoOpsUsage, aliasManager, dynamicProperties, rewardCalculator); } @Test @@ -113,7 +113,13 @@ void usesEstimator() { .setMaxAutomaticTokenAssociations(maxAutomaticAssociations) .build(); final var query = accountInfoQuery(a, ANSWER_ONLY); - given(view.infoForAccount(queryTarget, aliasManager, rewardCalculator)).willReturn(Optional.of(info)); + given(view.infoForAccount( + queryTarget, + aliasManager, + dynamicProperties.maxTokensRelsPerInfoQuery(), + rewardCalculator, + dynamicProperties.areTokenBalancesEnabledInQueries())) + .willReturn(Optional.of(info)); given(cryptoOpsUsage.cryptoInfoUsage(any(), any())).willReturn(expected); final var usage = subject.usageGiven(query, view); @@ -135,7 +141,13 @@ void usesEstimator() { @Test void returnsDefaultIfNoSuchAccount() { - given(view.infoForAccount(queryTarget, aliasManager, rewardCalculator)).willReturn(Optional.empty()); + given(view.infoForAccount( + queryTarget, + aliasManager, + dynamicProperties.maxTokensRelsPerInfoQuery(), + rewardCalculator, + dynamicProperties.areTokenBalancesEnabledInQueries())) + .willReturn(Optional.empty()); final var usage = subject.usageGiven(accountInfoQuery(a, ANSWER_ONLY), view); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/contract/GetContractInfoAnswerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/contract/GetContractInfoAnswerTest.java index 6424e5017ec2..3e0b60667546 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/contract/GetContractInfoAnswerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/contract/GetContractInfoAnswerTest.java @@ -30,6 +30,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.given; @@ -38,6 +40,7 @@ import com.google.protobuf.ByteString; import com.hedera.node.app.service.mono.context.primitives.StateView; +import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.ledger.accounts.AliasManager; import com.hedera.node.app.service.mono.ledger.accounts.staking.RewardCalculator; import com.hedera.node.app.service.mono.state.adapters.MerkleMapLike; @@ -72,6 +75,7 @@ class GetContractInfoAnswerTest { private Transaction paymentTxn; private final int maxTokenPerContractInfo = 10; + private final boolean areTokenBalancesEnabledInQueries = true; private final String node = "0.0.3"; private final String payer = "0.0.12345"; private final String target = "0.0.123"; @@ -90,6 +94,9 @@ class GetContractInfoAnswerTest { @Mock private MerkleMap contracts; + @Mock + private GlobalDynamicProperties dynamicProperties; + @Mock private RewardCalculator rewardCalculator; @@ -99,7 +106,6 @@ class GetContractInfoAnswerTest { @BeforeEach void setup() { - // we don't return the token relationships data anymore, since the field it is deprecated info = ContractGetInfoResponse.ContractInfo.newBuilder() .setLedgerId(ledgerId) .setContractID(asContract(target)) @@ -115,14 +121,21 @@ void setup() { .build()) .build(); - subject = new GetContractInfoAnswer(aliasManager, optionValidator, rewardCalculator); + subject = new GetContractInfoAnswer(aliasManager, optionValidator, dynamicProperties, rewardCalculator); } @Test void getsTheInfo() throws Throwable { + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokenPerContractInfo); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); final Query query = validQuery(ANSWER_ONLY, fee, target); - given(view.infoForContract(asContract(target), aliasManager, rewardCalculator)) + given(view.infoForContract( + asContract(target), + aliasManager, + maxTokenPerContractInfo, + rewardCalculator, + areTokenBalancesEnabledInQueries)) .willReturn(Optional.of(info)); // when: @@ -164,14 +177,21 @@ void getsInfoFromCtxWhenAvailable() throws Throwable { assertEquals(OK, opResponse.getHeader().getNodeTransactionPrecheckCode()); assertSame(info, opResponse.getContractInfo()); // and: - verify(view, never()).infoForContract(any(), any(), any()); + verify(view, never()).infoForContract(any(), any(), anyInt(), any(), anyBoolean()); } @Test void recognizesMissingInfoWhenNoCtxGiven() throws Throwable { + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokenPerContractInfo); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); final Query sensibleQuery = validQuery(ANSWER_ONLY, 5L, target); - given(view.infoForContract(asContract(target), aliasManager, rewardCalculator)) + given(view.infoForContract( + asContract(target), + aliasManager, + maxTokenPerContractInfo, + rewardCalculator, + areTokenBalancesEnabledInQueries)) .willReturn(Optional.empty()); // when: @@ -195,7 +215,7 @@ void recognizesMissingInfoWhenCtxGiven() throws Throwable { final ContractGetInfoResponse opResponse = response.getContractGetInfo(); assertTrue(opResponse.hasHeader(), "Missing response header!"); assertEquals(INVALID_CONTRACT_ID, opResponse.getHeader().getNodeTransactionPrecheckCode()); - verify(view, never()).infoForContract(any(), any(), any()); + verify(view, never()).infoForContract(any(), any(), anyInt(), any(), anyBoolean()); } @Test diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountBalanceAnswerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountBalanceAnswerTest.java index 85bc62d54d7a..926f5b5833f9 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountBalanceAnswerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountBalanceAnswerTest.java @@ -19,6 +19,7 @@ import static com.hedera.node.app.service.mono.utils.EntityNumPair.fromAccountTokenRel; import static com.hedera.test.utils.IdUtils.asAccount; import static com.hedera.test.utils.IdUtils.asContract; +import static com.hedera.test.utils.IdUtils.tokenBalanceWith; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoGetAccountBalance; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_DELETED; @@ -36,6 +37,7 @@ import com.hedera.node.app.hapi.utils.ByteStringUtils; import com.hedera.node.app.service.mono.context.MutableStateChildren; import com.hedera.node.app.service.mono.context.primitives.StateView; +import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.ledger.accounts.AliasManager; import com.hedera.node.app.service.mono.legacy.core.jproto.JEd25519Key; import com.hedera.node.app.service.mono.legacy.core.jproto.JKey; @@ -62,7 +64,7 @@ import com.hederahashgraph.api.proto.java.ResponseType; import com.hederahashgraph.api.proto.java.TokenID; import com.swirlds.merkle.map.MerkleMap; -import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -74,6 +76,9 @@ class GetAccountBalanceAnswerTest { @Mock private AccountStorageAdapter accounts; + @Mock + private GlobalDynamicProperties dynamicProperties; + @Mock private OptionValidator optionValidator; @@ -84,7 +89,7 @@ class GetAccountBalanceAnswerTest { @BeforeEach void setup() { - subject = new GetAccountBalanceAnswer(aliasManager, optionValidator); + subject = new GetAccountBalanceAnswer(aliasManager, optionValidator, dynamicProperties); } @Test @@ -231,6 +236,8 @@ void resolvesAliasIfExtant() { given(aliasManager.lookupIdBy(aliasId.getAlias())).willReturn(wellKnownId); accountV.setKey(EntityNum.fromAccountId(asAccount(accountIdLit))); given(accounts.get(wellKnownId)).willReturn(accountV); + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokenRels); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); final CryptoGetAccountBalanceQuery op = CryptoGetAccountBalanceQuery.newBuilder().setAccountID(aliasId).build(); @@ -243,7 +250,12 @@ void resolvesAliasIfExtant() { assertTrue(response.getCryptogetAccountBalance().hasHeader(), "Missing response header!"); assertEquals( - Collections.emptyList(), response.getCryptogetAccountBalance().getTokenBalancesList()); + List.of( + tokenBalanceWith(aToken, aBalance, 1), + tokenBalanceWith(bToken, bBalance, 2), + tokenBalanceWith(cToken, cBalance, 123), + tokenBalanceWith(dToken, dBalance, 123)), + response.getCryptogetAccountBalance().getTokenBalancesList()); assertEquals(OK, status); assertEquals(balance, answer); assertEquals( @@ -258,6 +270,8 @@ void answersWithAccountBalance() { final var wellKnownId = EntityNum.fromLong(12345L); accountV.setKey(EntityNum.fromAccountId(asAccount(accountIdLit))); given(accounts.get(wellKnownId)).willReturn(accountV); + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokenRels); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); // when: final Response response = subject.responseGiven(query, wellKnownView(), OK); @@ -267,9 +281,13 @@ void answersWithAccountBalance() { // expect: assertTrue(response.getCryptogetAccountBalance().hasHeader(), "Missing response header!"); - // we don't return token balances data in the query anymore since it is deprecated assertEquals( - Collections.emptyList(), response.getCryptogetAccountBalance().getTokenBalancesList()); + List.of( + tokenBalanceWith(aToken, aBalance, 1), + tokenBalanceWith(bToken, bBalance, 2), + tokenBalanceWith(cToken, cBalance, 123), + tokenBalanceWith(dToken, dBalance, 123)), + response.getCryptogetAccountBalance().getTokenBalancesList()); assertEquals(OK, status); assertEquals(balance, answer); assertEquals(id, response.getCryptogetAccountBalance().getAccountID()); @@ -283,6 +301,8 @@ void answersWithAccountBalanceWhenTheAccountIDIsContractID() { accountV.setKey(EntityNum.fromAccountId(asAccount(accountIdLit))); final var view = wellKnownView(); given(accounts.get(EntityNum.fromContractId(id))).willReturn(accountV); + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokenRels); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); // when: final Response response = subject.responseGiven(query, view, OK); @@ -293,7 +313,12 @@ void answersWithAccountBalanceWhenTheAccountIDIsContractID() { // expect: assertTrue(response.getCryptogetAccountBalance().hasHeader(), "Missing response header!"); assertEquals( - Collections.emptyList(), response.getCryptogetAccountBalance().getTokenBalancesList()); + List.of( + tokenBalanceWith(aToken, aBalance, 1), + tokenBalanceWith(bToken, bBalance, 2), + tokenBalanceWith(cToken, cBalance, 123), + tokenBalanceWith(dToken, dBalance, 123)), + response.getCryptogetAccountBalance().getTokenBalancesList()); assertEquals(OK, status); assertEquals(balance, answer); assertEquals( diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswerTest.java index c8df75204e47..d296926d406c 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/queries/crypto/GetAccountInfoAnswerTest.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.mono.queries.crypto; +import static com.hedera.node.app.service.mono.context.primitives.StateView.REMOVED_TOKEN; import static com.hedera.node.app.service.mono.utils.EntityIdUtils.asEvmAddress; import static com.hedera.node.app.service.mono.utils.EntityNumPair.fromAccountTokenRel; import static com.hedera.test.factories.scenarios.TxnHandlingScenario.COMPLEX_KEY_ACCOUNT_KT; @@ -34,6 +35,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -55,6 +58,7 @@ import com.hedera.node.app.service.mono.state.migration.TokenRelStorageAdapter; import com.hedera.node.app.service.mono.state.submerkle.EntityId; import com.hedera.node.app.service.mono.state.submerkle.FcTokenAllowanceId; +import com.hedera.node.app.service.mono.state.submerkle.RawTokenRelationship; import com.hedera.node.app.service.mono.store.schedule.ScheduleStore; import com.hedera.node.app.service.mono.txns.validation.OptionValidator; import com.hedera.node.app.service.mono.utils.EntityNum; @@ -73,7 +77,7 @@ import com.hederahashgraph.api.proto.java.Transaction; import com.swirlds.common.utility.CommonUtils; import com.swirlds.merkle.map.MerkleMap; -import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.TreeMap; import java.util.TreeSet; @@ -210,7 +214,7 @@ void setup() throws Throwable { view = new StateView(scheduleStore, children, networkInfo); - subject = new GetAccountInfoAnswer(optionValidator, aliasManager, rewardCalculator); + subject = new GetAccountInfoAnswer(optionValidator, aliasManager, dynamicProperties, rewardCalculator); } @Test @@ -245,11 +249,12 @@ void getsInvalidResponse() throws Throwable { @Test void identifiesFailInvalid() throws Throwable { + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokensPerAccountInfo); final Query query = validQuery(ANSWER_ONLY, fee, target); // and: final StateView view = mock(StateView.class); - given(view.infoForAccount(any(), any(), any())).willReturn(Optional.empty()); + given(view.infoForAccount(any(), any(), anyInt(), any(), anyBoolean())).willReturn(Optional.empty()); // when: final Response response = subject.responseGiven(query, view, OK, fee); @@ -263,12 +268,36 @@ void identifiesFailInvalid() throws Throwable { @Test @SuppressWarnings("unchecked") void getsTheAccountInfo() throws Throwable { - + given(dynamicProperties.maxTokensRelsPerInfoQuery()).willReturn(maxTokensPerAccountInfo); + given(dynamicProperties.areTokenBalancesEnabledInQueries()).willReturn(true); final MerkleMap tokens = mock(MerkleMap.class); children.setTokens(MerkleMapLike.from(tokens)); + + given(token.hasKycKey()).willReturn(true); + given(token.hasFreezeKey()).willReturn(true); + given(token.decimals()) + .willReturn(1) + .willReturn(2) + .willReturn(3) + .willReturn(1) + .willReturn(2) + .willReturn(3); + given(deletedToken.decimals()).willReturn(4); + given(tokens.getOrDefault(EntityNum.fromTokenId(firstToken), REMOVED_TOKEN)) + .willReturn(token); + given(tokens.getOrDefault(EntityNum.fromTokenId(secondToken), REMOVED_TOKEN)) + .willReturn(token); + given(tokens.getOrDefault(EntityNum.fromTokenId(thirdToken), REMOVED_TOKEN)) + .willReturn(token); + given(tokens.getOrDefault(EntityNum.fromTokenId(fourthToken), REMOVED_TOKEN)) + .willReturn(deletedToken); + given(tokens.getOrDefault(EntityNum.fromTokenId(missingToken), REMOVED_TOKEN)) + .willReturn(REMOVED_TOKEN); payerAccount.setKey(EntityNum.fromAccountId(payerId)); payerAccount.setHeadTokenId(firstToken.getTokenNum()); + given(token.symbol()).willReturn("HEYMA"); + given(deletedToken.symbol()).willReturn("THEWAY"); given(accounts.get(EntityNum.fromAccountId(asAccount(target)))).willReturn(payerAccount); given(networkInfo.ledgerId()).willReturn(ledgerId); given(rewardCalculator.epochSecondAtStartOfPeriod(12345678L)).willReturn(12345678L); @@ -305,8 +334,20 @@ void getsTheAccountInfo() throws Throwable { assertEquals(12345678L, info.getStakingInfo().getStakePeriodStart().getSeconds()); assertEquals(0L, info.getStakingInfo().getStakedToMe()); - // We no more return token association details in the response - assertEquals(Collections.emptyList(), info.getTokenRelationshipsList()); + // and: + assertEquals( + List.of( + new RawTokenRelationship(firstBalance, 0, 0, firstToken.getTokenNum(), true, true, true) + .asGrpcFor(token), + new RawTokenRelationship(secondBalance, 0, 0, secondToken.getTokenNum(), false, false, true) + .asGrpcFor(token), + new RawTokenRelationship(thirdBalance, 0, 0, thirdToken.getTokenNum(), true, true, false) + .asGrpcFor(token), + new RawTokenRelationship(fourthBalance, 0, 0, fourthToken.getTokenNum(), false, false, true) + .asGrpcFor(deletedToken), + new RawTokenRelationship(missingBalance, 0, 0, missingToken.getTokenNum(), false, false, false) + .asGrpcFor(REMOVED_TOKEN)), + info.getTokenRelationshipsList()); } @Test diff --git a/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties b/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties index 9fdac1d19def..a3c451d6f5bf 100644 --- a/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties +++ b/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties @@ -226,3 +226,4 @@ accounts.blocklist.path=hedera-node/data/onboard/evm-addresses-blocklist.csv staking.sumOfConsensusWeights=500 cache.cryptoTransfer.warmThreads=30 hedera.config.version=0 +tokens.balancesInQueries.enabled=true diff --git a/hedera-node/hedera-mono-service/src/test/resources/bootstrap/standard.properties b/hedera-node/hedera-mono-service/src/test/resources/bootstrap/standard.properties index 60ad5ba20718..cd2e64a8c48e 100644 --- a/hedera-node/hedera-mono-service/src/test/resources/bootstrap/standard.properties +++ b/hedera-node/hedera-mono-service/src/test/resources/bootstrap/standard.properties @@ -224,3 +224,4 @@ accounts.blocklist.path=hedera-node/data/onboard/evm-addresses-blocklist.csv staking.sumOfConsensusWeights=500 cache.cryptoTransfer.warmThreads=30 hedera.config.version=10 +tokens.balancesInQueries.enabled=true diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java index d9fb5268f193..17c8d9888057 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java @@ -21,6 +21,7 @@ import static com.hedera.hapi.node.base.ResponseType.ANSWER_ONLY; import static com.hedera.node.app.service.token.api.AccountSummariesApi.hexedEvmAddressOf; import static com.hedera.node.app.service.token.api.AccountSummariesApi.summarizeStakingInfo; +import static com.hedera.node.app.service.token.api.AccountSummariesApi.tokenRelationshipsOf; import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static java.util.Objects.requireNonNull; @@ -38,12 +39,15 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.ReadableStakingInfoStore; +import com.hedera.node.app.service.token.ReadableTokenRelationStore; +import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.PaidQueryHandler; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.data.LedgerConfig; import com.hedera.node.config.data.StakingConfig; +import com.hedera.node.config.data.TokensConfig; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; @@ -51,7 +55,6 @@ /** * This class contains all workflow-related functionality regarding {@link HederaFunctionality#CONTRACT_GET_INFO}. - * The token relationships field is deprecated and is no more returned by this query. */ @Singleton public class ContractGetInfoHandler extends PaidQueryHandler { @@ -91,9 +94,12 @@ public Response findResponse(@NonNull final QueryContext context, @NonNull final final var config = context.configuration(); contractGetInfo.contractInfo(infoFor( contract, + config.getConfigData(TokensConfig.class), config.getConfigData(LedgerConfig.class), config.getConfigData(StakingConfig.class), + context.createStore(ReadableTokenStore.class), context.createStore(ReadableStakingInfoStore.class), + context.createStore(ReadableTokenRelationStore.class), context.createStore(ReadableNetworkStakingRewardsStore.class))); } return Response.newBuilder().contractGetInfo(contractGetInfo).build(); @@ -101,9 +107,12 @@ public Response findResponse(@NonNull final QueryContext context, @NonNull final private ContractInfo infoFor( @NonNull final Account contract, + @NonNull final TokensConfig tokensConfig, @NonNull final LedgerConfig ledgerConfig, @NonNull final StakingConfig stakingConfig, + @NonNull final ReadableTokenStore tokenStore, @NonNull final ReadableStakingInfoStore stakingInfoStore, + @NonNull final ReadableTokenRelationStore tokenRelationStore, @NonNull final ReadableNetworkStakingRewardsStore stakingRewardsStore) { final var accountId = contract.accountIdOrThrow(); final var stakingInfo = summarizeStakingInfo( @@ -112,7 +121,8 @@ private ContractInfo infoFor( stakingRewardsStore.isStakingRewardsActivated(), contract, stakingInfoStore); - return ContractInfo.newBuilder() + final var maxReturnedRels = tokensConfig.maxRelsPerInfoQuery(); + final var builder = ContractInfo.newBuilder() .ledgerId(ledgerConfig.id()) .accountID(accountId) .contractID(ContractID.newBuilder().contractNum(accountId.accountNumOrThrow())) @@ -126,8 +136,11 @@ private ContractInfo infoFor( .expirationTime(Timestamp.newBuilder().seconds(contract.expirationSecond())) .maxAutomaticTokenAssociations(contract.maxAutoAssociations()) .contractAccountID(hexedEvmAddressOf(contract)) - .stakingInfo(stakingInfo) - .build(); + .stakingInfo(stakingInfo); + if (tokensConfig.balancesInQueriesEnabled()) { + builder.tokenRelationships(tokenRelationshipsOf(contract, tokenStore, tokenRelationStore, maxReturnedRels)); + } + return builder.build(); } private @Nullable Account contractFrom(@NonNull final QueryContext context) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java index bd9c4cda6a80..73e499e3b625 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountBalanceHandler.java @@ -24,9 +24,15 @@ import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.QueryHeader; import com.hedera.hapi.node.base.ResponseHeader; +import com.hedera.hapi.node.base.TokenBalance; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Token; +import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.hapi.node.token.CryptoGetAccountBalanceQuery; import com.hedera.hapi.node.token.CryptoGetAccountBalanceResponse; import com.hedera.hapi.node.transaction.Query; @@ -39,13 +45,14 @@ import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.data.TokensConfig; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; /** * This class contains all workflow-related functionality regarding {@link * HederaFunctionality#CRYPTO_GET_ACCOUNT_BALANCE}. - * The token balances field is deprecated and is no more returned by this query. */ @Singleton public class CryptoGetAccountBalanceHandler extends FreeQueryHandler { @@ -107,8 +114,54 @@ public Response findResponse(@NonNull final QueryContext context, @NonNull final : accountStore.getContractById(op.contractIDOrThrow()); requireNonNull(account); response.accountID(account.accountIdOrThrow()).balance(account.tinybarBalance()); + if (config.balancesInQueriesEnabled()) { + response.tokenBalances(getTokenBalances(config, account, tokenStore, tokenRelationStore)); + } } return Response.newBuilder().cryptogetAccountBalance(response).build(); } + + /** + * Calculate TokenBalance of an Account + * + * @param tokenConfig use TokenConfig to get maxRelsPerInfoQuery value + * @param account the account to be calculated from + * @param readableTokenStore readable token store + * @param tokenRelationStore token relation store + * @return ArrayList of TokenBalance object + */ + private List getTokenBalances( + @NonNull final TokensConfig tokenConfig, + @NonNull final Account account, + @NonNull final ReadableTokenStore readableTokenStore, + @NonNull final ReadableTokenRelationStore tokenRelationStore) { + final var ret = new ArrayList(); + var tokenId = account.headTokenId(); + int count = 0; + TokenRelation tokenRelation; + Token token; // token from readableToken store by tokenID + AccountID accountID; // build from accountNumber + TokenBalance tokenBalance; // created TokenBalance object + while (tokenId != null && !tokenId.equals(TokenID.DEFAULT) && count < tokenConfig.maxRelsPerInfoQuery()) { + accountID = account.accountId(); + tokenRelation = tokenRelationStore.get(accountID, tokenId); + if (tokenRelation != null) { + token = readableTokenStore.get(tokenId); + if (token != null) { + tokenBalance = TokenBalance.newBuilder() + .tokenId(tokenId) + .balance(tokenRelation.balance()) + .decimals(token.decimals()) + .build(); + ret.add(tokenBalance); + } + tokenId = tokenRelation.nextToken(); + } else { + break; + } + count++; + } + return ret; + } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java index d1eb9f961f35..4961f2e62e47 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoGetAccountInfoHandler.java @@ -21,6 +21,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseType.COST_ANSWER; +import static com.hedera.node.app.service.token.api.AccountSummariesApi.tokenRelationshipsOf; import static com.hedera.node.app.service.token.impl.handlers.transfer.TransferContextImpl.isOfEvmAddressSize; import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static java.util.Objects.requireNonNull; @@ -41,6 +42,8 @@ import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.ReadableStakingInfoStore; +import com.hedera.node.app.service.token.ReadableTokenRelationStore; +import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.api.AccountSummariesApi; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.PaidQueryHandler; @@ -48,6 +51,7 @@ import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.data.LedgerConfig; import com.hedera.node.config.data.StakingConfig; +import com.hedera.node.config.data.TokensConfig; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Optional; import javax.inject.Inject; @@ -56,7 +60,6 @@ /** * This class contains all workflow-related functionality regarding {@link * HederaFunctionality#CRYPTO_GET_INFO}. - * The token relationships field is deprecated and is no more returned by this query. */ @Singleton public class CryptoGetAccountInfoHandler extends PaidQueryHandler { @@ -133,9 +136,12 @@ private Optional infoForAccount( requireNonNull(accountID); requireNonNull(context); + final var tokensConfig = context.configuration().getConfigData(TokensConfig.class); final var ledgerConfig = context.configuration().getConfigData(LedgerConfig.class); final var stakingConfig = context.configuration().getConfigData(StakingConfig.class); final var accountStore = context.createStore(ReadableAccountStore.class); + final var tokenRelationStore = context.createStore(ReadableTokenRelationStore.class); + final var tokenStore = context.createStore(ReadableTokenStore.class); final var stakingInfoStore = context.createStore(ReadableStakingInfoStore.class); final var stakingRewardsStore = context.createStore(ReadableNetworkStakingRewardsStore.class); @@ -160,9 +166,14 @@ private Optional infoForAccount( info.ownedNfts(account.numberOwnedNfts()); info.maxAutomaticTokenAssociations(account.maxAutoAssociations()); info.ethereumNonce(account.ethereumNonce()); + // info.proxyAccountID(); Deprecated if (!isOfEvmAddressSize(account.alias())) { info.alias(account.alias()); } + if (tokensConfig.balancesInQueriesEnabled()) { + info.tokenRelationships(tokenRelationshipsOf( + account, tokenStore, tokenRelationStore, tokensConfig.maxRelsPerInfoQuery())); + } info.stakingInfo(AccountSummariesApi.summarizeStakingInfo( stakingConfig.rewardHistoryNumStoredPeriods(), stakingConfig.periodMins(), @@ -183,7 +194,7 @@ public Fees computeFees(@NonNull final QueryContext queryContext) { final var account = accountStore.getAccountById(accountId); return queryContext.feeCalculator().legacyCalculate(sigValueObj -> new GetAccountInfoResourceUsage( - cryptoOpsUsage, null, null) + cryptoOpsUsage, null, null, null) .usageGiven(query, account)); } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java index 9ddd37d30849..8f944fe7da05 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountBalanceHandlerTest.java @@ -20,10 +20,10 @@ import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.TOKENS; import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.TOKEN_RELS; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; @@ -34,6 +34,7 @@ import com.hedera.hapi.node.base.QueryHeader; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.ResponseHeader; +import com.hedera.hapi.node.base.TokenBalance; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.token.Account; @@ -56,6 +57,8 @@ import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -263,6 +266,7 @@ void getsResponseIfOkResponse() { given(readableStates1.get(ACCOUNTS)).willReturn(readableAccounts); ReadableAccountStore ReadableAccountStore = new ReadableAccountStoreImpl(readableStates1); + given(token1.decimals()).willReturn(100); final var readableToken = MapReadableKVState.builder(TOKENS) .value(asToken(3L), token1) .build(); @@ -306,12 +310,12 @@ void getsResponseIfOkResponse() { final var accountBalanceResponse = response.cryptogetAccountBalance(); assertEquals(ResponseCodeEnum.OK, accountBalanceResponse.header().nodeTransactionPrecheckCode()); assertEquals(expectedInfo.tinybarBalance(), accountBalanceResponse.balance()); - assertThat(accountBalanceResponse.tokenBalances()).isEmpty(); + assertIterableEquals(getExpectedTokenBalance(3L), accountBalanceResponse.tokenBalances()); } @Test - @DisplayName("Returns empty token relations in response") - void returnEmptyRelations() { + @DisplayName("check maxRelsPerInfoQuery in TokenConfig is correctly handled") + void checkConfigmaxRelsPerInfoQuery() { givenValidAccount(accountNum); final var responseHeader = ResponseHeader.newBuilder() .nodeTransactionPrecheckCode(ResponseCodeEnum.OK) @@ -324,6 +328,8 @@ void returnEmptyRelations() { given(readableStates1.get(ACCOUNTS)).willReturn(readableAccounts); ReadableAccountStore ReadableAccountStore = new ReadableAccountStoreImpl(readableStates1); + given(token1.decimals()).willReturn(100); + given(token2.decimals()).willReturn(50); final var readableToken = MapReadableKVState.builder(TOKENS) .value(asToken(3L), token1) .value(asToken(4L), token2) @@ -403,13 +409,42 @@ void returnEmptyRelations() { final var accountBalanceResponse = response.cryptogetAccountBalance(); assertEquals(ResponseCodeEnum.OK, accountBalanceResponse.header().nodeTransactionPrecheckCode()); assertEquals(expectedInfo.tinybarBalance(), accountBalanceResponse.balance()); - assertThat(accountBalanceResponse.tokenBalances()).isEmpty(); + assertIterableEquals(getExpectedTokenBalances(), accountBalanceResponse.tokenBalances()); + assertEquals(2, accountBalanceResponse.tokenBalances().size()); } private Account getExpectedInfo() { return Account.newBuilder().accountId(id).tinybarBalance(payerBalance).build(); } + private List getExpectedTokenBalance(long tokenNum) { + var ret = new ArrayList(); + final var tokenBalance = TokenBalance.newBuilder() + .tokenId(TokenID.newBuilder().tokenNum(tokenNum).build()) + .balance(1000) + .decimals(100) + .build(); + ret.add(tokenBalance); + return ret; + } + + private List getExpectedTokenBalances() { + var ret = new ArrayList(); + final var tokenBalance1 = TokenBalance.newBuilder() + .tokenId(TokenID.newBuilder().tokenNum(3L).build()) + .balance(1000) + .decimals(100) + .build(); + final var tokenBalance2 = TokenBalance.newBuilder() + .tokenId(TokenID.newBuilder().tokenNum(4L).build()) + .balance(100) + .decimals(50) + .build(); + ret.add(tokenBalance1); + ret.add(tokenBalance2); + return ret; + } + private Query createGetAccountBalanceQuery(final long accountId) { final var data = CryptoGetAccountBalanceQuery.newBuilder() .accountID(AccountID.newBuilder().accountNum(accountId).build()) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java index fc650649c627..84d3bca0318c 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoGetAccountInfoHandlerTest.java @@ -18,6 +18,8 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.FAIL_INVALID; import static com.hedera.hapi.node.base.ResponseType.ANSWER_ONLY; +import static com.hedera.hapi.node.base.TokenFreezeStatus.FREEZE_NOT_APPLICABLE; +import static com.hedera.hapi.node.base.TokenKycStatus.KYC_NOT_APPLICABLE; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.ACCOUNTS_KEY; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_INFO_KEY; import static com.hedera.node.app.service.token.impl.TokenServiceImpl.TOKENS_KEY; @@ -25,7 +27,6 @@ import static com.hedera.node.app.service.token.impl.handlers.BaseTokenHandler.asToken; import static com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil.NETWORK_REWARDS; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -41,6 +42,7 @@ import com.hedera.hapi.node.base.StakingInfo; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenRelationship; import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.state.token.NetworkStakingRewards; @@ -75,6 +77,8 @@ import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.utility.CommonUtils; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -243,6 +247,8 @@ void getsResponseIfOkResponse() { account = account.copyBuilder().stakedNodeId(0).declineReward(false).build(); setupAccountStore(); + given(token1.decimals()).willReturn(100); + given(token1.symbol()).willReturn("FOO"); given(token1.tokenId()).willReturn(asToken(3L)); setupTokenStore(token1); @@ -271,14 +277,18 @@ void getsResponseIfOkResponse() { } @Test - @DisplayName("returns empty token relations list") - void returnsEmptyTokenRelations() { + @DisplayName("check multiple token relations list") + void checkMulitpleTokenRelations() { final var responseHeader = getOkResponse(); final var expectedInfo = getExpectedAccountInfos(); account = account.copyBuilder().stakedNodeId(0).declineReward(false).build(); setupAccountStore(); + given(token1.decimals()).willReturn(100); + given(token2.decimals()).willReturn(50); + given(token1.symbol()).willReturn("FOO"); + given(token2.symbol()).willReturn("BAR"); given(token1.tokenId()).willReturn(asToken(3L)); given(token2.tokenId()).willReturn(asToken(4L)); setupTokenStore(token1, token2); @@ -329,8 +339,7 @@ void returnsEmptyTokenRelations() { assertEquals(ResponseCodeEnum.OK, cryptoGetInfoResponse.header().nodeTransactionPrecheckCode()); assertEquals(expectedInfo, cryptoGetInfoResponse.accountInfo()); - // We don't return token relationships information in queries - assertThat(cryptoGetInfoResponse.accountInfo().tokenRelationships()).isEmpty(); + assertEquals(2, cryptoGetInfoResponse.accountInfo().tokenRelationships().size()); } @Test @@ -344,6 +353,8 @@ void testStakeNumber() { .build(); setupAccountStore(); + given(token1.decimals()).willReturn(100); + given(token1.symbol()).willReturn("FOO"); given(token1.tokenId()).willReturn(asToken(3L)); setupTokenStore(token1); @@ -384,6 +395,8 @@ void testEvmAddressAlias() { .build(); setupAccountStore(); + given(token1.decimals()).willReturn(100); + given(token1.symbol()).willReturn("FOO"); given(token1.tokenId()).willReturn(asToken(3L)); setupTokenStore(token1); @@ -487,6 +500,7 @@ private AccountInfo getExpectedAccountInfo() { .ethereumNonce(0) .alias(alias.alias()) .contractAccountID("0000000000000000000000000000000000000003") + .tokenRelationships(getExpectedTokenRelationship()) .stakingInfo(getExpectedStakingInfo()) .build(); } @@ -507,6 +521,7 @@ private AccountInfo getExpectedAccountInfo2() { .ethereumNonce(0) .alias(alias.alias()) .contractAccountID("0000000000000000000000000000000000000003") + .tokenRelationships(getExpectedTokenRelationship()) .stakingInfo(getExpectedStakingInfo2()) .build(); } @@ -526,6 +541,7 @@ private AccountInfo getExpectedAccountInfoEvm(Bytes evmAddress) { .maxAutomaticTokenAssociations(10) .ethereumNonce(0) .contractAccountID("6aeb3773ea468a814d954e6dec795bfee7d76e26") + .tokenRelationships(getExpectedTokenRelationship()) .stakingInfo(getExpectedStakingInfo()) .build(); } @@ -546,10 +562,52 @@ private AccountInfo getExpectedAccountInfos() { .ethereumNonce(0) .alias(alias.alias()) .contractAccountID("0000000000000000000000000000000000000003") + .tokenRelationships(getExpectedTokenRelationships()) .stakingInfo(getExpectedStakingInfo()) .build(); } + private List getExpectedTokenRelationship() { + var ret = new ArrayList(); + final var tokenRelationship1 = TokenRelationship.newBuilder() + .tokenId(TokenID.newBuilder().tokenNum(3L).build()) + .symbol("FOO") + .balance(1000) + .decimals(100) + .kycStatus(KYC_NOT_APPLICABLE) + .freezeStatus(FREEZE_NOT_APPLICABLE) + .automaticAssociation(true) + .build(); + ret.add(tokenRelationship1); + return ret; + } + + private List getExpectedTokenRelationships() { + var ret = new ArrayList(); + final var tokenRelationship1 = TokenRelationship.newBuilder() + .tokenId(TokenID.newBuilder().tokenNum(3L).build()) + .symbol("FOO") + .balance(1000) + .decimals(100) + .kycStatus(KYC_NOT_APPLICABLE) + .freezeStatus(FREEZE_NOT_APPLICABLE) + .automaticAssociation(true) + .build(); + final var tokenRelationship2 = TokenRelationship.newBuilder() + .tokenId(TokenID.newBuilder().tokenNum(4L).build()) + .symbol("BAR") + .balance(100) + .decimals(50) + .kycStatus(KYC_NOT_APPLICABLE) + .freezeStatus(FREEZE_NOT_APPLICABLE) + .automaticAssociation(true) + .build(); + + ret.add(tokenRelationship1); + ret.add(tokenRelationship2); + return ret; + } + private StakingInfo getExpectedStakingInfo() { return StakingInfo.newBuilder() .declineReward(false) diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java index 1bb0d233bd10..b8aeb52cfca1 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java @@ -16,6 +16,12 @@ package com.hedera.node.app.service.token.api; +import static com.hedera.hapi.node.base.TokenFreezeStatus.FREEZE_NOT_APPLICABLE; +import static com.hedera.hapi.node.base.TokenFreezeStatus.FROZEN; +import static com.hedera.hapi.node.base.TokenFreezeStatus.UNFROZEN; +import static com.hedera.hapi.node.base.TokenKycStatus.GRANTED; +import static com.hedera.hapi.node.base.TokenKycStatus.KYC_NOT_APPLICABLE; +import static com.hedera.hapi.node.base.TokenKycStatus.REVOKED; import static com.hedera.node.app.hapi.utils.CommonUtils.asEvmAddress; import static com.hedera.node.app.service.evm.accounts.HederaEvmContractAliases.EVM_ADDRESS_LEN; import static com.hedera.node.app.service.token.api.StakingRewardsApi.epochSecondAtStartOfPeriod; @@ -28,12 +34,22 @@ import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.StakingInfo; import com.hedera.hapi.node.base.Timestamp; +import com.hedera.hapi.node.base.TokenFreezeStatus; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.base.TokenKycStatus; +import com.hedera.hapi.node.base.TokenRelationship; import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.Token; +import com.hedera.hapi.node.state.token.TokenRelation; import com.hedera.node.app.service.evm.utils.EthSigsUtils; import com.hedera.node.app.service.token.ReadableStakingInfoStore; +import com.hedera.node.app.service.token.ReadableTokenRelationStore; +import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; import java.util.function.UnaryOperator; /** @@ -65,6 +81,72 @@ static String hexedEvmAddressOf(@NonNull final Account account) { } } + /** + * Returns up to the given limit of token relationships for the given account, relative to the provided + * {@link ReadableTokenStore} and {@link ReadableTokenRelationStore}. + * + * @param account the account to get token relationships for + * @param readableTokenStore the readable token store + * @param tokenRelationStore the readable token relation store + * @param limit the maximum number of token relationships to return + * @return the token relationships for the given account + */ + static List tokenRelationshipsOf( + @NonNull final Account account, + @NonNull final ReadableTokenStore readableTokenStore, + @NonNull final ReadableTokenRelationStore tokenRelationStore, + final long limit) { + requireNonNull(account); + requireNonNull(tokenRelationStore); + requireNonNull(readableTokenStore); + + final var ret = new ArrayList(); + var tokenId = account.headTokenId(); + int count = 0; + TokenRelation tokenRelation; + Token token; // token from readableToken store by tokenID + AccountID accountID; // build from accountNumber + while (tokenId != null && !tokenId.equals(TokenID.DEFAULT) && count < limit) { + accountID = account.accountId(); + tokenRelation = tokenRelationStore.get(accountID, tokenId); + if (tokenRelation != null) { + token = readableTokenStore.get(tokenId); + if (token != null) { + addTokenRelation(ret, token, tokenRelation, tokenId); + } + tokenId = tokenRelation.nextToken(); + } else { + break; + } + count++; + } + return ret; + } + + private static void addTokenRelation( + ArrayList ret, Token token, TokenRelation tokenRelation, TokenID tokenId) { + TokenFreezeStatus freezeStatus = FREEZE_NOT_APPLICABLE; + if (token.hasFreezeKey()) { + freezeStatus = tokenRelation.frozen() ? FROZEN : UNFROZEN; + } + + TokenKycStatus kycStatus = KYC_NOT_APPLICABLE; + if (token.hasKycKey()) { + kycStatus = tokenRelation.kycGranted() ? GRANTED : REVOKED; + } + + final var tokenRelationship = TokenRelationship.newBuilder() + .tokenId(tokenId) + .symbol(token.symbol()) + .balance(tokenRelation.balance()) + .decimals(token.decimals()) + .kycStatus(kycStatus) + .freezeStatus(freezeStatus) + .automaticAssociation(tokenRelation.automaticAssociation()) + .build(); + ret.add(tokenRelationship); + } + /** * Tries to recover EVM address from an account key with a given recovery function. * diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java index 8da1f1e67c2a..65f05da27af9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/contract/HapiGetContractInfo.java @@ -159,6 +159,7 @@ protected void assertExpectationsGiven(HapiSpec spec) throws Throwable { assertExpectedRels(contract, relationships, actualTokenRels, spec); assertNoUnexpectedRels(contract, absentRelationships, actualTokenRels, spec); actualInfo = actualInfo.toBuilder() + .clearTokenRelationships() .addAllTokenRelationships(actualTokenRels) .build(); } From ab50ce363d714a08edcc9934bb4595a0704eb012 Mon Sep 17 00:00:00 2001 From: Stanimir Stoyanov Date: Sun, 24 Dec 2023 19:46:58 +0200 Subject: [PATCH 39/80] test: Enable fuzzy matching in LogsSuite (#10630) Signed-off-by: Stanimir Stoyanov --- .../test-clients/record-snapshots/Logs.json | 1 + .../suites/contract/records/LogsSuite.java | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 hedera-node/test-clients/record-snapshots/Logs.json diff --git a/hedera-node/test-clients/record-snapshots/Logs.json b/hedera-node/test-clients/record-snapshots/Logs.json new file mode 100644 index 000000000000..cc6abb22483d --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/Logs.json @@ -0,0 +1 @@ +{"specSnapshots":{"log0Works":{"placeholderNum":1126,"encodedItems":[{"b64Body":"Cg8KCQjc1ZWsBhDHAhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiYpPCvBhDgytHvARptCiISIJXluEUb4YMQBA5eFzfZjmfjWKXLt4rf9LOsVNkJt2uhCiM6IQLXr0r3loqPqMHExMhrpfCxo86ZGtSB5x3y870imBfYzgoiEiDf1GFNx88QTzi2ok9ab9ivOgYNJOY0d11XRB3Vmf2m5CIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOcIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDOTVhie6P/3Y6fS28LnR+LG/kijvEy0uGabllXrEsOt7tQsyO9xH+Mymn9ZzK6DpEaDAiY1pWsBhCbxLf7ASIPCgkI3NWVrAYQxwISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjd1ZWsBhDLAhICGAISAhgDGO7GyDQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB+g4KAxjnCCLyDjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM5OTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzJhNGMwODk2MTQ2MTAwNWM1NzgwNjM3OGI5YTFmMzE0NjEwMDc4NTc4MDYzYzY3MGY4NjQxNDYxMDA5NDU3ODA2M2M2ODNkNmEzMTQ2MTAwYjA1NzgwNjNkMDUyODVkNDE0NjEwMGNjNTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDIxMjU2NWI2MTAwZTg1NjViMDA1YjYxMDA5MjYwMDQ4MDM2MDM4MTAxOTA2MTAwOGQ5MTkwNjEwMjY1NTY1YjYxMDExYzU2NWIwMDViNjEwMGFlNjAwNDgwMzYwMzgxMDE5MDYxMDBhOTkxOTA2MTAyYTU1NjViNjEwMTRlNTY1YjAwNWI2MTAwY2E2MDA0ODAzNjAzODEwMTkwNjEwMGM1OTE5MDYxMDJkMjU2NWI2MTAxN2U1NjViMDA1YjYxMDBlNjYwMDQ4MDM2MDM4MTAxOTA2MTAwZTE5MTkwNjEwMmE1NTY1YjYxMDFiZTU2NWIwMDViODA4Mjg0N2ZhOGZiMmY5YTQ5YWZjMmVhMTQ4MzE5MzI2YzcyMDg5NjU1NTUxNTFkYjJjZTEzN2MwNTE3NDA5ODczMGFlZGMzNjA0MDUxNjA0MDUxODA5MTAzOTBhNDUwNTA1MDU2NWI4MDgyN2Y1MTNkYWQ3NTgyZmQ4YjExYzhmNGQwNWU2ZTdhYzhjYWFhNWViNjkwZTkxNzNkZDJiZWQ5NmI1YWUwZTBkMDI0NjA0MDUxNjA0MDUxODA5MTAzOTBhMzUwNTA1NjViODA3ZjQ2NjkyYzBlNTljYTljZDFhZDhmOTg0YTlkMTE3MTVlYzgzNDI0Mzk4YjdlZWQ0ZTA1YzhjZTg0NjYyNDE1YTg2MDQwNTE2MDQwNTE4MDkxMDM5MGEyNTA1NjViODE4Mzg1N2Y3NWU3ZDk1Y2Q3MjU4OGFmNDljZTJlNGI3ZjAwNGJjZTkxNmQ0MjI5OTlhZGYyNjJhNjQwZTQyMzlhYWIwMGM3ODQ2MDQwNTE2MTAxYjA5MTkwNjEwMzQ4NTY1YjYwNDA1MTgwOTEwMzkwYTQ1MDUwNTA1MDU2NWI4MDYwNDA1MTYxMDFjYzkxOTA2MTAzNDg1NjViNjA0MDUxODA5MTAzOTBhMDUwNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDFlZjgxNjEwMWRjNTY1YjgxMTQ2MTAxZmE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyMGM4MTYxMDFlNjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTAyMmI1NzYxMDIyYTYxMDFkNzU2NWI1YjYwMDA2MTAyMzk4NjgyODcwMTYxMDFmZDU2NWI5MzUwNTA2MDIwNjEwMjRhODY4Mjg3MDE2MTAxZmQ1NjViOTI1MDUwNjA0MDYxMDI1Yjg2ODI4NzAxNjEwMWZkNTY1YjkxNTA1MDkyNTA5MjUwOTI1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyN2M1NzYxMDI3YjYxMDFkNzU2NWI1YjYwMDA2MTAyOGE4NTgyODYwMTYxMDFmZDU2NWI5MjUwNTA2MDIwNjEwMjliODU4Mjg2MDE2MTAxZmQ1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyYmI1NzYxMDJiYTYxMDFkNzU2NWI1YjYwMDA2MTAyYzk4NDgyODUwMTYxMDFmZDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwODA2MDgwODU4NzAzMTIxNTYxMDJlYzU3NjEwMmViNjEwMWQ3NTY1YjViNjAwMDYxMDJmYTg3ODI4ODAxNjEwMWZkNTY1Yjk0NTA1MDYwMjA2MTAzMGI4NzgyODgwMTYxMDFmZDU2NWI5MzUwNTA2MDQwNjEwMzFjODc4Mjg4MDE2MTAxZmQ1NjViOTI1MDUwNjA2MDYxMDMyZDg3ODI4ODAxNjEwMWZkNTY1YjkxNTA1MDkyOTU5MTk0NTA5MjUwNTY1YjYxMDM0MjgxNjEwMWRjNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAzNWQ2MDAwODMwMTg0NjEwMzM5NTY1YjkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNDQ2NmQ2MGY5OTE5N2U2MDZiMzdhYWMwM2M5YjU3NzRiOTE5YzQ1ZmZlNjk3ODFhMmZiOGEyOGY1OGM5YjYxNDY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwcH6aEbUuSP/ObY38T2Xey/IGYP/sKQOMYOtlxNZUyg5ZM2QCEybBkWBPHdZ/VmlMGgsImdaVrAYQk/DoHyIPCgkI3dWVrAYQywISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjd1ZWsBhDNAhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOcIGiISIDap9wLB9Q68wcOUC4dI/tP/EeUZWrQPH38GfmsKPiRrIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGOgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCLRdB17e453Bv98gzRg0jRg7r1VZVZJUDQiwy0tfF23QDqHRlP2Aky/Sz2ENvIVi8aDAiZ1pWsBhDTl6+hAiIPCgkI3dWVrAYQzQISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs4JCgMY6AgSmQdggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjKkwIlhRhAFxXgGN4uaHzFGEAeFeAY8Zw+GQUYQCUV4BjxoPWoxRhALBXgGPQUoXUFGEAzFdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQISVlthAOhWWwBbYQCSYASANgOBAZBhAI2RkGECZVZbYQEcVlsAW2EArmAEgDYDgQGQYQCpkZBhAqVWW2EBTlZbAFthAMpgBIA2A4EBkGEAxZGQYQLSVlthAX5WWwBbYQDmYASANgOBAZBhAOGRkGECpVZbYQG+VlsAW4CChH+o+y+aSa/C6hSDGTJscgiWVVUVHbLOE3wFF0CYcwrtw2BAUWBAUYCRA5CkUFBQVluAgn9RPa11gv2LEcj00F5uesjKql62kOkXPdK+2Wta4ODQJGBAUWBAUYCRA5CjUFBWW4B/RmksDlnKnNGtj5hKnRFxXsg0JDmLfu1OBcjOhGYkFahgQFFgQFGAkQOQolBWW4GDhX9159lc1yWIr0nOLkt/AEvOkW1CKZmt8mKmQOQjmqsAx4RgQFFhAbCRkGEDSFZbYEBRgJEDkKRQUFBQVluAYEBRYQHMkZBhA0hWW2BAUYCRA5CgUFZbYACA/VtgAIGQUJGQUFZbYQHvgWEB3FZbgRRhAfpXYACA/VtQVltgAIE1kFBhAgyBYQHmVluSkVBQVltgAIBgAGBghIYDEhVhAitXYQIqYQHXVltbYABhAjmGgocBYQH9VluTUFBgIGECSoaChwFhAf1WW5JQUGBAYQJbhoKHAWEB/VZbkVBQklCSUJJWW2AAgGBAg4UDEhVhAnxXYQJ7YQHXVltbYABhAoqFgoYBYQH9VluSUFBgIGECm4WChgFhAf1WW5FQUJJQkpBQVltgAGAggoQDEhVhArtXYQK6YQHXVltbYABhAsmEgoUBYQH9VluRUFCSkVBQVltgAIBgAIBggIWHAxIVYQLsV2EC62EB11ZbW2AAYQL6h4KIAWEB/VZblFBQYCBhAwuHgogBYQH9VluTUFBgQGEDHIeCiAFhAf1WW5JQUGBgYQMth4KIAWEB/VZbkVBQkpWRlFCSUFZbYQNCgWEB3FZbglJQUFZbYABgIIIBkFBhA11gAIMBhGEDOVZbkpFQUFb+omRpcGZzWCISIERm1g+ZGX5gazeqwDybV3S5GcRf/ml4Gi+4oo9YybYUZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOgIShYKFAAAAAAAAAAAAAAAAAAAAAAAAARocgcKAxjoCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQje1ZWsBhDPAhICGAISAhgDGJirbCICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOi8KAxjoCBCowwEiJNBShdQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw==","b64Record":"CiUIFiIDGOgIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCNwVN95o5DgMKcbxW+cYRaaniy0HBevm9a+VF0xZvm6n99/EL+GpkS7sT2ZUvggK0aCwia1pWsBhCD+a0pIg8KCQje1ZWsBhDPAhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMK+6YTq5BAoDGOgIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjZrwEyqgIKAxjoCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPUhYKCQoCGAIQ3fTCAQoJCgIYYhDe9MIB"}]},"log1Works":{"placeholderNum":1129,"encodedItems":[{"b64Body":"Cg8KCQji1ZWsBhDjAhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiepPCvBhDA8brhARptCiISIPqmM0hg/n/jCgZWaTnyINyrPifxTbYyKRyqpJSDEn4SCiM6IQMxS8isWdV8YCG8vN9KZFgTbaUBYkymQSMVPiYFz8lPOwoiEiDgW6ht9qiKmrFdA7sBXHoxXSBAo8xTrqE4ljtOrdGvMiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOoIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCvYAdKzHeJrDe4T2Wq4Qlreze7NMCBcMcMpQDWvPu4GPYOMXvAu6Tv+puRNWzhyzgaDAie1pWsBhCrqd36ASIPCgkI4tWVrAYQ4wISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQji1ZWsBhDnAhICGAISAhgDGO7GyDQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB+g4KAxjqCCLyDjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM5OTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzJhNGMwODk2MTQ2MTAwNWM1NzgwNjM3OGI5YTFmMzE0NjEwMDc4NTc4MDYzYzY3MGY4NjQxNDYxMDA5NDU3ODA2M2M2ODNkNmEzMTQ2MTAwYjA1NzgwNjNkMDUyODVkNDE0NjEwMGNjNTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDIxMjU2NWI2MTAwZTg1NjViMDA1YjYxMDA5MjYwMDQ4MDM2MDM4MTAxOTA2MTAwOGQ5MTkwNjEwMjY1NTY1YjYxMDExYzU2NWIwMDViNjEwMGFlNjAwNDgwMzYwMzgxMDE5MDYxMDBhOTkxOTA2MTAyYTU1NjViNjEwMTRlNTY1YjAwNWI2MTAwY2E2MDA0ODAzNjAzODEwMTkwNjEwMGM1OTE5MDYxMDJkMjU2NWI2MTAxN2U1NjViMDA1YjYxMDBlNjYwMDQ4MDM2MDM4MTAxOTA2MTAwZTE5MTkwNjEwMmE1NTY1YjYxMDFiZTU2NWIwMDViODA4Mjg0N2ZhOGZiMmY5YTQ5YWZjMmVhMTQ4MzE5MzI2YzcyMDg5NjU1NTUxNTFkYjJjZTEzN2MwNTE3NDA5ODczMGFlZGMzNjA0MDUxNjA0MDUxODA5MTAzOTBhNDUwNTA1MDU2NWI4MDgyN2Y1MTNkYWQ3NTgyZmQ4YjExYzhmNGQwNWU2ZTdhYzhjYWFhNWViNjkwZTkxNzNkZDJiZWQ5NmI1YWUwZTBkMDI0NjA0MDUxNjA0MDUxODA5MTAzOTBhMzUwNTA1NjViODA3ZjQ2NjkyYzBlNTljYTljZDFhZDhmOTg0YTlkMTE3MTVlYzgzNDI0Mzk4YjdlZWQ0ZTA1YzhjZTg0NjYyNDE1YTg2MDQwNTE2MDQwNTE4MDkxMDM5MGEyNTA1NjViODE4Mzg1N2Y3NWU3ZDk1Y2Q3MjU4OGFmNDljZTJlNGI3ZjAwNGJjZTkxNmQ0MjI5OTlhZGYyNjJhNjQwZTQyMzlhYWIwMGM3ODQ2MDQwNTE2MTAxYjA5MTkwNjEwMzQ4NTY1YjYwNDA1MTgwOTEwMzkwYTQ1MDUwNTA1MDU2NWI4MDYwNDA1MTYxMDFjYzkxOTA2MTAzNDg1NjViNjA0MDUxODA5MTAzOTBhMDUwNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDFlZjgxNjEwMWRjNTY1YjgxMTQ2MTAxZmE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyMGM4MTYxMDFlNjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTAyMmI1NzYxMDIyYTYxMDFkNzU2NWI1YjYwMDA2MTAyMzk4NjgyODcwMTYxMDFmZDU2NWI5MzUwNTA2MDIwNjEwMjRhODY4Mjg3MDE2MTAxZmQ1NjViOTI1MDUwNjA0MDYxMDI1Yjg2ODI4NzAxNjEwMWZkNTY1YjkxNTA1MDkyNTA5MjUwOTI1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyN2M1NzYxMDI3YjYxMDFkNzU2NWI1YjYwMDA2MTAyOGE4NTgyODYwMTYxMDFmZDU2NWI5MjUwNTA2MDIwNjEwMjliODU4Mjg2MDE2MTAxZmQ1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyYmI1NzYxMDJiYTYxMDFkNzU2NWI1YjYwMDA2MTAyYzk4NDgyODUwMTYxMDFmZDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwODA2MDgwODU4NzAzMTIxNTYxMDJlYzU3NjEwMmViNjEwMWQ3NTY1YjViNjAwMDYxMDJmYTg3ODI4ODAxNjEwMWZkNTY1Yjk0NTA1MDYwMjA2MTAzMGI4NzgyODgwMTYxMDFmZDU2NWI5MzUwNTA2MDQwNjEwMzFjODc4Mjg4MDE2MTAxZmQ1NjViOTI1MDUwNjA2MDYxMDMyZDg3ODI4ODAxNjEwMWZkNTY1YjkxNTA1MDkyOTU5MTk0NTA5MjUwNTY1YjYxMDM0MjgxNjEwMWRjNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAzNWQ2MDAwODMwMTg0NjEwMzM5NTY1YjkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNDQ2NmQ2MGY5OTE5N2U2MDZiMzdhYWMwM2M5YjU3NzRiOTE5YzQ1ZmZlNjk3ODFhMmZiOGEyOGY1OGM5YjYxNDY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw8MO6cQ1InVEOOJ9GBAzpzjDse20vxT0NvHOBM45MyxcLz3B5c3ChCGLwY/f2bMpTGgsIn9aVrAYQ66jsAiIPCgkI4tWVrAYQ5wISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjj1ZWsBhDpAhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOoIGiISIOh3+7dXcuOAcpt8+5DvADfpRd3k1SisL4OyWUZo0TOYIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGOsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC5Hp/j7YCiTsFewgESpWNmNj774WYL9dzd7sffRvXg/oOLk2Cc1IVGS7gMjY/KUSEaDAif1pWsBhCL5KGEAiIPCgkI49WVrAYQ6QISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs4JCgMY6wgSmQdggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjKkwIlhRhAFxXgGN4uaHzFGEAeFeAY8Zw+GQUYQCUV4BjxoPWoxRhALBXgGPQUoXUFGEAzFdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQISVlthAOhWWwBbYQCSYASANgOBAZBhAI2RkGECZVZbYQEcVlsAW2EArmAEgDYDgQGQYQCpkZBhAqVWW2EBTlZbAFthAMpgBIA2A4EBkGEAxZGQYQLSVlthAX5WWwBbYQDmYASANgOBAZBhAOGRkGECpVZbYQG+VlsAW4CChH+o+y+aSa/C6hSDGTJscgiWVVUVHbLOE3wFF0CYcwrtw2BAUWBAUYCRA5CkUFBQVluAgn9RPa11gv2LEcj00F5uesjKql62kOkXPdK+2Wta4ODQJGBAUWBAUYCRA5CjUFBWW4B/RmksDlnKnNGtj5hKnRFxXsg0JDmLfu1OBcjOhGYkFahgQFFgQFGAkQOQolBWW4GDhX9159lc1yWIr0nOLkt/AEvOkW1CKZmt8mKmQOQjmqsAx4RgQFFhAbCRkGEDSFZbYEBRgJEDkKRQUFBQVluAYEBRYQHMkZBhA0hWW2BAUYCRA5CgUFZbYACA/VtgAIGQUJGQUFZbYQHvgWEB3FZbgRRhAfpXYACA/VtQVltgAIE1kFBhAgyBYQHmVluSkVBQVltgAIBgAGBghIYDEhVhAitXYQIqYQHXVltbYABhAjmGgocBYQH9VluTUFBgIGECSoaChwFhAf1WW5JQUGBAYQJbhoKHAWEB/VZbkVBQklCSUJJWW2AAgGBAg4UDEhVhAnxXYQJ7YQHXVltbYABhAoqFgoYBYQH9VluSUFBgIGECm4WChgFhAf1WW5FQUJJQkpBQVltgAGAggoQDEhVhArtXYQK6YQHXVltbYABhAsmEgoUBYQH9VluRUFCSkVBQVltgAIBgAIBggIWHAxIVYQLsV2EC62EB11ZbW2AAYQL6h4KIAWEB/VZblFBQYCBhAwuHgogBYQH9VluTUFBgQGEDHIeCiAFhAf1WW5JQUGBgYQMth4KIAWEB/VZbkVBQkpWRlFCSUFZbYQNCgWEB3FZbglJQUFZbYABgIIIBkFBhA11gAIMBhGEDOVZbkpFQUFb+omRpcGZzWCISIERm1g+ZGX5gazeqwDybV3S5GcRf/ml4Gi+4oo9YybYUZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGOsIShYKFAAAAAAAAAAAAAAAAAAAAAAAAARrcgcKAxjrCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjk1ZWsBhDrAhICGAISAhgDGJirbCICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOi8KAxjrCBCowwEiJMZw+GQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw==","b64Record":"CiUIFiIDGOsIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA61Mb3O9O8zDvv7KYvgT+tbwMFj+4ertYIh3vxmZx27y2rrBT2/oHarH0qkkb4BXsaCwig1pWsBhDrrfkLIg8KCQjk1ZWsBhDrAhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMNXfYjrbBAoDGOsIIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAQAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiDsgEyzAIKAxjrCBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAEAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIEZpLA5ZypzRrY+YSp0RcV7INCQ5i37tTgXIzoRmJBWoGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1IWCgkKAhgCEKm/xQEKCQoCGGIQqr/FAQ=="}]},"log2Works":{"placeholderNum":1132,"encodedItems":[{"b64Body":"Cg8KCQjo1ZWsBhD/AhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAikpPCvBhDolu/9ARptCiISIGZ2QEFwoctLValcmFAjA8P4khw9A10iyitLbae8zijYCiM6IQIYLqk8tw8yO1TnYmm6v8/9bXz3zQkMAYLGpqgHhwb7qgoiEiAhCYJ+5ReWxcbfMmk4kmXODkewZWe4qfXU6r7dWR9E0CIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGO0IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB+GdctxS7bDwMkjgxoc8xLvkhOXpZJSk8OULYyvHLVCvG0ssEgLp1ZP08sk1FE5/caDAik1pWsBhDT3LaXAiIPCgkI6NWVrAYQ/wISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjp1ZWsBhCDAxICGAISAhgDGO7GyDQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB+g4KAxjtCCLyDjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM5OTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzJhNGMwODk2MTQ2MTAwNWM1NzgwNjM3OGI5YTFmMzE0NjEwMDc4NTc4MDYzYzY3MGY4NjQxNDYxMDA5NDU3ODA2M2M2ODNkNmEzMTQ2MTAwYjA1NzgwNjNkMDUyODVkNDE0NjEwMGNjNTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDIxMjU2NWI2MTAwZTg1NjViMDA1YjYxMDA5MjYwMDQ4MDM2MDM4MTAxOTA2MTAwOGQ5MTkwNjEwMjY1NTY1YjYxMDExYzU2NWIwMDViNjEwMGFlNjAwNDgwMzYwMzgxMDE5MDYxMDBhOTkxOTA2MTAyYTU1NjViNjEwMTRlNTY1YjAwNWI2MTAwY2E2MDA0ODAzNjAzODEwMTkwNjEwMGM1OTE5MDYxMDJkMjU2NWI2MTAxN2U1NjViMDA1YjYxMDBlNjYwMDQ4MDM2MDM4MTAxOTA2MTAwZTE5MTkwNjEwMmE1NTY1YjYxMDFiZTU2NWIwMDViODA4Mjg0N2ZhOGZiMmY5YTQ5YWZjMmVhMTQ4MzE5MzI2YzcyMDg5NjU1NTUxNTFkYjJjZTEzN2MwNTE3NDA5ODczMGFlZGMzNjA0MDUxNjA0MDUxODA5MTAzOTBhNDUwNTA1MDU2NWI4MDgyN2Y1MTNkYWQ3NTgyZmQ4YjExYzhmNGQwNWU2ZTdhYzhjYWFhNWViNjkwZTkxNzNkZDJiZWQ5NmI1YWUwZTBkMDI0NjA0MDUxNjA0MDUxODA5MTAzOTBhMzUwNTA1NjViODA3ZjQ2NjkyYzBlNTljYTljZDFhZDhmOTg0YTlkMTE3MTVlYzgzNDI0Mzk4YjdlZWQ0ZTA1YzhjZTg0NjYyNDE1YTg2MDQwNTE2MDQwNTE4MDkxMDM5MGEyNTA1NjViODE4Mzg1N2Y3NWU3ZDk1Y2Q3MjU4OGFmNDljZTJlNGI3ZjAwNGJjZTkxNmQ0MjI5OTlhZGYyNjJhNjQwZTQyMzlhYWIwMGM3ODQ2MDQwNTE2MTAxYjA5MTkwNjEwMzQ4NTY1YjYwNDA1MTgwOTEwMzkwYTQ1MDUwNTA1MDU2NWI4MDYwNDA1MTYxMDFjYzkxOTA2MTAzNDg1NjViNjA0MDUxODA5MTAzOTBhMDUwNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDFlZjgxNjEwMWRjNTY1YjgxMTQ2MTAxZmE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyMGM4MTYxMDFlNjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTAyMmI1NzYxMDIyYTYxMDFkNzU2NWI1YjYwMDA2MTAyMzk4NjgyODcwMTYxMDFmZDU2NWI5MzUwNTA2MDIwNjEwMjRhODY4Mjg3MDE2MTAxZmQ1NjViOTI1MDUwNjA0MDYxMDI1Yjg2ODI4NzAxNjEwMWZkNTY1YjkxNTA1MDkyNTA5MjUwOTI1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyN2M1NzYxMDI3YjYxMDFkNzU2NWI1YjYwMDA2MTAyOGE4NTgyODYwMTYxMDFmZDU2NWI5MjUwNTA2MDIwNjEwMjliODU4Mjg2MDE2MTAxZmQ1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyYmI1NzYxMDJiYTYxMDFkNzU2NWI1YjYwMDA2MTAyYzk4NDgyODUwMTYxMDFmZDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwODA2MDgwODU4NzAzMTIxNTYxMDJlYzU3NjEwMmViNjEwMWQ3NTY1YjViNjAwMDYxMDJmYTg3ODI4ODAxNjEwMWZkNTY1Yjk0NTA1MDYwMjA2MTAzMGI4NzgyODgwMTYxMDFmZDU2NWI5MzUwNTA2MDQwNjEwMzFjODc4Mjg4MDE2MTAxZmQ1NjViOTI1MDUwNjA2MDYxMDMyZDg3ODI4ODAxNjEwMWZkNTY1YjkxNTA1MDkyOTU5MTk0NTA5MjUwNTY1YjYxMDM0MjgxNjEwMWRjNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAzNWQ2MDAwODMwMTg0NjEwMzM5NTY1YjkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNDQ2NmQ2MGY5OTE5N2U2MDZiMzdhYWMwM2M5YjU3NzRiOTE5YzQ1ZmZlNjk3ODFhMmZiOGEyOGY1OGM5YjYxNDY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwlgojxR4GKaKpLBFFpgBbNvqDGtAxmx2Yc8SCdM5PMDnsPd1b/SS6zU5EjHwWBJF2GgsIpdaVrAYQg+inHyIPCgkI6dWVrAYQgwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjp1ZWsBhCFAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGO0IGiISIHbhSDbN+WFVcEKOOjezoBk0n4j90Rag71y3PbMJYteWIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGO4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDreA/gOLiM5hPcTqavnXvt3eqbOr4KkQSYFTZHrnZn1pkF5EFc0YGR82uZWP6FFLcaDAil1pWsBhCToMSgAiIPCgkI6dWVrAYQhQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs4JCgMY7ggSmQdggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjKkwIlhRhAFxXgGN4uaHzFGEAeFeAY8Zw+GQUYQCUV4BjxoPWoxRhALBXgGPQUoXUFGEAzFdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQISVlthAOhWWwBbYQCSYASANgOBAZBhAI2RkGECZVZbYQEcVlsAW2EArmAEgDYDgQGQYQCpkZBhAqVWW2EBTlZbAFthAMpgBIA2A4EBkGEAxZGQYQLSVlthAX5WWwBbYQDmYASANgOBAZBhAOGRkGECpVZbYQG+VlsAW4CChH+o+y+aSa/C6hSDGTJscgiWVVUVHbLOE3wFF0CYcwrtw2BAUWBAUYCRA5CkUFBQVluAgn9RPa11gv2LEcj00F5uesjKql62kOkXPdK+2Wta4ODQJGBAUWBAUYCRA5CjUFBWW4B/RmksDlnKnNGtj5hKnRFxXsg0JDmLfu1OBcjOhGYkFahgQFFgQFGAkQOQolBWW4GDhX9159lc1yWIr0nOLkt/AEvOkW1CKZmt8mKmQOQjmqsAx4RgQFFhAbCRkGEDSFZbYEBRgJEDkKRQUFBQVluAYEBRYQHMkZBhA0hWW2BAUYCRA5CgUFZbYACA/VtgAIGQUJGQUFZbYQHvgWEB3FZbgRRhAfpXYACA/VtQVltgAIE1kFBhAgyBYQHmVluSkVBQVltgAIBgAGBghIYDEhVhAitXYQIqYQHXVltbYABhAjmGgocBYQH9VluTUFBgIGECSoaChwFhAf1WW5JQUGBAYQJbhoKHAWEB/VZbkVBQklCSUJJWW2AAgGBAg4UDEhVhAnxXYQJ7YQHXVltbYABhAoqFgoYBYQH9VluSUFBgIGECm4WChgFhAf1WW5FQUJJQkpBQVltgAGAggoQDEhVhArtXYQK6YQHXVltbYABhAsmEgoUBYQH9VluRUFCSkVBQVltgAIBgAIBggIWHAxIVYQLsV2EC62EB11ZbW2AAYQL6h4KIAWEB/VZblFBQYCBhAwuHgogBYQH9VluTUFBgQGEDHIeCiAFhAf1WW5JQUGBgYQMth4KIAWEB/VZbkVBQkpWRlFCSUFZbYQNCgWEB3FZbglJQUFZbYABgIIIBkFBhA11gAIMBhGEDOVZbkpFQUFb+omRpcGZzWCISIERm1g+ZGX5gazeqwDybV3S5GcRf/ml4Gi+4oo9YybYUZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGO4IShYKFAAAAAAAAAAAAAAAAAAAAAAAAARucgcKAxjuCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjq1ZWsBhCHAxICGAISAhgDGJirbCICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOk8KAxjuCBCowwEiRHi5ofMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC","b64Record":"CiUIFiIDGO4IKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDxtG9siWKnFj7y3MoBdRcne0VvaZpzgt7Aty1g2yTKBUkPOiFeZfvVtkzQUQ9xD3oaCwim1pWsBhDDrd4oIg8KCQjq1ZWsBhCHAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMODSZTr9BAoDGO4IIoACBAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAACigtwEy7gIKAxjuCBKAAgQAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAaIFE9rXWC/YsRyPTQXm56yMqqXraQ6Rc90r7Za1rg4NAkGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJSFgoJCgIYAhC/pcsBCgkKAhhiEMClywE="}]},"log3Works":{"placeholderNum":1135,"encodedItems":[{"b64Body":"Cg8KCQju1ZWsBhCbAxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiqpPCvBhDY/qOQAhptCiISIMCCHJs3vK5PhYYePnxbAAwdRrVLToWoXm0hrKhAzjNZCiM6IQIVgaIgwQKuNNEr8lHKdWT2Ntii552W3nWoxqQcdxAxFAoiEiAPMQeykCr2+T/VIH/1cePtW/yvPWmujACUW58obUJGmSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPAIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC7O1d9B19XnfZgCTXf5UqqL+SaZZz4z8V/rBy3QMwmJkQmy6ndHiPtAvMPXI8Z7WQaDAiq1pWsBhCz26yXAiIPCgkI7tWVrAYQmwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjv1ZWsBhCfAxICGAISAhgDGO7GyDQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB+g4KAxjwCCLyDjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM5OTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzJhNGMwODk2MTQ2MTAwNWM1NzgwNjM3OGI5YTFmMzE0NjEwMDc4NTc4MDYzYzY3MGY4NjQxNDYxMDA5NDU3ODA2M2M2ODNkNmEzMTQ2MTAwYjA1NzgwNjNkMDUyODVkNDE0NjEwMGNjNTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDIxMjU2NWI2MTAwZTg1NjViMDA1YjYxMDA5MjYwMDQ4MDM2MDM4MTAxOTA2MTAwOGQ5MTkwNjEwMjY1NTY1YjYxMDExYzU2NWIwMDViNjEwMGFlNjAwNDgwMzYwMzgxMDE5MDYxMDBhOTkxOTA2MTAyYTU1NjViNjEwMTRlNTY1YjAwNWI2MTAwY2E2MDA0ODAzNjAzODEwMTkwNjEwMGM1OTE5MDYxMDJkMjU2NWI2MTAxN2U1NjViMDA1YjYxMDBlNjYwMDQ4MDM2MDM4MTAxOTA2MTAwZTE5MTkwNjEwMmE1NTY1YjYxMDFiZTU2NWIwMDViODA4Mjg0N2ZhOGZiMmY5YTQ5YWZjMmVhMTQ4MzE5MzI2YzcyMDg5NjU1NTUxNTFkYjJjZTEzN2MwNTE3NDA5ODczMGFlZGMzNjA0MDUxNjA0MDUxODA5MTAzOTBhNDUwNTA1MDU2NWI4MDgyN2Y1MTNkYWQ3NTgyZmQ4YjExYzhmNGQwNWU2ZTdhYzhjYWFhNWViNjkwZTkxNzNkZDJiZWQ5NmI1YWUwZTBkMDI0NjA0MDUxNjA0MDUxODA5MTAzOTBhMzUwNTA1NjViODA3ZjQ2NjkyYzBlNTljYTljZDFhZDhmOTg0YTlkMTE3MTVlYzgzNDI0Mzk4YjdlZWQ0ZTA1YzhjZTg0NjYyNDE1YTg2MDQwNTE2MDQwNTE4MDkxMDM5MGEyNTA1NjViODE4Mzg1N2Y3NWU3ZDk1Y2Q3MjU4OGFmNDljZTJlNGI3ZjAwNGJjZTkxNmQ0MjI5OTlhZGYyNjJhNjQwZTQyMzlhYWIwMGM3ODQ2MDQwNTE2MTAxYjA5MTkwNjEwMzQ4NTY1YjYwNDA1MTgwOTEwMzkwYTQ1MDUwNTA1MDU2NWI4MDYwNDA1MTYxMDFjYzkxOTA2MTAzNDg1NjViNjA0MDUxODA5MTAzOTBhMDUwNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDFlZjgxNjEwMWRjNTY1YjgxMTQ2MTAxZmE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyMGM4MTYxMDFlNjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTAyMmI1NzYxMDIyYTYxMDFkNzU2NWI1YjYwMDA2MTAyMzk4NjgyODcwMTYxMDFmZDU2NWI5MzUwNTA2MDIwNjEwMjRhODY4Mjg3MDE2MTAxZmQ1NjViOTI1MDUwNjA0MDYxMDI1Yjg2ODI4NzAxNjEwMWZkNTY1YjkxNTA1MDkyNTA5MjUwOTI1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyN2M1NzYxMDI3YjYxMDFkNzU2NWI1YjYwMDA2MTAyOGE4NTgyODYwMTYxMDFmZDU2NWI5MjUwNTA2MDIwNjEwMjliODU4Mjg2MDE2MTAxZmQ1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyYmI1NzYxMDJiYTYxMDFkNzU2NWI1YjYwMDA2MTAyYzk4NDgyODUwMTYxMDFmZDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwODA2MDgwODU4NzAzMTIxNTYxMDJlYzU3NjEwMmViNjEwMWQ3NTY1YjViNjAwMDYxMDJmYTg3ODI4ODAxNjEwMWZkNTY1Yjk0NTA1MDYwMjA2MTAzMGI4NzgyODgwMTYxMDFmZDU2NWI5MzUwNTA2MDQwNjEwMzFjODc4Mjg4MDE2MTAxZmQ1NjViOTI1MDUwNjA2MDYxMDMyZDg3ODI4ODAxNjEwMWZkNTY1YjkxNTA1MDkyOTU5MTk0NTA5MjUwNTY1YjYxMDM0MjgxNjEwMWRjNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAzNWQ2MDAwODMwMTg0NjEwMzM5NTY1YjkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNDQ2NmQ2MGY5OTE5N2U2MDZiMzdhYWMwM2M5YjU3NzRiOTE5YzQ1ZmZlNjk3ODFhMmZiOGEyOGY1OGM5YjYxNDY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwn26XQUFraWixEsK9EGcblKSTPgqOC2v3ZtS5fuRX6KT0PKC4brUgpoGMuiV88iW3GgsIq9aVrAYQs+P2OyIPCgkI79WVrAYQnwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjv1ZWsBhChAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGPAIGiISIBoVDcKxQAcgq1vd0n0aWd8PI69op3PMNuJms/+TQXXvIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGPEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBevTlbrsokDzm0oRj1+kkkH+BbIrJviBTjstUoqoVMpM8QqLv/ylo4l2aMfUOFMNQaDAir1pWsBhDL6rChAiIPCgkI79WVrAYQoQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs4JCgMY8QgSmQdggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjKkwIlhRhAFxXgGN4uaHzFGEAeFeAY8Zw+GQUYQCUV4BjxoPWoxRhALBXgGPQUoXUFGEAzFdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQISVlthAOhWWwBbYQCSYASANgOBAZBhAI2RkGECZVZbYQEcVlsAW2EArmAEgDYDgQGQYQCpkZBhAqVWW2EBTlZbAFthAMpgBIA2A4EBkGEAxZGQYQLSVlthAX5WWwBbYQDmYASANgOBAZBhAOGRkGECpVZbYQG+VlsAW4CChH+o+y+aSa/C6hSDGTJscgiWVVUVHbLOE3wFF0CYcwrtw2BAUWBAUYCRA5CkUFBQVluAgn9RPa11gv2LEcj00F5uesjKql62kOkXPdK+2Wta4ODQJGBAUWBAUYCRA5CjUFBWW4B/RmksDlnKnNGtj5hKnRFxXsg0JDmLfu1OBcjOhGYkFahgQFFgQFGAkQOQolBWW4GDhX9159lc1yWIr0nOLkt/AEvOkW1CKZmt8mKmQOQjmqsAx4RgQFFhAbCRkGEDSFZbYEBRgJEDkKRQUFBQVluAYEBRYQHMkZBhA0hWW2BAUYCRA5CgUFZbYACA/VtgAIGQUJGQUFZbYQHvgWEB3FZbgRRhAfpXYACA/VtQVltgAIE1kFBhAgyBYQHmVluSkVBQVltgAIBgAGBghIYDEhVhAitXYQIqYQHXVltbYABhAjmGgocBYQH9VluTUFBgIGECSoaChwFhAf1WW5JQUGBAYQJbhoKHAWEB/VZbkVBQklCSUJJWW2AAgGBAg4UDEhVhAnxXYQJ7YQHXVltbYABhAoqFgoYBYQH9VluSUFBgIGECm4WChgFhAf1WW5FQUJJQkpBQVltgAGAggoQDEhVhArtXYQK6YQHXVltbYABhAsmEgoUBYQH9VluRUFCSkVBQVltgAIBgAIBggIWHAxIVYQLsV2EC62EB11ZbW2AAYQL6h4KIAWEB/VZblFBQYCBhAwuHgogBYQH9VluTUFBgQGEDHIeCiAFhAf1WW5JQUGBgYQMth4KIAWEB/VZbkVBQkpWRlFCSUFZbYQNCgWEB3FZbglJQUFZbYABgIIIBkFBhA11gAIMBhGEDOVZbkpFQUFb+omRpcGZzWCISIERm1g+ZGX5gazeqwDybV3S5GcRf/ml4Gi+4oo9YybYUZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGPEIShYKFAAAAAAAAAAAAAAAAAAAAAAAAARxcgcKAxjxCBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjw1ZWsBhCjAxICGAISAhgDGJirbCICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOm8KAxjxCBCowwEiZCpMCJYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM=","b64Record":"CiUIFiIDGPEIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC0QB6eFLwx/53nMQUAgbLcosKNuUWjmsR7GM6lZgSKHMScSRgMdej5cSLODQDvMZoaCwis1pWsBhDL9s9FIg8KCQjw1ZWsBhCjAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMJbEaDqfBQoDGPEIIoACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAIAAAAAAAAAABEAAAAAIAAAAAAAgAAAAAAAACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAACi6vAEykAMKAxjxCBKAAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAACAAAAAAAAAAARAAAAACAAAAAAAIAAAAAAAAAgAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAaIKj7L5pJr8LqFIMZMmxyCJZVVRUdss4TfAUXQJhzCu3DGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIaIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADUhYKCQoCGAIQq4jRAQoJCgIYYhCsiNEB"}]},"log4Works":{"placeholderNum":1138,"encodedItems":[{"b64Body":"Cg8KCQj01ZWsBhC3AxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiwpPCvBhCYuYqnAhptCiISIGmkvyoQGiQWUZUeLFcIL7ZN+ELN5+HL/TZ3U2FJE0aXCiM6IQK3YHP/nDQFIeissP8tj9I5FU8sbo3ibmuI87ZxyvCy2QoiEiDtIB5jK6bqsR2r+ag5OKC/em+5HnO3O8LjMf4QqeSOuiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGPMIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDCLXbSyxKNe9zwmFSmXp9XHv3uPTE+JgmUx15VOrC/Nf2BIdHfFD5rmg4RT8Mr+/8aDAiw1pWsBhCL9oi0AiIPCgkI9NWVrAYQtwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj11ZWsBhC7AxICGAISAhgDGO7GyDQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB+g4KAxjzCCLyDjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM5OTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzJhNGMwODk2MTQ2MTAwNWM1NzgwNjM3OGI5YTFmMzE0NjEwMDc4NTc4MDYzYzY3MGY4NjQxNDYxMDA5NDU3ODA2M2M2ODNkNmEzMTQ2MTAwYjA1NzgwNjNkMDUyODVkNDE0NjEwMGNjNTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDIxMjU2NWI2MTAwZTg1NjViMDA1YjYxMDA5MjYwMDQ4MDM2MDM4MTAxOTA2MTAwOGQ5MTkwNjEwMjY1NTY1YjYxMDExYzU2NWIwMDViNjEwMGFlNjAwNDgwMzYwMzgxMDE5MDYxMDBhOTkxOTA2MTAyYTU1NjViNjEwMTRlNTY1YjAwNWI2MTAwY2E2MDA0ODAzNjAzODEwMTkwNjEwMGM1OTE5MDYxMDJkMjU2NWI2MTAxN2U1NjViMDA1YjYxMDBlNjYwMDQ4MDM2MDM4MTAxOTA2MTAwZTE5MTkwNjEwMmE1NTY1YjYxMDFiZTU2NWIwMDViODA4Mjg0N2ZhOGZiMmY5YTQ5YWZjMmVhMTQ4MzE5MzI2YzcyMDg5NjU1NTUxNTFkYjJjZTEzN2MwNTE3NDA5ODczMGFlZGMzNjA0MDUxNjA0MDUxODA5MTAzOTBhNDUwNTA1MDU2NWI4MDgyN2Y1MTNkYWQ3NTgyZmQ4YjExYzhmNGQwNWU2ZTdhYzhjYWFhNWViNjkwZTkxNzNkZDJiZWQ5NmI1YWUwZTBkMDI0NjA0MDUxNjA0MDUxODA5MTAzOTBhMzUwNTA1NjViODA3ZjQ2NjkyYzBlNTljYTljZDFhZDhmOTg0YTlkMTE3MTVlYzgzNDI0Mzk4YjdlZWQ0ZTA1YzhjZTg0NjYyNDE1YTg2MDQwNTE2MDQwNTE4MDkxMDM5MGEyNTA1NjViODE4Mzg1N2Y3NWU3ZDk1Y2Q3MjU4OGFmNDljZTJlNGI3ZjAwNGJjZTkxNmQ0MjI5OTlhZGYyNjJhNjQwZTQyMzlhYWIwMGM3ODQ2MDQwNTE2MTAxYjA5MTkwNjEwMzQ4NTY1YjYwNDA1MTgwOTEwMzkwYTQ1MDUwNTA1MDU2NWI4MDYwNDA1MTYxMDFjYzkxOTA2MTAzNDg1NjViNjA0MDUxODA5MTAzOTBhMDUwNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDFlZjgxNjEwMWRjNTY1YjgxMTQ2MTAxZmE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyMGM4MTYxMDFlNjU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTAyMmI1NzYxMDIyYTYxMDFkNzU2NWI1YjYwMDA2MTAyMzk4NjgyODcwMTYxMDFmZDU2NWI5MzUwNTA2MDIwNjEwMjRhODY4Mjg3MDE2MTAxZmQ1NjViOTI1MDUwNjA0MDYxMDI1Yjg2ODI4NzAxNjEwMWZkNTY1YjkxNTA1MDkyNTA5MjUwOTI1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyN2M1NzYxMDI3YjYxMDFkNzU2NWI1YjYwMDA2MTAyOGE4NTgyODYwMTYxMDFmZDU2NWI5MjUwNTA2MDIwNjEwMjliODU4Mjg2MDE2MTAxZmQ1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyYmI1NzYxMDJiYTYxMDFkNzU2NWI1YjYwMDA2MTAyYzk4NDgyODUwMTYxMDFmZDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwODA2MDgwODU4NzAzMTIxNTYxMDJlYzU3NjEwMmViNjEwMWQ3NTY1YjViNjAwMDYxMDJmYTg3ODI4ODAxNjEwMWZkNTY1Yjk0NTA1MDYwMjA2MTAzMGI4NzgyODgwMTYxMDFmZDU2NWI5MzUwNTA2MDQwNjEwMzFjODc4Mjg4MDE2MTAxZmQ1NjViOTI1MDUwNjA2MDYxMDMyZDg3ODI4ODAxNjEwMWZkNTY1YjkxNTA1MDkyOTU5MTk0NTA5MjUwNTY1YjYxMDM0MjgxNjEwMWRjNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAzNWQ2MDAwODMwMTg0NjEwMzM5NTY1YjkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNDQ2NmQ2MGY5OTE5N2U2MDZiMzdhYWMwM2M5YjU3NzRiOTE5YzQ1ZmZlNjk3ODFhMmZiOGEyOGY1OGM5YjYxNDY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwj/CHD5GwhwKxRMdEtB9rFl/Sd+0sudD8ORqCks3F2XqPLQ9rCtptLKW4Mty1s9ytGgsIsdaVrAYQk8bIWCIPCgkI9dWVrAYQuwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj11ZWsBhC9AxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGPMIGiISIJE/6DOVq7nre1K063qw3dT34vc3Ksk80FTFMS7OeEFnIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGPQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjArOxy4IANuY6vYx5eWpA2Z9z1xI8aiyQN78o8NkYsoT88nKK5NvcBQvXPA4dS8vw0aDAix1pWsBhCL38i9AiIPCgkI9dWVrAYQvQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQs4JCgMY9AgSmQdggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjKkwIlhRhAFxXgGN4uaHzFGEAeFeAY8Zw+GQUYQCUV4BjxoPWoxRhALBXgGPQUoXUFGEAzFdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQISVlthAOhWWwBbYQCSYASANgOBAZBhAI2RkGECZVZbYQEcVlsAW2EArmAEgDYDgQGQYQCpkZBhAqVWW2EBTlZbAFthAMpgBIA2A4EBkGEAxZGQYQLSVlthAX5WWwBbYQDmYASANgOBAZBhAOGRkGECpVZbYQG+VlsAW4CChH+o+y+aSa/C6hSDGTJscgiWVVUVHbLOE3wFF0CYcwrtw2BAUWBAUYCRA5CkUFBQVluAgn9RPa11gv2LEcj00F5uesjKql62kOkXPdK+2Wta4ODQJGBAUWBAUYCRA5CjUFBWW4B/RmksDlnKnNGtj5hKnRFxXsg0JDmLfu1OBcjOhGYkFahgQFFgQFGAkQOQolBWW4GDhX9159lc1yWIr0nOLkt/AEvOkW1CKZmt8mKmQOQjmqsAx4RgQFFhAbCRkGEDSFZbYEBRgJEDkKRQUFBQVluAYEBRYQHMkZBhA0hWW2BAUYCRA5CgUFZbYACA/VtgAIGQUJGQUFZbYQHvgWEB3FZbgRRhAfpXYACA/VtQVltgAIE1kFBhAgyBYQHmVluSkVBQVltgAIBgAGBghIYDEhVhAitXYQIqYQHXVltbYABhAjmGgocBYQH9VluTUFBgIGECSoaChwFhAf1WW5JQUGBAYQJbhoKHAWEB/VZbkVBQklCSUJJWW2AAgGBAg4UDEhVhAnxXYQJ7YQHXVltbYABhAoqFgoYBYQH9VluSUFBgIGECm4WChgFhAf1WW5FQUJJQkpBQVltgAGAggoQDEhVhArtXYQK6YQHXVltbYABhAsmEgoUBYQH9VluRUFCSkVBQVltgAIBgAIBggIWHAxIVYQLsV2EC62EB11ZbW2AAYQL6h4KIAWEB/VZblFBQYCBhAwuHgogBYQH9VluTUFBgQGEDHIeCiAFhAf1WW5JQUGBgYQMth4KIAWEB/VZbkVBQkpWRlFCSUFZbYQNCgWEB3FZbglJQUFZbYABgIIIBkFBhA11gAIMBhGEDOVZbkpFQUFb+omRpcGZzWCISIERm1g+ZGX5gazeqwDybV3S5GcRf/ml4Gi+4oo9YybYUZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGPQIShYKFAAAAAAAAAAAAAAAAAAAAAAAAAR0cgcKAxj0CBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQj21ZWsBhC/AxICGAISAhgDGJirbCICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOpABCgMY9AgQqMMBIoQBxoPWowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE","b64Record":"CiUIFiIDGPQIKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD4UeZZVYD1ioDUorhCXOHU0Myo3/U0coZg5IpmGiqW4vmwPpffdCTLx3Afoe2w9fAaCwiy1pWsBhDD5/BhIg8KCQj21ZWsBhC/AxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMNr9azrBBQoDGPQIIoACBAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAQAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAIAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAACjWwgEysgMKAxj0CBKAAgQAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAACAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAaIHXn2VzXJYivSc4uS38AS86RbUIpma3yYqZA5COaqwDHGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIaIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFIWCgkKAhgCELP71wEKCQoCGGIQtPvXAQ=="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java index 59620c4c3818..44189818c386 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java @@ -26,6 +26,8 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; @@ -70,7 +72,10 @@ public List getSpecsInSuite() { @HapiTest final HapiSpec log0Works() { return defaultHapiSpec("log0Works") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log0", BigInteger.valueOf(15)) .via("log0") .gas(GAS_TO_OFFER)) @@ -84,7 +89,10 @@ final HapiSpec log0Works() { @HapiTest final HapiSpec log1Works() { return defaultHapiSpec("log1Works") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log1", BigInteger.valueOf(15)) .via("log1") .gas(GAS_TO_OFFER)) @@ -101,7 +109,10 @@ final HapiSpec log1Works() { @HapiTest final HapiSpec log2Works() { return defaultHapiSpec("log2Works") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log2", BigInteger.ONE, BigInteger.TWO) .gas(GAS_TO_OFFER) .via("log2")) @@ -120,7 +131,10 @@ final HapiSpec log2Works() { @HapiTest final HapiSpec log3Works() { return defaultHapiSpec("log3Works") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log3", BigInteger.ONE, BigInteger.TWO, BigInteger.valueOf(3)) .gas(GAS_TO_OFFER) .via("log3")) @@ -140,7 +154,10 @@ final HapiSpec log3Works() { @HapiTest final HapiSpec log4Works() { return defaultHapiSpec("log4Works") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall( CONTRACT, "log4", From 3c74658181a570c7a223e1a9c6c0e223f27d3a43 Mon Sep 17 00:00:00 2001 From: Valentin Valkanov Date: Sun, 24 Dec 2023 21:46:54 +0200 Subject: [PATCH 40/80] fix: enable fuzzy record matching in some of the smart contract operations (#10625) Signed-off-by: Valentin Valkanov --- .../DelegateCallOperation.json | 1 + .../ExtCodeCopyOperation.json | 1 + .../ExtCodeHashOperation.json | 1 + .../ExtCodeSizeOperation.json | 1 + .../record-snapshots/GlobalProperties.json | 1 + .../opcodes/DelegateCallOperationSuite.java | 7 +++++- .../opcodes/ExtCodeCopyOperationSuite.java | 9 +++++++- .../opcodes/ExtCodeHashOperationSuite.java | 9 +++++++- .../opcodes/ExtCodeSizeOperationSuite.java | 9 +++++++- .../opcodes/GlobalPropertiesSuite.java | 22 +++++++++++++++---- 10 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 hedera-node/test-clients/record-snapshots/DelegateCallOperation.json create mode 100644 hedera-node/test-clients/record-snapshots/ExtCodeCopyOperation.json create mode 100644 hedera-node/test-clients/record-snapshots/ExtCodeHashOperation.json create mode 100644 hedera-node/test-clients/record-snapshots/ExtCodeSizeOperation.json create mode 100644 hedera-node/test-clients/record-snapshots/GlobalProperties.json diff --git a/hedera-node/test-clients/record-snapshots/DelegateCallOperation.json b/hedera-node/test-clients/record-snapshots/DelegateCallOperation.json new file mode 100644 index 000000000000..f69363cb116d --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/DelegateCallOperation.json @@ -0,0 +1 @@ +{"specSnapshots":{"VerifiesExistence":{"placeholderNum":1166,"encodedItems":[{"b64Body":"Cg8KCQj+5JCsBhCoAxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi6s+uvBhDo4qOAAhptCiISIE8bQtsZyZmradBoCjzSbl0U6RMvmTh0pGZSc5XfDujkCiM6IQJOOD4vhuA7vgTBhMR14qS1WkP/jEbc20sMnUJk78dFTAoiEiAYLJ8kT6vgGXJtzpvKnd8LhV6HGl3ObgUlZHTblBB4KyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGI8JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAvDo1lNYbKUtLOENEyzW8Uq2FYqLI+B5Cs3rNK00vJ3gNSzmu2oFri+jLFYUpBYggaDAi65ZCsBhD7/rabAiIPCgkI/uSQrAYQqAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj/5JCsBhCsAxICGAISAhgDGLSg8zUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBrBEKAxiPCSKkETYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDQzMjgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNjI1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzNTdiOWZhZDMxNDYxMDA2NzU3ODA2M2E2MzVmMDFkMTQ2MTAwOWQ1NzgwNjNjNjY3NjRlMTE0NjEwMGQzNTc4MDYzZjU1MzMyYWIxNDYxMDEwOTU3NWI2MDAwODBmZDViNjEwMDliNjAwNDgwMzYwMzgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDEzZjU2NWIwMDViNjEwMGQxNjAwNDgwMzYwMzgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDFmMTU2NWIwMDViNjEwMTA3NjAwNDgwMzYwMzgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDJhMDU2NWIwMDViNjEwMTNkNjAwNDgwMzYwMzgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDM1MzU2NWIwMDViNjAwMDYwNjA2MDQwNTE4MDgwN2Y3Mzc0NmY3MjY1NTY2MTZjNzU2NTI4NzU2OTZlNzQzMjM1MzYyOTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjUwNjAxMzAxOTA1MDYwNDA1MTgwOTEwMzkwMjA2MDQwNTE2MDI0MDE2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA5MDUwNjAyMDgxODI1MTYwMjA4NDAxODY1YWZhOTE1MDUwNTA1MDU2NWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjA0MDUxODA4MDdmNzM3NDZmNzI2NTU2NjE2Yzc1NjUyODc1Njk2ZTc0MzIzNTM2MjkwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI1MDYwMTMwMTkwNTA2MDQwNTE4MDkxMDM5MDIwN2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYwNDA1MTgxNjNmZmZmZmZmZjE2N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDI4MTUyNjAwNDAxNjAwMDYwNDA1MTgwODMwMzgxODY1YWY0OTI1MDUwNTA1MDUwNTY1YjgwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYzNDYwNDA1MTgwODA3ZjczNzQ2ZjcyNjU1NjYxNmM3NTY1Mjg3NTY5NmU3NDMyMzUzNjI5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNTA2MDEzMDE5MDUwNjA0MDUxODA5MTAzOTAyMDdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ5MDYwNDA1MTgyNjNmZmZmZmZmZjE2N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDI4MTUyNjAwNDAxNjAwMDYwNDA1MTgwODMwMzgxODU4ODVhZjI5MzUwNTA1MDUwNTA1MDU2NWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MzQ2MDQwNTE4MDgwN2Y3Mzc0NmY3MjY1NTY2MTZjNzU2NTI4NzU2OTZlNzQzMjM1MzYyOTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjUwNjAxMzAxOTA1MDYwNDA1MTgwOTEwMzkwMjA3YzAxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5MDA0OTA2MDQwNTE4MjYzZmZmZmZmZmYxNjdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyODE1MjYwMDQwMTYwMDA2MDQwNTE4MDgzMDM4MTg1ODg1YWYxOTM1MDUwNTA1MDUwNTA1NjAwYTE2NTYyN2E3YTcyMzA1ODIwNTE0MTRlZDc0MWMxNjg0MTBjMTMyODEyNTQyNDJiY2JhZjY2NTgwMTA1ZGQ2OGUyNDg3Y2IzM2RiZDI0NWQwMDAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwS2EhmWOqn6+J3A/omASrVhOzoUE/TE0zX8S0wFEHo3VIvsQy1MqwDeY0E11LnYhBGgsIu+WQrAYQm5eGQCIPCgkI/+SQrAYQrAMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj/5JCsBhCuAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGI8JGiISIBVAFPnP4E5Tp1HYMNditCeGe9qMgsJc51rgZhKMyWTuIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD1z77nOWcwvvcYMcW0N4QTOJhdQXmpOHNb17ViYCpfUQYvhHoMW5Qg0SYqtNACzF4aDAi75ZCsBhDj7/OlAiIPCgkI/+SQrAYQrgMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQucKCgMYkAkSsghggGBAUmAENhBhAGJXYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAY1e5+tMUYQBnV4BjpjXwHRRhAJ1XgGPGZ2ThFGEA01eAY/VTMqsUYQEJV1tgAID9W2EAm2AEgDYDgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQE/VlsAW2EA0WAEgDYDgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQHxVlsAW2EBB2AEgDYDgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQKgVlsAW2EBPWAEgDYDgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQNTVlsAW2AAYGBgQFGAgH9zdG9yZVZhbHVlKHVpbnQyNTYpAAAAAAAAAAAAAAAAAIFSUGATAZBQYEBRgJEDkCBgQFFgJAFgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFCQUGAggYJRYCCEAYZa+pFQUFBQVluAc///////////////////////////FmBAUYCAf3N0b3JlVmFsdWUodWludDI1NikAAAAAAAAAAAAAAAAAgVJQYBMBkFBgQFGAkQOQIHwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEYEBRgWP/////FnwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKBUmAEAWAAYEBRgIMDgYZa9JJQUFBQUFZbgHP//////////////////////////xY0YEBRgIB/c3RvcmVWYWx1ZSh1aW50MjU2KQAAAAAAAAAAAAAAAACBUlBgEwGQUGBAUYCRA5AgfAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkASQYEBRgmP/////FnwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKBUmAEAWAAYEBRgIMDgYWIWvKTUFBQUFBQVluAc///////////////////////////FjRgQFGAgH9zdG9yZVZhbHVlKHVpbnQyNTYpAAAAAAAAAAAAAAAAAIFSUGATAZBQYEBRgJEDkCB8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQBJBgQFGCY/////8WfAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoFSYAQBYABgQFGAgwOBhYha8ZNQUFBQUFBWAKFlYnp6cjBYIFFBTtdBwWhBDBMoElQkK8uvZlgBBd1o4kh8sz29JF0AACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJAJShYKFAAAAAAAAAAAAAAAAAAAAAAAAASQcgcKAxiQCRABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQiA5ZCsBhCwAxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYkAkQoI0GIiSmNfAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASNFY=","b64Record":"CiUIHSIDGJAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCczt6DzobYOlpo5Pl0OYs7KqCdM6ZaxqBilUXhY/4UA2U20gECSgIJVmM/lgP0PQEaCwi85ZCsBhCry8RKIg8KCQiA5ZCsBhCwAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMOCssQM6HhoYSU5WQUxJRF9TT0xJRElUWV9BRERSRVNTKKCNBlIWCgkKAhgCEL/Z4gYKCQoCGGIQwNniBg=="},{"b64Body":"Cg8KCQiA5ZCsBhCyAxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYkAkQoI0GIiSmNfAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=","b64Record":"CiUIFiIDGJAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDMUZYzKNmFBnjsCqihERwqfc3c83TgPwURR1LD2lLZeQoxWV02rIVnUa9nKI//HP4aDAi85ZCsBhD7m8TLAiIPCgkIgOWQrAYQsgMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYkAkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/record-snapshots/ExtCodeCopyOperation.json b/hedera-node/test-clients/record-snapshots/ExtCodeCopyOperation.json new file mode 100644 index 000000000000..33d36b54d903 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/ExtCodeCopyOperation.json @@ -0,0 +1 @@ +{"specSnapshots":{"VerifiesExistence":{"placeholderNum":1186,"encodedItems":[{"b64Body":"Cg8KCQjB65CsBhCZARICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISICAgOM4OJyejVX/W1GWN5a1cxy2hkVqkTwPtFnSc91IGEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGKMJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDNAceZr1VE36ppC9JYRvniC/WUJvUgBkO6YUR0xnPvTVpFI919Vzbovl+aaEqjrzsaDAj965CsBhCb2MiEASIPCgkIweuQrAYQmQESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxijCRCAqNa5Bw=="},{"b64Body":"Cg8KCQjB65CsBhCbARICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj9ueuvBhDw9tP9AhptCiISIA3UM4LmZOxVBGsgG02U09z63sorY8+6wwE/XOk+iSSSCiM6IQIwzVHmAcE+OJJBbFAUOAIeH9hF/9A67135F8Hrthq0ngoiEiAVSI3U9q9RzR4SQt6dGBb38eTCnqqw6sv4DXvTOIvgdyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCD+EQhVEvvsicBU0K/HOlZB0sxP/ytyRT6+URtX+lhI/LKvsK0pXAhLEUxFDtqwfcaDAj965CsBhDbjrOiAyIPCgkIweuQrAYQmwESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjC65CsBhCfARICGAISAhgDGMCMrzEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBnAkKAxikCSKUCTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDIyYTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0MTU3NjAwMDM1NjBlMDFjODA2MzBjYjhmNzRlMTQ2MTAwNDY1NzgwNjM1ZDAwNzI4MTE0NjEwMDllNTc4MDYzYzk1MWMyY2UxNDYxMDBmNjU3NWI2MDAwODBmZDViNjEwMDg4NjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAwNWM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxYjM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZTA2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBiNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDFiZTU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYxMDEzODYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTBjNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWM5NTY1YjYwNDA1MTgwODA2MDIwMDE4MjgxMDM4MjUyODM4MTgxNTE4MTUyNjAyMDAxOTE1MDgwNTE5MDYwMjAwMTkwODA4MzgzNjAwMDViODM4MTEwMTU2MTAxNzg1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAxNWQ1NjViNTA1MDUwNTA5MDUwOTA4MTAxOTA2MDFmMTY4MDE1NjEwMWE1NTc4MDgyMDM4MDUxNjAwMTgzNjAyMDAzNjEwMTAwMGEwMzE5MTY4MTUyNjAyMDAxOTE1MDViNTA5MjUwNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MTNiOTA1MDkxOTA1MDU2NWI2MDAwODEzZjkwNTA5MTkwNTA1NjViNjA2MDgxM2I2MDQwNTE5MTUwNjAxZjE5NjAxZjYwMjA4MzAxMDExNjgyMDE2MDQwNTI4MDgyNTI4MDYwMDA2MDIwODQwMTg1M2M4MDYwMjA4MzAxZjNmZWEyNjU2MjdhN2E3MjMxNTgyMDVkNmZmZTc3YzJhYzQxMDgyM2M5ZjU1Y2Q5NjIyMjU3ZTE4NjkzODBkM2M1ZjI3ODFlN2I5ZjhmODY3MWY5ZGU2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw6XMNO3O7v/CpS2YGu7+rm7CstxOVfejkBbcwkloHJLmC86nfcknAC4nDUzKJieThGgwI/uuQrAYQu/ulqwEiDwoJCMLrkKwGEJ8BEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjC65CsBhChARICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKQJGiISIBGsPPBhkPmG6tg2WP7bRLDJJl/FKOoQiQT9zD78mgD4IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDc5zrDRt9A656EwX2wqV4PkSpSDuN9bkh+WmBMHbTQSAMHWZ5+c2bF/ht4Hay/vzYaDAj+65CsBhCLr4WsAyIPCgkIwuuQrAYQoQESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt8GCgMYpQkSqgRggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBBV2AANWDgHIBjDLj3ThRhAEZXgGNdAHKBFGEAnleAY8lRws4UYQD2V1tgAID9W2EAiGAEgDYDYCCBEBVhAFxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAbNWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81thAOBgBIA2A2AggRAVYQC0V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQG+VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbYQE4YASANgNgIIEQFWEBDFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkFBQUGEByVZbYEBRgIBgIAGCgQOCUoOBgVGBUmAgAZFQgFGQYCABkICDg2AAW4OBEBVhAXhXgIIBUYGEAVJgIIEBkFBhAV1WW1BQUFCQUJCBAZBgHxaAFWEBpVeAggOAUWABg2AgA2EBAAoDGRaBUmAgAZFQW1CSUFBQYEBRgJEDkPNbYACBO5BQkZBQVltgAIE/kFCRkFBWW2BggTtgQFGRUGAfGWAfYCCDAQEWggFgQFKAglKAYABgIIQBhTyAYCCDAfP+omVienpyMVggXW/+d8KsQQgjyfVc2WIiV+GGk4DTxfJ4Hnufj4Zx+d5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYpQlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABKVyBwoDGKUJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjD65CsBhCjARICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYpQkQoI0GIiTJUcLOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASNFY=","b64Record":"CiUIHSIDGKUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDDeYk2ocHKZDnk7tAh1xkxJw35UDBG386nAmMJPRfs9P2bNOjBE930lGHQdt/DxxoaDAj/65CsBhCb2N7QASIPCgkIw+uQrAYQowESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQjD65CsBhCpARICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYpQkQoI0GIiTJUcLOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKM=","b64Record":"CiUIFiIDGKUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAesvTju79GHjF1Xnz5WX7tngWyXXH8FoUDvaHVeBPaLPo9PV7jNkWVlH3a7Xxs2X0aDAj/65CsBhDr1vm1AyIPCgkIw+uQrAYQqQESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYpQkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/record-snapshots/ExtCodeHashOperation.json b/hedera-node/test-clients/record-snapshots/ExtCodeHashOperation.json new file mode 100644 index 000000000000..2a0a00b6e6c0 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/ExtCodeHashOperation.json @@ -0,0 +1 @@ +{"specSnapshots":{"VerifiesExistence":{"placeholderNum":1182,"encodedItems":[{"b64Body":"Cg8KCQjw6pCsBhCeBhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwisueuvBhDwkbI4Gm0KIhIg7WIyoF5amrQssGySvfxZUAma057D0HCEA7lJyjxoxaIKIzohA/m3VjBau44xoQphxFveiaWMiQTIiZ3E3xHvuK9FpNk5CiISIALV0tOZDrAwLCZ7eH3zPEtX41sna296LkaHbEgU+qpwIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGJ8JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjASHeZU0Yku3QYyq+ugcJLMhcknnjdqVccAQUzf0i0qnxV9EhDeGs5oSIvhaoDU5CAaCwis65CsBhDb8L5UIg8KCQjw6pCsBhCeBhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjw6pCsBhCiBhICGAISAhgDGMCMrzEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBnAkKAxifCSKUCTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDIyYTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0MTU3NjAwMDM1NjBlMDFjODA2MzBjYjhmNzRlMTQ2MTAwNDY1NzgwNjM1ZDAwNzI4MTE0NjEwMDllNTc4MDYzYzk1MWMyY2UxNDYxMDBmNjU3NWI2MDAwODBmZDViNjEwMDg4NjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAwNWM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxYjM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZTA2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBiNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDFiZTU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYxMDEzODYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTBjNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWM5NTY1YjYwNDA1MTgwODA2MDIwMDE4MjgxMDM4MjUyODM4MTgxNTE4MTUyNjAyMDAxOTE1MDgwNTE5MDYwMjAwMTkwODA4MzgzNjAwMDViODM4MTEwMTU2MTAxNzg1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAxNWQ1NjViNTA1MDUwNTA5MDUwOTA4MTAxOTA2MDFmMTY4MDE1NjEwMWE1NTc4MDgyMDM4MDUxNjAwMTgzNjAyMDAzNjEwMTAwMGEwMzE5MTY4MTUyNjAyMDAxOTE1MDViNTA5MjUwNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MTNiOTA1MDkxOTA1MDU2NWI2MDAwODEzZjkwNTA5MTkwNTA1NjViNjA2MDgxM2I2MDQwNTE5MTUwNjAxZjE5NjAxZjYwMjA4MzAxMDExNjgyMDE2MDQwNTI4MDgyNTI4MDYwMDA2MDIwODQwMTg1M2M4MDYwMjA4MzAxZjNmZWEyNjU2MjdhN2E3MjMxNTgyMDVkNmZmZTc3YzJhYzQxMDgyM2M5ZjU1Y2Q5NjIyMjU3ZTE4NjkzODBkM2M1ZjI3ODFlN2I5ZjhmODY3MWY5ZGU2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw60zCtf1jyWopQmtQP0KwAS+4aBIEe6QxOeslL0wCtsH5STgfD0Y4WZtSyymvcAllGgwIrOuQrAYQi6aQ1gIiDwoJCPDqkKwGEKIGEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjx6pCsBhCkBhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJ8JGiISIEeAQi8O0UfIi0ZfaJZ0H662/Dc/YxloJBlGw9Wk5MKdIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDghVInrzDzps8bIqeyXcTqQI/C+5jrFjSw/BfYsbvW9wYhfa4vzBkBeYX4qoT2bcQaCwit65CsBhCrzbFeIg8KCQjx6pCsBhCkBhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC3wYKAxigCRKqBGCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAEFXYAA1YOAcgGMMuPdOFGEARleAY10AcoEUYQCeV4BjyVHCzhRhAPZXW2AAgP1bYQCIYASANgNgIIEQFWEAXFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkFBQUGEBs1ZbYEBRgIKBUmAgAZFQUGBAUYCRA5DzW2EA4GAEgDYDYCCBEBVhALRXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAb5WW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81thAThgBIA2A2AggRAVYQEMV2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQHJVltgQFGAgGAgAYKBA4JSg4GBUYFSYCABkVCAUZBgIAGQgIODYABbg4EQFWEBeFeAggFRgYQBUmAggQGQUGEBXVZbUFBQUJBQkIEBkGAfFoAVYQGlV4CCA4BRYAGDYCADYQEACgMZFoFSYCABkVBbUJJQUFBgQFGAkQOQ81tgAIE7kFCRkFBWW2AAgT+QUJGQUFZbYGCBO2BAUZFQYB8ZYB9gIIMBARaCAWBAUoCCUoBgAGAghAGFPIBgIIMB8/6iZWJ6enIxWCBdb/53wqxBCCPJ9VzZYiJX4YaTgNPF8ngee5+PhnH53mRzb2xjQwAFEQAyIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxigCUoWChQAAAAAAAAAAAAAAAAAAAAAAAAEoHIHCgMYoAkQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQjx6pCsBhCmBhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIE7BvzkP8hgzTChrZ2bmDFmZZxC0lHxUBZqbvSRlB2CSEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGKEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD2pskiDg3Otdh7JX2m0DF1QAvswhHHCmDsHM8rVuNcEr61qUDbUauqKDsYH3rkINQaDAit65CsBhCbz6jgAiIPCgkI8eqQrAYQpgYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxihCRCAqNa5Bw=="},{"b64Body":"Cg8KCQjy6pCsBhCoBhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYoAkQoI0GIiRdAHKBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASNFY=","b64Record":"CiUIHSIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhZByFPD4KCAz9pvU1eT3NWucZoSri25huUkE0foPZ/9Mjo9v+FHIbNMzdhOukUuoaDAiu65CsBhC7vtuEASIPCgkI8uqQrAYQqAYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQjy6pCsBhCuBhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYoAkQoI0GIiRdAHKBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKE=","b64Record":"CiUIFiIDGKAJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCIEoV49uRwqPp1SP3Sgn2KUn8+2eYksjKtiF9+yi/m251EmzA2aqlNmeBXXPIqB7caDAiu65CsBhDj3NHqAiIPCgkI8uqQrAYQrgYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYoAkSIMXSRgGG9yM8kn59stzHA8DlALZTyoInO3v62ARdhaRwIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/record-snapshots/ExtCodeSizeOperation.json b/hedera-node/test-clients/record-snapshots/ExtCodeSizeOperation.json new file mode 100644 index 000000000000..3e8b2ee8dc16 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/ExtCodeSizeOperation.json @@ -0,0 +1 @@ +{"specSnapshots":{"VerifiesExistence":{"placeholderNum":1178,"encodedItems":[{"b64Body":"Cg8KCQjL6pCsBhCQARICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiHueuvBhC4yod1Gm0KIhIgwJaHL+ps0jyFNdwMWzJ46H8Jr7rdpcByD1Xx4WchFt8KIzohAzvbHOU8CweTx2PcTwQq9OrUg1cxmo9ZAd6NtEUNVWISCiISIJkjd0pUlCzZZxznilWX39mUjTqAMBBODE69adLuGRg/IgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGJsJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjADWI+wNZUxtGhidNjemee6Ns4IAp1He3eVK5QifVNAzzDX5Xn3QlDxZrAnLeJwMioaDAiH65CsBhDTgvuNASIPCgkIy+qQrAYQkAESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjL6pCsBhCUARICGAISAhgDGMCMrzEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBnAkKAxibCSKUCTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDIyYTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0MTU3NjAwMDM1NjBlMDFjODA2MzBjYjhmNzRlMTQ2MTAwNDY1NzgwNjM1ZDAwNzI4MTE0NjEwMDllNTc4MDYzYzk1MWMyY2UxNDYxMDBmNjU3NWI2MDAwODBmZDViNjEwMDg4NjAwNDgwMzYwMzYwMjA4MTEwMTU2MTAwNWM1NzYwMDA4MGZkNWI4MTAxOTA4MDgwMzU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNjAyMDAxOTA5MjkxOTA1MDUwNTA2MTAxYjM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZTA2MDA0ODAzNjAzNjAyMDgxMTAxNTYxMDBiNDU3NjAwMDgwZmQ1YjgxMDE5MDgwODAzNTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2OTA2MDIwMDE5MDkyOTE5MDUwNTA1MDYxMDFiZTU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYxMDEzODYwMDQ4MDM2MDM2MDIwODExMDE1NjEwMTBjNTc2MDAwODBmZDViODEwMTkwODA4MDM1NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY5MDYwMjAwMTkwOTI5MTkwNTA1MDUwNjEwMWM5NTY1YjYwNDA1MTgwODA2MDIwMDE4MjgxMDM4MjUyODM4MTgxNTE4MTUyNjAyMDAxOTE1MDgwNTE5MDYwMjAwMTkwODA4MzgzNjAwMDViODM4MTEwMTU2MTAxNzg1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAxNWQ1NjViNTA1MDUwNTA5MDUwOTA4MTAxOTA2MDFmMTY4MDE1NjEwMWE1NTc4MDgyMDM4MDUxNjAwMTgzNjAyMDAzNjEwMTAwMGEwMzE5MTY4MTUyNjAyMDAxOTE1MDViNTA5MjUwNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MTNiOTA1MDkxOTA1MDU2NWI2MDAwODEzZjkwNTA5MTkwNTA1NjViNjA2MDgxM2I2MDQwNTE5MTUwNjAxZjE5NjAxZjYwMjA4MzAxMDExNjgyMDE2MDQwNTI4MDgyNTI4MDYwMDA2MDIwODQwMTg1M2M4MDYwMjA4MzAxZjNmZWEyNjU2MjdhN2E3MjMxNTgyMDVkNmZmZTc3YzJhYzQxMDgyM2M5ZjU1Y2Q5NjIyMjU3ZTE4NjkzODBkM2M1ZjI3ODFlN2I5ZjhmODY3MWY5ZGU2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwWwPUOUu9Mq0zK01y52cT/oRatbJzqE8onIACIih3LFvdqGSwVsATpqihJN8nxmyXGgwIh+uQrAYQm5CtjwMiDwoJCMvqkKwGEJQBEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjM6pCsBhCWARICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJsJGiISIO6B0OScGlOaeS1hOINXc0+e7R4EvhkHKN9iQNqdnVcpIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJwJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhU0KqnSoUxcBILwznMuA73AaMuAMTY1pTwPru6Kf7Vo6WqBYVlqDPNJm1LARkEwgaDAiI65CsBhCTytqXASIPCgkIzOqQrAYQlgESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQt8GCgMYnAkSqgRggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBBV2AANWDgHIBjDLj3ThRhAEZXgGNdAHKBFGEAnleAY8lRws4UYQD2V1tgAID9W2EAiGAEgDYDYCCBEBVhAFxXYACA/VuBAZCAgDVz//////////////////////////8WkGAgAZCSkZBQUFBhAbNWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81thAOBgBIA2A2AggRAVYQC0V2AAgP1bgQGQgIA1c///////////////////////////FpBgIAGQkpGQUFBQYQG+VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbYQE4YASANgNgIIEQFWEBDFdgAID9W4EBkICANXP//////////////////////////xaQYCABkJKRkFBQUGEByVZbYEBRgIBgIAGCgQOCUoOBgVGBUmAgAZFQgFGQYCABkICDg2AAW4OBEBVhAXhXgIIBUYGEAVJgIIEBkFBhAV1WW1BQUFCQUJCBAZBgHxaAFWEBpVeAggOAUWABg2AgA2EBAAoDGRaBUmAgAZFQW1CSUFBQYEBRgJEDkPNbYACBO5BQkZBQVltgAIE/kFCRkFBWW2BggTtgQFGRUGAfGWAfYCCDAQEWggFgQFKAglKAYABgIIQBhTyAYCCDAfP+omVienpyMVggXW/+d8KsQQgjyfVc2WIiV+GGk4DTxfJ4Hnufj4Zx+d5kc29sY0MABREAMiKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYnAlKFgoUAAAAAAAAAAAAAAAAAAAAAAAABJxyBwoDGJwJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjM6pCsBhCYARICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISILGNsu+41rReLvoVS9pmWyhT3aOLSeQRq5JFUMl7AhldEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGJ0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAviebVwoudPA8ZAL1BV5HsWprEQJsveocpkiNPabNK2V8+kjXhQp9/akZSMCs1p9IaDAiI65CsBhCr0+mYAyIPCgkIzOqQrAYQmAESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxidCRCAqNa5Bw=="},{"b64Body":"Cg8KCQjN6pCsBhCaARICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYnAkQoI0GIiQMuPdOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASNFY=","b64Record":"CiUIHSIDGJwJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDa5dIJOGAA/UrCkko00Xe04Ou5dHppTAT4aGGyH63S1/jZA7XB6Ssr1Gxdz4B1FaQaDAiJ65CsBhCj85u+ASIPCgkIzeqQrAYQmgESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQjN6pCsBhCgARICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYnAkQoI0GIiQMuPdOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJ0=","b64Record":"CiUIFiIDGJwJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdeplgc2pPxY9czA4ir+Uxv9AVR04Bz/EFMr6U2yVsQ+H+q7G2emUm7snZlm5CqycaDAiJ65CsBhCTzM2jAyIPCgkIzeqQrAYQoAESAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYnAkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/record-snapshots/GlobalProperties.json b/hedera-node/test-clients/record-snapshots/GlobalProperties.json new file mode 100644 index 000000000000..3dbed36c3349 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/GlobalProperties.json @@ -0,0 +1 @@ +{"specSnapshots":{"chainIdWorks":{"placeholderNum":1212,"encodedItems":[{"b64Body":"Cg8KCQi1vpOsBhDbBhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjxjO6vBhCAj9LaAhptCiISIMypSvqUR8zHa6J8fullWCLe9ATlwCaH6ZbvRHI0g2u2CiM6IQJxRKjwsltLnnzSjzj23UiYGj2I0jMoteQOZDAihBS5cAoiEiCawKco0fV6UwUnk7pqT6tg1iDwSlGqFgcFLmAY5a3veCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGL0JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBTh1xd3YcyH+UI065MxQmrjT4zSkjgpV/+sMabDXahusHbhEugsX1x4rXbALcgOYAaDAjxvpOsBhCbioP0AiIPCgkItb6TrAYQ2wYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi2vpOsBhDfBhICGAISAhgDGJbvpTAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBpgcKAxi9CSKeBzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDFhZjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzE1ZTgxMmFkMTQ2MTAwNTE1NzgwNjMxYTkzZDFjMzE0NjEwMDZmNTc4MDYzNTY0YjgxZWYxNDYxMDA4ZDU3ODA2M2QxYTgyYTlkMTQ2MTAwYWI1NzViNjAwMDgwZmQ1YjYxMDA1OTYxMDBjOTU2NWI2MDQwNTE2MTAwNjY5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA3NzYxMDBkMTU2NWI2MDQwNTE2MTAwODQ5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA5NTYxMDBkOTU2NWI2MDQwNTE2MTAwYTI5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBiMzYxMDBlMTU2NWI2MDQwNTE2MTAwYzA5MTkwNjEwMTA3NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA0ODkwNTA5MDU2NWI2MDAwNDU5MDUwOTA1NjViNjAwMDQ2OTA1MDkwNTY1YjYwMDA0MTkwNTA5MDU2NWI2MTAwZjI4MTYxMDEzZDU2NWI4MjUyNTA1MDU2NWI2MTAxMDE4MTYxMDE2ZjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTFjNjAwMDgzMDE4NDYxMDBlOTU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTM3NjAwMDgzMDE4NDYxMDBmODU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwMTQ4ODI2MTAxNGY1NjViOTA1MDkxOTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDQzY2Y2OTY5ZDczNTVhNjI2YzNjOGI5NGIxMzQ4YzYwMDU1ZDYwM2ExMTE4MTQ5MzZkMTYzODk3ZmUyODJkYjQ2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwHdIPSZFugLZxnC7xZM2tkMdvFLDgiPtin8bcfCvYySFaFRl5M/yUQ1ypE0NKytG/GgwI8r6TrAYQm/unmQEiDwoJCLa+k6wGEN8GEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQi2vpOsBhDhBhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGL0JGiISIOdsLAD1TuiKCGqO+T5NdqCtoRmtfR4b4Mapc7JbDgxHIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC4lYidYsTYqSzXcK/74rBaQF6nci1EYCSVglpXIogQf+8oOR91sBi1R+1EnmRXNnwaDAjyvpOsBhCrz5GaAyIPCgkItr6TrAYQ4QYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQuQFCgMYvgkSrwNggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjFegSrRRhAFFXgGMak9HDFGEAb1eAY1ZLge8UYQCNV4Bj0agqnRRhAKtXW2AAgP1bYQBZYQDJVltgQFFhAGaRkGEBIlZbYEBRgJEDkPNbYQB3YQDRVltgQFFhAISRkGEBIlZbYEBRgJEDkPNbYQCVYQDZVltgQFFhAKKRkGEBIlZbYEBRgJEDkPNbYQCzYQDhVltgQFFhAMCRkGEBB1ZbYEBRgJEDkPNbYABIkFCQVltgAEWQUJBWW2AARpBQkFZbYABBkFCQVlthAPKBYQE9VluCUlBQVlthAQGBYQFvVluCUlBQVltgAGAgggGQUGEBHGAAgwGEYQDpVluSkVBQVltgAGAgggGQUGEBN2AAgwGEYQD4VluSkVBQVltgAGEBSIJhAU9WW5BQkZBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAgZBQkZBQVv6iZGlwZnNYIhIgQ89padc1WmJsPIuUsTSMYAVdYDoRGBSTbRY4l/4oLbRkc29sY0MACAcAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYvglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABL5yBwoDGL4JEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQi3vpOsBhDjBhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYvgkQoI0GIgRWS4Hv","b64Record":"CiUIFiIDGL4JKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDbMX+TPQBCHzKfJkGQxHyy9U1gn7snCmQy8zOJS3/ejn23gf4d21QPc+EEl87YjmoaDAjzvpOsBhC7u/+iASIPCgkIt76TrAYQ4wYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYvgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"baseFeeWorks":{"placeholderNum":1216,"encodedItems":[{"b64Body":"Cg8KCQi8vpOsBhD/BhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwj4jO6vBhCYkt1pGm0KIhIgu9+mSoNJkhPdprUDJxKIqTERUMN3ck+C3UDgkiQtrvAKIzohA5MAd+it7XTtYKCXouhXCUfpLUPBxDoXYXsjH8iu6iL3CiISIBB0CuwAZKv3QX9cIJLAJzyWYGTXTRd2PjTWtYruw2GyIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGMEJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCwoUYzBIlM4NaNv3TTQEZZNViMGJ0X3x5e6rNX9BhyN4t8CnFsidCCqHRvRH06QREaDAj4vpOsBhDj27yFASIPCgkIvL6TrAYQ/wYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi8vpOsBhCDBxICGAISAhgDGJbvpTAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBpgcKAxjBCSKeBzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDFhZjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzE1ZTgxMmFkMTQ2MTAwNTE1NzgwNjMxYTkzZDFjMzE0NjEwMDZmNTc4MDYzNTY0YjgxZWYxNDYxMDA4ZDU3ODA2M2QxYTgyYTlkMTQ2MTAwYWI1NzViNjAwMDgwZmQ1YjYxMDA1OTYxMDBjOTU2NWI2MDQwNTE2MTAwNjY5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA3NzYxMDBkMTU2NWI2MDQwNTE2MTAwODQ5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA5NTYxMDBkOTU2NWI2MDQwNTE2MTAwYTI5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBiMzYxMDBlMTU2NWI2MDQwNTE2MTAwYzA5MTkwNjEwMTA3NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA0ODkwNTA5MDU2NWI2MDAwNDU5MDUwOTA1NjViNjAwMDQ2OTA1MDkwNTY1YjYwMDA0MTkwNTA5MDU2NWI2MTAwZjI4MTYxMDEzZDU2NWI4MjUyNTA1MDU2NWI2MTAxMDE4MTYxMDE2ZjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTFjNjAwMDgzMDE4NDYxMDBlOTU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTM3NjAwMDgzMDE4NDYxMDBmODU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwMTQ4ODI2MTAxNGY1NjViOTA1MDkxOTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDQzY2Y2OTY5ZDczNTVhNjI2YzNjOGI5NGIxMzQ4YzYwMDU1ZDYwM2ExMTE4MTQ5MzZkMTYzODk3ZmUyODJkYjQ2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw3QTDIzMfAENvWF/9QnNf6oBsm4GWSXs1Lkgt0zcO2bLDss/wAbnZMgghgK4NUqnIGgwI+L6TrAYQm66V6gIiDwoJCLy+k6wGEIMHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQi9vpOsBhCFBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGMEJGiISII32TCZKhNLXkapC0O3gUYoQdAx5YyOTGn0yylOODgkhIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGMIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCTR5r7s2mfQM8S4ejv3pZpLisvAHPynvgy5e0VKschnem8rPlSwIjcK+0+iKRpr/4aDAj5vpOsBhCbztSPASIPCgkIvb6TrAYQhQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQuQFCgMYwgkSrwNggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjFegSrRRhAFFXgGMak9HDFGEAb1eAY1ZLge8UYQCNV4Bj0agqnRRhAKtXW2AAgP1bYQBZYQDJVltgQFFhAGaRkGEBIlZbYEBRgJEDkPNbYQB3YQDRVltgQFFhAISRkGEBIlZbYEBRgJEDkPNbYQCVYQDZVltgQFFhAKKRkGEBIlZbYEBRgJEDkPNbYQCzYQDhVltgQFFhAMCRkGEBB1ZbYEBRgJEDkPNbYABIkFCQVltgAEWQUJBWW2AARpBQkFZbYABBkFCQVlthAPKBYQE9VluCUlBQVlthAQGBYQFvVluCUlBQVltgAGAgggGQUGEBHGAAgwGEYQDpVluSkVBQVltgAGAgggGQUGEBN2AAgwGEYQD4VluSkVBQVltgAGEBSIJhAU9WW5BQkZBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAgZBQkZBQVv6iZGlwZnNYIhIgQ89padc1WmJsPIuUsTSMYAVdYDoRGBSTbRY4l/4oLbRkc29sY0MACAcAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYwglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABMJyBwoDGMIJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQi9vpOsBhCHBxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYwgkQoI0GIgQV6BKt","b64Record":"CiUIFiIDGMIJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCKx460TBx1fIOBtOgGByKTraVfQgG8I6LlhT3Wg1RP8wWEWW0GTDI740Zs9XXAuRAaDAj5vpOsBhCDq870AiIPCgkIvb6TrAYQhwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYwgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"coinbaseWorks":{"placeholderNum":1220,"encodedItems":[{"b64Body":"Cg8KCQjCvpOsBhCjBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj+jO6vBhDA+qLlAhptCiISIDzI4HP97QndN5tR5xEvOTln5Bz2YBeTuuLS/3jSXRUVCiM6IQPwO5IYTwFbztvZm30Nm5f7e1MDOQfN4OQBwrhuWEaL+woiEiA+CeAW6Gq/aUDdphtZ1J6eeKjqmRT2KLxkEdyLxyErHyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMUJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZu+2LKxfICzoIMqDDfNhRL8BgQh6TBGZ0bXaA379Dvz89oLxz5xf1s8sKmOwjY5MaDAj+vpOsBhCrmonrAiIPCgkIwr6TrAYQowcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjDvpOsBhCnBxICGAISAhgDGJbvpTAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBpgcKAxjFCSKeBzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDFhZjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzE1ZTgxMmFkMTQ2MTAwNTE1NzgwNjMxYTkzZDFjMzE0NjEwMDZmNTc4MDYzNTY0YjgxZWYxNDYxMDA4ZDU3ODA2M2QxYTgyYTlkMTQ2MTAwYWI1NzViNjAwMDgwZmQ1YjYxMDA1OTYxMDBjOTU2NWI2MDQwNTE2MTAwNjY5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA3NzYxMDBkMTU2NWI2MDQwNTE2MTAwODQ5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA5NTYxMDBkOTU2NWI2MDQwNTE2MTAwYTI5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBiMzYxMDBlMTU2NWI2MDQwNTE2MTAwYzA5MTkwNjEwMTA3NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA0ODkwNTA5MDU2NWI2MDAwNDU5MDUwOTA1NjViNjAwMDQ2OTA1MDkwNTY1YjYwMDA0MTkwNTA5MDU2NWI2MTAwZjI4MTYxMDEzZDU2NWI4MjUyNTA1MDU2NWI2MTAxMDE4MTYxMDE2ZjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTFjNjAwMDgzMDE4NDYxMDBlOTU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTM3NjAwMDgzMDE4NDYxMDBmODU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwMTQ4ODI2MTAxNGY1NjViOTA1MDkxOTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDQzY2Y2OTY5ZDczNTVhNjI2YzNjOGI5NGIxMzQ4YzYwMDU1ZDYwM2ExMTE4MTQ5MzZkMTYzODk3ZmUyODJkYjQ2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwKbkDhhw3lG2ElWnWdUeAdpM88RkRKD8xtH9qf2RkNGX5XYsZOxmOaFlooAPNjCp1GgwI/76TrAYQg5TtjgEiDwoJCMO+k6wGEKcHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjDvpOsBhCpBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGMUJGiISIEjbSxU8K2LqnouoacBgU06BpfRWTt55ynQjKXAbByyBIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGMYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDcg/y73z9uDQNvz8O0v4ZLBjN79RF5esdY0OHwt21ExPXliy5NvwAxuPSc0I5k39saDAj/vpOsBhDj+/z0AiIPCgkIw76TrAYQqQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQuQFCgMYxgkSrwNggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjFegSrRRhAFFXgGMak9HDFGEAb1eAY1ZLge8UYQCNV4Bj0agqnRRhAKtXW2AAgP1bYQBZYQDJVltgQFFhAGaRkGEBIlZbYEBRgJEDkPNbYQB3YQDRVltgQFFhAISRkGEBIlZbYEBRgJEDkPNbYQCVYQDZVltgQFFhAKKRkGEBIlZbYEBRgJEDkPNbYQCzYQDhVltgQFFhAMCRkGEBB1ZbYEBRgJEDkPNbYABIkFCQVltgAEWQUJBWW2AARpBQkFZbYABBkFCQVlthAPKBYQE9VluCUlBQVlthAQGBYQFvVluCUlBQVltgAGAgggGQUGEBHGAAgwGEYQDpVluSkVBQVltgAGAgggGQUGEBN2AAgwGEYQD4VluSkVBQVltgAGEBSIJhAU9WW5BQkZBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAgZBQkZBQVv6iZGlwZnNYIhIgQ89padc1WmJsPIuUsTSMYAVdYDoRGBSTbRY4l/4oLbRkc29sY0MACAcAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYxglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABMZyBwoDGMYJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjEvpOsBhCrBxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYxgkQoI0GIgTRqCqd","b64Record":"CiUIFiIDGMYJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAPOgnHVWq5n7lVl0NCcpv/py7avh5Xu1VuK+1p1rhjHxze/hZUbjvu8sfcJlXXO3IaDAiAv5OsBhDjw4iZASIPCgkIxL6TrAYQqwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYxgkSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABiIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="}]},"gasLimitWorks":{"placeholderNum":1224,"encodedItems":[{"b64Body":"Cg8KCQjJvpOsBhDHBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiFje6vBhDwscaPARptCiISIJj6gkt87acu/h6fKBKdtzzY4gfI1k28YXIkDbYjLi7hCiM6IQPw5OPynzrcTuyvNd1+arQUaFTLZ1LKppJmLGuCTeh2ywoiEiCMNe7oawfRjfZljmhvFCuqmXNfX94OBtoC7sAOEYMGpCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMkJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCew4XOzHJ7Y8fIPhXvuFrGLRvKGAdwA5ko/xyG/1/QqC6XI95jx/w5A1jGYMgvLd8aDAiFv5OsBhD7g9ahASIPCgkIyb6TrAYQxwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjJvpOsBhDLBxICGAISAhgDGJbvpTAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBpgcKAxjJCSKeBzYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDFhZjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzE1ZTgxMmFkMTQ2MTAwNTE1NzgwNjMxYTkzZDFjMzE0NjEwMDZmNTc4MDYzNTY0YjgxZWYxNDYxMDA4ZDU3ODA2M2QxYTgyYTlkMTQ2MTAwYWI1NzViNjAwMDgwZmQ1YjYxMDA1OTYxMDBjOTU2NWI2MDQwNTE2MTAwNjY5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA3NzYxMDBkMTU2NWI2MDQwNTE2MTAwODQ5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA5NTYxMDBkOTU2NWI2MDQwNTE2MTAwYTI5MTkwNjEwMTIyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDBiMzYxMDBlMTU2NWI2MDQwNTE2MTAwYzA5MTkwNjEwMTA3NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA0ODkwNTA5MDU2NWI2MDAwNDU5MDUwOTA1NjViNjAwMDQ2OTA1MDkwNTY1YjYwMDA0MTkwNTA5MDU2NWI2MTAwZjI4MTYxMDEzZDU2NWI4MjUyNTA1MDU2NWI2MTAxMDE4MTYxMDE2ZjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTFjNjAwMDgzMDE4NDYxMDBlOTU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTM3NjAwMDgzMDE4NDYxMDBmODU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwMTQ4ODI2MTAxNGY1NjViOTA1MDkxOTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDQzY2Y2OTY5ZDczNTVhNjI2YzNjOGI5NGIxMzQ4YzYwMDU1ZDYwM2ExMTE4MTQ5MzZkMTYzODk3ZmUyODJkYjQ2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwp/eODpOa/Satw4fnnF0t8sYuCuGl+pD3OMcDOskYrO8kgc9fwrZZrvviAfF/cfBvGgwIhb+TrAYQg8O2owMiDwoJCMm+k6wGEMsHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjKvpOsBhDNBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGMkJGiISIDfOsL7urEHq3zQsW3o+yz01t1CFCyYqFX4VLkRv3c1eIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGMoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB5F2LjxNUAFTqsEUYRLyotsvYDf1oT2STJ7zysr/Dm+Qlr2MHcqcMg6D4OkPhI5P0aDAiGv5OsBhD76fi0ASIPCgkIyr6TrAYQzQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQuQFCgMYygkSrwNggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjFegSrRRhAFFXgGMak9HDFGEAb1eAY1ZLge8UYQCNV4Bj0agqnRRhAKtXW2AAgP1bYQBZYQDJVltgQFFhAGaRkGEBIlZbYEBRgJEDkPNbYQB3YQDRVltgQFFhAISRkGEBIlZbYEBRgJEDkPNbYQCVYQDZVltgQFFhAKKRkGEBIlZbYEBRgJEDkPNbYQCzYQDhVltgQFFhAMCRkGEBB1ZbYEBRgJEDkPNbYABIkFCQVltgAEWQUJBWW2AARpBQkFZbYABBkFCQVlthAPKBYQE9VluCUlBQVlthAQGBYQFvVluCUlBQVltgAGAgggGQUGEBHGAAgwGEYQDpVluSkVBQVltgAGAgggGQUGEBN2AAgwGEYQD4VluSkVBQVltgAGEBSIJhAU9WW5BQkZBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAgZBQkZBQVv6iZGlwZnNYIhIgQ89padc1WmJsPIuUsTSMYAVdYDoRGBSTbRY4l/4oLbRkc29sY0MACAcAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYyglKFgoUAAAAAAAAAAAAAAAAAAAAAAAABMpyBwoDGMoJEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjKvpOsBhDPBxICGAISAhgDGMC46vsDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46EAoDGMoJEMDDkwciBBqT0cM=","b64Record":"CiUIFiIDGMoJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDN73kVcF8Eq3awZaF2cEZ0s7+niSwUH9Zi0AW+QkCHtV5gp7O2yhkY5rjgqVkN9wcaDAiGv5OsBhCjjO22AyIPCgkIyr6TrAYQzwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA+qGWAzqvAgoDGMoJEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOThwCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogLbcBVIYCgoKAhgCEP/zw6wGCgoKAhhiEID0w6wG"}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java index 75aa82e6c810..8e3d1b6f6343 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/DelegateCallOperationSuite.java @@ -23,7 +23,9 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -62,7 +64,10 @@ HapiSpec verifiesExistence() { final var contract = "CallOperationsChecker"; final var INVALID_ADDRESS = "0x0000000000000000000000000000000000123456"; return defaultHapiSpec("VerifiesExistence") - .given(uploadInitCode(contract), contractCreate(contract)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(contract), + contractCreate(contract)) .when() .then( contractCall(contract, "delegateCall", asHeadlongAddress(INVALID_ADDRESS)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java index 1eff4ed465bc..721938300304 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeCopyOperationSuite.java @@ -28,7 +28,10 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import com.google.protobuf.ByteString; @@ -72,7 +75,11 @@ HapiSpec verifiesExistence() { final var account = "account"; return defaultHapiSpec("VerifiesExistence") - .given(cryptoCreate(account), uploadInitCode(contract), contractCreate(contract)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_FUNCTION_PARAMETERS), + cryptoCreate(account), + uploadInitCode(contract), + contractCreate(contract)) .when() .then( contractCall(contract, codeCopyOf, asHeadlongAddress(invalidAddress)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java index 8ab37156667f..35b0324b112f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeHashOperationSuite.java @@ -28,7 +28,10 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; import com.google.protobuf.ByteString; @@ -75,7 +78,11 @@ HapiSpec verifiesExistence() { final String account = "account"; return defaultHapiSpec("VerifiesExistence") - .given(uploadInitCode(contract), contractCreate(contract), cryptoCreate(account)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_FUNCTION_PARAMETERS), + uploadInitCode(contract), + contractCreate(contract), + cryptoCreate(account)) .when() .then( contractCall(contract, hashOf, asHeadlongAddress(invalidAddress)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java index a872a0f7dcda..04309eae803b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java @@ -31,7 +31,10 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; @@ -77,7 +80,11 @@ HapiSpec verifiesExistence() { final var account = "account"; return defaultHapiSpec("VerifiesExistence") - .given(uploadInitCode(contract), contractCreate(contract), cryptoCreate(account)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_FUNCTION_PARAMETERS), + uploadInitCode(contract), + contractCreate(contract), + cryptoCreate(account)) .when() .then( contractCall(contract, sizeOf, asHeadlongAddress(invalidAddress)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java index 29be654e1313..0360290630e5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java @@ -26,7 +26,9 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; @@ -80,7 +82,10 @@ final HapiSpec chainIdWorks() { final var devChainId = BigInteger.valueOf(298L); final Set acceptableChainIds = Set.of(devChainId, defaultChainId); return defaultHapiSpec("chainIdWorks") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, GET_CHAIN_ID).via("chainId")) .then( getTxnRecord("chainId") @@ -102,7 +107,10 @@ final HapiSpec chainIdWorks() { final HapiSpec baseFeeWorks() { final var expectedBaseFee = BigInteger.valueOf(0); return defaultHapiSpec("baseFeeWorks") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, GET_BASE_FEE).via("baseFee")) .then( getTxnRecord("baseFee") @@ -125,7 +133,10 @@ final HapiSpec baseFeeWorks() { @HapiTest final HapiSpec coinbaseWorks() { return defaultHapiSpec("coinbaseWorks") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "getCoinbase").via("coinbase")) .then(withOpContext((spec, opLog) -> { final var expectedCoinbase = @@ -149,7 +160,10 @@ final HapiSpec coinbaseWorks() { final HapiSpec gasLimitWorks() { final var gasLimit = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("contracts.maxGasPerSec")); return defaultHapiSpec("gasLimitWorks") - .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) + .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), + contractCreate(CONTRACT)) .when(contractCall(CONTRACT, GET_GAS_LIMIT).via("gasLimit").gas(gasLimit)) .then( getTxnRecord("gasLimit") From c7dd6e6c944b99a8ba0cafd5e637d3dc18123822 Mon Sep 17 00:00:00 2001 From: Miroslav Gatsanoga Date: Sun, 24 Dec 2023 23:30:24 +0200 Subject: [PATCH 41/80] fix: conditional records of hollow account creation via internal transfer with `EthereumTransaction` (#10539) Signed-off-by: Miroslav Gatsanoga Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../app/workflows/handle/HandleWorkflow.java | 18 ++++++-- .../scope/HandleHederaNativeOperations.java | 17 +++++++ .../HandleHederaNativeOperationsTest.java | 45 ++++++++++++++++--- .../record-snapshots/LeakyCryptoTests.json | 1 + .../bdd/spec/utilops/domain/ParsedItem.java | 12 +++++ .../spec/utilops/domain/RecordSnapshot.java | 5 ++- .../spec/utilops/records/SnapshotModeOp.java | 13 ++++-- .../suites/leaky/LeakyCryptoTestsSuite.java | 4 ++ 8 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 hedera-node/test-clients/record-snapshots/LeakyCryptoTests.json diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 0da10d5c58bb..6d32033e85b6 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -100,6 +100,7 @@ import com.swirlds.platform.system.transaction.ConsensusTransaction; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; +import java.util.EnumSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -113,6 +114,10 @@ public class HandleWorkflow { private static final Logger logger = LogManager.getLogger(HandleWorkflow.class); + private static final Set DISPATCHING_CONTRACT_TRANSACTIONS = EnumSet.of( + HederaFunctionality.CONTRACT_CREATE, + HederaFunctionality.CONTRACT_CALL, + HederaFunctionality.ETHEREUM_TRANSACTION); private final NetworkInfo networkInfo; private final PreHandleWorkflow preHandleWorkflow; @@ -437,8 +442,14 @@ private void handleUserTransaction( // Dispatch the transaction to the handler dispatcher.dispatchHandle(context); - // Possibly charge assessed fees for preceding child transactions - if (!recordListBuilder.precedingRecordBuilders().isEmpty()) { + // Possibly charge assessed fees for preceding child transactions; but + // only if not a contract operation, since these dispatches were already + // charged using gas. [FUTURE - stop setting transactionFee in recordBuilder + // at the point of dispatch, so we no longer need this special case here.] + final var isContractOp = + DISPATCHING_CONTRACT_TRANSACTIONS.contains(transactionInfo.functionality()); + if (!isContractOp + && !recordListBuilder.precedingRecordBuilders().isEmpty()) { // We intentionally charge fees even if the transaction failed (may need to update // mono-service to this behavior?) final var childFees = recordListBuilder.precedingRecordBuilders().stream() @@ -685,8 +696,9 @@ private record ValidationResult( /** * Rolls back the stack and sets the status of the transaction in case of a failure. + * * @param rollbackStack whether to rollback the stack. Will be false when the failure is due to a - * {@link HandleException} that is due to a contract call revert. + * {@link HandleException} that is due to a contract call revert. * @param status the status to set * @param stack the save point stack to rollback * @param recordListBuilder the record list builder to revert diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index 5bba8ec06890..5b90688026e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -23,8 +23,10 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.token.CryptoTransferTransactionBody; +import com.hedera.hapi.node.token.CryptoUpdateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; import com.hedera.node.app.service.token.ReadableAccountStore; @@ -33,6 +35,7 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -101,12 +104,18 @@ public void setNonce(final long contractNumber, final long nonce) { final var synthTxn = TransactionBody.newBuilder() .cryptoCreateAccount(synthHollowAccountCreation(evmAddress)) .build(); + // Note the use of the null "verification assistant" callback; we don't want any // signing requirements enforced for this synthetic transaction try { final var childRecordBuilder = context.dispatchRemovablePrecedingTransaction( synthTxn, CryptoCreateRecordBuilder.class, null, context.payer()); childRecordBuilder.memo(LAZY_CREATION_MEMO); + + final var lazyCreateFees = context.dispatchComputeFees(synthTxn, context.payer()); + final var finalizationFees = getLazyCreationFinalizationFees(); + childRecordBuilder.transactionFee(lazyCreateFees.totalFee() + finalizationFees.totalFee()); + return childRecordBuilder.status(); } catch (final HandleException e) { // It is critically important we don't let HandleExceptions propagate to the workflow because @@ -166,4 +175,12 @@ public boolean checkForCustomFees(@NonNull final CryptoTransferTransactionBody o final var tokenServiceApi = context.serviceApi(TokenServiceApi.class); return tokenServiceApi.checkForCustomFees(op); } + + private Fees getLazyCreationFinalizationFees() { + final var updateTxnBody = + CryptoUpdateTransactionBody.newBuilder().key(Key.newBuilder().ecdsaSecp256k1(Bytes.EMPTY)); + final var synthTxn = + TransactionBody.newBuilder().cryptoUpdateAccount(updateTxnBody).build(); + return context.dispatchComputeFees(synthTxn, context.payer()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index f8871dfa84c8..e37a2d0ee123 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -34,6 +34,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.PARANOID_SOMEBODY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SOMEBODY; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.LAZY_CREATION_MEMO; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthHollowAccountCreation; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -51,6 +52,7 @@ import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.token.CryptoTransferTransactionBody; +import com.hedera.hapi.node.token.CryptoUpdateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; @@ -60,7 +62,9 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.function.Predicate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -147,33 +151,64 @@ void getTokenUsesStore() { @Test void createsHollowAccountByDispatching() { - final var synthTxn = TransactionBody.newBuilder() + final var synthLazyCreate = TransactionBody.newBuilder() .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); + when(context.dispatchRemovablePrecedingTransaction( - eq(synthTxn), eq(CryptoCreateRecordBuilder.class), eq(null), eq(A_NEW_ACCOUNT_ID))) + eq(synthLazyCreate), eq(CryptoCreateRecordBuilder.class), eq(null), eq(A_NEW_ACCOUNT_ID))) .thenReturn(cryptoCreateRecordBuilder); + + final var synthLazyCreateFees = new Fees(1L, 2L, 3L); + given(context.dispatchComputeFees(synthLazyCreate, A_NEW_ACCOUNT_ID)).willReturn(synthLazyCreateFees); + + final var synthFinalizatonFees = new Fees(4L, 5L, 6L); + final var synthFinalizationTxn = TransactionBody.newBuilder() + .cryptoUpdateAccount(CryptoUpdateTransactionBody.newBuilder() + .key(Key.newBuilder().ecdsaSecp256k1(Bytes.EMPTY))) + .build(); + given(context.dispatchComputeFees(synthFinalizationTxn, A_NEW_ACCOUNT_ID)) + .willReturn(synthFinalizatonFees); + given(cryptoCreateRecordBuilder.status()).willReturn(OK); final var status = subject.createHollowAccount(CANONICAL_ALIAS); - assertEquals(OK, status); + + verify(cryptoCreateRecordBuilder).memo(LAZY_CREATION_MEMO); + verify(cryptoCreateRecordBuilder) + .transactionFee(synthLazyCreateFees.totalFee() + synthFinalizatonFees.totalFee()); } @Test void createsHollowAccountByDispatchingDoesNotThrowErrors() { - final var synthTxn = TransactionBody.newBuilder() + final var synthLazyCreate = TransactionBody.newBuilder() .cryptoCreateAccount(synthHollowAccountCreation(CANONICAL_ALIAS)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); given(context.dispatchRemovablePrecedingTransaction( - eq(synthTxn), eq(CryptoCreateRecordBuilder.class), eq(null), eq(A_NEW_ACCOUNT_ID))) + eq(synthLazyCreate), eq(CryptoCreateRecordBuilder.class), eq(null), eq(A_NEW_ACCOUNT_ID))) .willReturn(cryptoCreateRecordBuilder); + + final var synthLazyCreateFees = new Fees(1L, 2L, 3L); + given(context.dispatchComputeFees(synthLazyCreate, A_NEW_ACCOUNT_ID)).willReturn(synthLazyCreateFees); + + final var synthFinalizatonFees = new Fees(4L, 5L, 6L); + final var synthFinalizationTxn = TransactionBody.newBuilder() + .cryptoUpdateAccount(CryptoUpdateTransactionBody.newBuilder() + .key(Key.newBuilder().ecdsaSecp256k1(Bytes.EMPTY))) + .build(); + given(context.dispatchComputeFees(synthFinalizationTxn, A_NEW_ACCOUNT_ID)) + .willReturn(synthFinalizatonFees); given(cryptoCreateRecordBuilder.status()).willReturn(MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); final var status = assertDoesNotThrow(() -> subject.createHollowAccount(CANONICAL_ALIAS)); assertThat(status).isEqualTo(MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); + + verify(cryptoCreateRecordBuilder).memo(LAZY_CREATION_MEMO); + verify(cryptoCreateRecordBuilder) + .transactionFee(synthLazyCreateFees.totalFee() + synthFinalizatonFees.totalFee()); } @Test diff --git a/hedera-node/test-clients/record-snapshots/LeakyCryptoTests.json b/hedera-node/test-clients/record-snapshots/LeakyCryptoTests.json new file mode 100644 index 000000000000..35263de2ef4d --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/LeakyCryptoTests.json @@ -0,0 +1 @@ +{"specSnapshots":{"lazyCreateViaEthereumCryptoTransfer":{"placeholderNum":1155,"encodedItems":[{"b64Body":"Cg8KCQi20pGsBhDXCxICGAISAhgDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo6aAboECgIYeRILCPKg7K8GEOjnyjAipgQKKwolaGVkZXJhLmFsbG93YW5jZXMubWF4VHJhbnNhY3Rpb25MaW1pdBICMjAKJgoWdXBncmFkZS5hcnRpZmFjdHMucGF0aBIMZGF0YS91cGdyYWRlCh4KFWNvbnRyYWN0cy5ldm0udmVyc2lvbhIFdjAuMzQKMAoibGVkZ2VyLmF1dG9SZW5ld1BlcmlvZC5tYXhEdXJhdGlvbhIKMTAwMDAwMDAwMAoiChpsZWRnZXIubWF4QXV0b0Fzc29jaWF0aW9ucxIENTAwMAowChdiYWxhbmNlcy5leHBvcnREaXIucGF0aBIVZGF0YS9hY2NvdW50QmFsYW5jZXMvCi0KImxlZGdlci5hdXRvUmVuZXdQZXJpb2QubWluRHVyYXRpb24SBzY5OTk5OTkKJQodY3J5cHRvQ3JlYXRlV2l0aEFsaWFzLmVuYWJsZWQSBHRydWUKKAofZW50aXRpZXMubGltaXRUb2tlbkFzc29jaWF0aW9ucxIFZmFsc2UKJwofY29udHJhY3RzLmFsbG93QXV0b0Fzc29jaWF0aW9ucxIEdHJ1ZQocChRsYXp5Q3JlYXRpb24uZW5hYmxlZBIEdHJ1ZQoYChFjb250cmFjdHMuY2hhaW5JZBIDMjk4ChwKFHRva2Vucy5tYXhQZXJBY2NvdW50EgQxMDAwCigKIWhlZGVyYS5hbGxvd2FuY2VzLm1heEFjY291bnRMaW1pdBIDMTAw","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2rDLxOtZFQMCwiwwfSMavBTm7BrtN3bTYYafYsDGuHq8RoreoeduC3knSFjZwkdiGgsI8tKRrAYQ4/jDOiIPCgkIttKRrAYQ1wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi20pGsBhDZCxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlo0CiISILPgdPvcEq1GRU50fE06m4foLm8UokMXb5hIREEzPgeWEICA9pamtogBSgUIgM7aAw==","b64Record":"CiUIFhIDGIQJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjApHQjwstYKfgRHyve/9nOWW2wX1ATd5OBFC+4KhGTdfERzc/e1kzdNlzFhyNEUi/IaDAjy0pGsBhCz2uS8AiIPCgkIttKRrAYQ2QsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIfCg0KAhgCEP//663M7JACCg4KAxiECRCAgOytzOyQAg=="},{"b64Body":"ChEKCQi30pGsBhDbCxICGAIgAVpoCiM6IQOmvN/o1MoYnPcKEgZcSyVS9VermjAh8wgzWjBmS8DLKkoFCIDO2gNqFGF1dG8tY3JlYXRlZCBhY2NvdW50kgEjOiEDprzf6NTKGJz3ChIGXEslUvVXq5owIfMIM1owZkvAyyo=","b64Record":"CgcIFhIDGIUJEjCeFpR5KFMQCylcw5EPxd89pMA7iOuTUGeIfOeho0TvGE/3negPyU+KYkFcj6+Wvt0aCwjz0pGsBhD60IZEIhEKCQi30pGsBhDbCxICGAIgASoUYXV0by1jcmVhdGVkIGFjY291bnRSAKoBFMfR0783EfCTMbjvJQh2aGnnsRoF"},{"b64Body":"Cg8KCQi30pGsBhDbCxICGAISAhgDGKqQBSICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOcj0KOwoKCgIYAhD/j9/ASgotCiUiIzohA6a83+jUyhic9woSBlxLJVL1V6uaMCHzCDNaMGZLwMsqEICQ38BK","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw4eB5WkIjZRsj0UfMgFe1YYHT5AgXHzn9GD16alQFM8g85Uee+0lXhEuaqHwVun0HGgsI89KRrAYQ+9CGRCIPCgkIt9KRrAYQ2wsSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiFCRCAkN/ASg=="},{"b64Body":"ChAKCQi30pGsBhDhCxIDGIQJEgIYAxjChwUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpIDuQEKsAEC+K2CASqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPooAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgwMNQJRT4IL57m1x4iLe1P/0fXXV3D++GIhFY5GCRPQAAIDAAaC/WZp3t1KVig1Y3fpJ7nrM5aiA88JjqB8TUAZkYtF67aB0wIlaxGUIDdqMdVswIeu1NMW5IjxLvNLYIyX039PYHBiAyrXuAQ==","b64Record":"CiAIHiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwsLvq1biaEzTAeS5Lrdq/g/ieQ4tAO5uDXpbt52626+BcJ3gu16HlPFi29KViWb9SGgwI89KRrAYQq/+DxgIiEAoJCLfSkawGEOELEgMYhAkqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMILh5wY6JRoQSU5TVUZGSUNJRU5UX0dBUyjAmgxQwJoMWIDKte4BagMYhQlSKgoHCgIYAxCeLgoJCgIYYhD2ls4NCggKAxigBhDwfAoKCgMYhAkQg8LPDYoBIOvvt4J9sCBr2foAKul3j4hFfZbOQhenmWRuIPdyTefq"},{"b64Body":"ChIKCQi40pGsBhDjCxIDGIQJIAFaOAoCMgBKBQiAztoDahRsYXp5LWNyZWF0ZWQgYWNjb3VudJIBFFPggvnubXHiIt7U//R9ddXcP74Y","b64Record":"CgcIFhIDGIcJEjDioi9aG9jWrcsGf0y1obT1xEZ5Uaomg0Fm3e7AywL4WNb9pm9DrTwsgg1ifcoo4gYaCwj00pGsBhDiqdpJIhIKCQi40pGsBhDjCxIDGIQJIAEqFGxhenktY3JlYXRlZCBhY2NvdW50MInK5RJSAA=="},{"b64Body":"ChAKCQi40pGsBhDjCxIDGIQJEgIYAxjChwUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpIDuQEKsAEC+K2CASoBoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPooAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgx6EgJRT4IL57m1x4iLe1P/0fXXV3D++GIhFY5GCRPQAAIDAAaDCe+psti/ghMQLh5j3tzMFYW7ViXDEHynVTbKmNYfp9KB6BCgbe6r+R0soWNePmyDUisENdaLBImhdxATj/j7TNBiAyrXuAQ==","b64Record":"CiUIFiIDGIcJKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD28eoGPGKreweEnp1faH5GW1kkWxivocVn47bYo9zxJEOxTbPJEAm807dh4bO84ngaCwj00pGsBhDjqdpJIhAKCQi40pGsBhDjCxIDGIQJKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDC05o2OpsCCgMYhwkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYVCAiXpYgMq17gFqAxiFCVJECgcKAhgDEJ4uCgkKAhhiEPb7s2wKCAoDGKAGEPB8CgoKAxiECRCDp7VsCgsKAxiFCRD/k+vcAwoLCgMYhwkQgJTr3AOKASCHGy7uUcFh6xS1ZqwjLOF8p9inQSRqES4CwNcEu8pi5A=="},{"b64Body":"Cg8KCQi50pGsBhCGDBICGAISAhgDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo6aAboECgIYeRILCPWg7K8GENjtnzUipgQKKwolaGVkZXJhLmFsbG93YW5jZXMubWF4VHJhbnNhY3Rpb25MaW1pdBICMjAKJgoWdXBncmFkZS5hcnRpZmFjdHMucGF0aBIMZGF0YS91cGdyYWRlCh4KFWNvbnRyYWN0cy5ldm0udmVyc2lvbhIFdjAuMzgKMAoibGVkZ2VyLmF1dG9SZW5ld1BlcmlvZC5tYXhEdXJhdGlvbhIKMTAwMDAwMDAwMAoiChpsZWRnZXIubWF4QXV0b0Fzc29jaWF0aW9ucxIENTAwMAowChdiYWxhbmNlcy5leHBvcnREaXIucGF0aBIVZGF0YS9hY2NvdW50QmFsYW5jZXMvCi0KImxlZGdlci5hdXRvUmVuZXdQZXJpb2QubWluRHVyYXRpb24SBzY5OTk5OTkKJQodY3J5cHRvQ3JlYXRlV2l0aEFsaWFzLmVuYWJsZWQSBHRydWUKKAofZW50aXRpZXMubGltaXRUb2tlbkFzc29jaWF0aW9ucxIFZmFsc2UKJwofY29udHJhY3RzLmFsbG93QXV0b0Fzc29jaWF0aW9ucxIEdHJ1ZQocChRsYXp5Q3JlYXRpb24uZW5hYmxlZBIEdHJ1ZQoYChFjb250cmFjdHMuY2hhaW5JZBIDMjk4ChwKFHRva2Vucy5tYXhQZXJBY2NvdW50EgQxMDAwCigKIWhlZGVyYS5hbGxvd2FuY2VzLm1heEFjY291bnRMaW1pdBIDMTAw","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwtpaaLhWmJd20BeOJuuD1GOthZfZdheczN8KiHZeVOBpMskVKHeiK2gqhjeuLFlkSGgsI9dKRrAYQu6zUNSIPCgkIudKRrAYQhgwSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/ParsedItem.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/ParsedItem.java index 6c95d1f5a940..df3602bf6cb2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/ParsedItem.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/ParsedItem.java @@ -16,8 +16,11 @@ package com.hedera.services.bdd.spec.utilops.domain; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; + import com.google.protobuf.InvalidProtocolBufferException; import com.hedera.services.stream.proto.RecordStreamItem; +import com.hederahashgraph.api.proto.java.FileID; import com.hederahashgraph.api.proto.java.SignedTransaction; import com.hederahashgraph.api.proto.java.TransactionBody; import com.hederahashgraph.api.proto.java.TransactionRecord; @@ -30,6 +33,9 @@ * @param itemRecord the transaction record */ public record ParsedItem(TransactionBody itemBody, TransactionRecord itemRecord) { + private static final FileID PROPERTIES_FILE_ID = + FileID.newBuilder().setFileNum(121).build(); + public static ParsedItem parse(final RecordStreamItem item) throws InvalidProtocolBufferException { final var txn = item.getTransaction(); final TransactionBody body; @@ -42,4 +48,10 @@ public static ParsedItem parse(final RecordStreamItem item) throws InvalidProtoc } return new ParsedItem(body, item.getRecord()); } + + public boolean isPropertyOverride() { + return itemRecord.getReceipt().getStatus() == SUCCESS + && itemBody.hasFileUpdate() + && PROPERTIES_FILE_ID.equals(itemBody.getFileUpdate().getFileID()); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/RecordSnapshot.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/RecordSnapshot.java index 1b280d1aa40f..9579aff77b64 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/RecordSnapshot.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/domain/RecordSnapshot.java @@ -56,6 +56,9 @@ public void setEncodedItems(@NonNull final List encodedItems) { } public List parsedItems() { - return encodedItems.stream().map(EncodedItem::asParsedItem).toList(); + return encodedItems.stream() + .map(EncodedItem::asParsedItem) + .filter(pi -> !pi.isPropertyOverride()) + .toList(); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java index 4ac7508d66e2..8132eb36923d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java @@ -126,6 +126,9 @@ public class SnapshotModeOp extends UtilOp implements SnapshotOp { // Keys are also regenerated every test execution "ed25519", "ECDSA_secp256k1", + // Ethereum data depends on ECDSA keys + "ethereum_data", + "ethereum_hash", // Plus some other fields that we might prefer to make deterministic "symbol", // Bloom field in ContractCall result @@ -286,6 +289,10 @@ public void finishLifecycle(@NonNull final HapiSpec spec) { boolean placeholderFound = false; for (final var item : allItems) { final var parsedItem = ParsedItem.parse(item); + if (parsedItem.isPropertyOverride()) { + // Property overrides vary with the previous contents of 0.0.121 + continue; + } final var body = parsedItem.itemBody(); if (body.hasNodeStakeUpdate()) { // We cannot ever expect to match node stake update export sequencing @@ -426,11 +433,10 @@ private void fuzzyMatch( "Mismatched field names ('" + expectedName + "' vs '" + actualName + "' between expected " + expectedMessage + " and " + actualMessage + " - " + mismatchContext.get()); } + if (shouldSkip(expectedName, expectedField.getValue().getClass())) { - // System.out.println("YES"); continue; } - // System.out.println("NO"); matchValues( expectedName, expectedField.getValue(), @@ -580,7 +586,8 @@ private void matchSingleValues( "Amount '" + expected + "' and '" + actual + "' varied by more than " + maxVariation + " tinybar - " + mismatchContext.get()); - } else if ("accountNum".equals(fieldName) && matchModes.contains(ALLOW_SKIPPED_ENTITY_IDS)) { + } else if (("accountNum".equals(fieldName) || "contractNum".equals(fieldName)) + && matchModes.contains(ALLOW_SKIPPED_ENTITY_IDS)) { Assertions.assertTrue( (long) expected - (long) actual >= 0, "AccountNum '" + expected + "' was not greater than '" + actual + mismatchContext.get()); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 838ebbed246c..8288c0e96c98 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -74,10 +74,13 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ALLOW_SKIPPED_ENTITY_IDS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.hapi.ContractCreateSuite.EMPTY_CONSTRUCTOR_CONTRACT; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.CRYPTO_TRANSFER_RECEIVER; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.FALSE; @@ -1001,6 +1004,7 @@ final HapiSpec lazyCreateViaEthereumCryptoTransfer() { return propertyPreservingHapiSpec("lazyCreateViaEthereumCryptoTransfer") .preserving(CHAIN_ID_PROP, LAZY_CREATE_PROPERTY_NAME, CONTRACTS_EVM_VERSION_PROP) .given( + snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, ALLOW_SKIPPED_ENTITY_IDS), overridingThree( CHAIN_ID_PROP, "298", From 6cd87bcb9c8dfe15b1817b382c73ad520707bf5c Mon Sep 17 00:00:00 2001 From: Deyan Dimitrov Date: Sun, 24 Dec 2023 23:34:18 +0200 Subject: [PATCH 42/80] fix: `Issue305Spec` test (#10437) Signed-off-by: dikel --- .../java/com/hedera/services/bdd/suites/issues/Issue305Spec.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java index 9e394355d70b..c7c9d910df38 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java @@ -88,6 +88,7 @@ final HapiSpec createDeleteInSameRoundWorks() { getFileInfo(nextFileId::get).hasDeleted(true)); } + @HapiTest final HapiSpec congestionMultipliersRefreshOnPropertyUpdate() { final var civilian = "civilian"; final var preCongestionTxn = "preCongestionTxn"; From c6cd6f19b491e4665ae455178e53d786e03c0766 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Sun, 24 Dec 2023 16:05:40 -0600 Subject: [PATCH 43/80] chore: cleanup while itemizing remaining disabled specs (#10640) Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../fees/congestion/ThrottleMultiplier.java | 2 +- .../app/throttle/ThrottleAccumulator.java | 4 +- .../file/impl/handlers/FileUpdateHandler.java | 3 +- .../mono/throttling/ThrottleReqsManager.java | 2 +- .../src/test/resources/bootstrap.properties | 2 +- .../impl/exec/TransactionProcessor.java | 3 +- .../impl/hevm/HederaEvmTransactionResult.java | 4 + .../hevm/HederaEvmTransactionResultTest.java | 8 ++ .../bdd/spec/transactions/HapiTxnOp.java | 3 +- .../utilops/streams/RecordAssertions.java | 11 ++- .../contract/hapi/ContractCallSuite.java | 2 +- .../opcodes/PrngSeedOperationSuite.java | 11 ++- .../opcodes/PushZeroOperationSuite.java | 4 +- .../ERC20ContractInteractions.java | 12 +-- .../suites/crypto/CryptoCornerCasesSuite.java | 12 +-- .../crypto/CryptoGetInfoRegression.java | 9 +- .../bdd/suites/crypto/NftTransferSuite.java | 4 +- .../suites/crypto/TxnReceiptRegression.java | 20 +++-- .../suites/crypto/TxnRecordRegression.java | 46 +++------- .../bdd/suites/ethereum/EthereumSuite.java | 4 +- .../ethereum/HelloWorldEthereumSuite.java | 41 +-------- .../bdd/suites/file/FileCreateSuite.java | 2 +- .../bdd/suites/file/FileUpdateSuite.java | 18 +++- .../file/ProtectedFilesUpdateSuite.java | 4 + .../bdd/suites/issues/Issue305Spec.java | 83 ++++++++++++++----- .../suites/leaky/LeakyContractTestsSuite.java | 41 ++++++++- .../misc/CannotDeleteSystemEntitiesSuite.java | 5 ++ .../src/main/resource/bootstrap.properties | 2 +- 28 files changed, 225 insertions(+), 137 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/ThrottleMultiplier.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/ThrottleMultiplier.java index 3de420ac5004..9040a28f5c63 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/ThrottleMultiplier.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/ThrottleMultiplier.java @@ -100,7 +100,7 @@ public long currentMultiplier() { /** * Rebuilds the object's internal state based on its dependencies expectations. - * Must be called every time when the suppliers {@link throttleSource} or {@link multiplierSupplier} are updated. + * Must be called every time when the suppliers {@code throttleSource} or {@code multiplierSupplier} are updated. */ public void resetExpectations() { activeThrottles = throttleSource.get(); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java index 5a4f2a443a87..0c32a1a6cebe 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java @@ -807,9 +807,7 @@ private void logResolvedDefinitions(final int capacitySplit) { sb.append(" ") .append(function) .append(": ") - .append(manager.currentUsage()) // use current usage instead of the package private - // asReadableRequirements(), otherwise we need to make it public as - // well + .append(manager.asReadableRequirements()) .append("\n"); }); log.info("{}", () -> sb.toString().trim()); diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java index 7745b9c15144..71c5ce929a58 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileUpdateHandler.java @@ -189,11 +189,12 @@ private void handleUpdateUpgradeFile(FileUpdateTransactionBody fileUpdate, Handl fileStore.resetFileContents(fileId); fileStore.addUpgradeContent(fileId, fileUpdate.contents()); } + // Note that upgrade file memos are generated programmatically + // as the SHA-384 hash of their contents final var file = new File.Builder() .fileId(fileId) .deleted(false) .expirationSecond(fileUpdate.expirationTimeOrElse(EXPIRE_NEVER).seconds()) - .memo(fileUpdate.memo()) .build(); fileStore.add(file); } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/throttling/ThrottleReqsManager.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/throttling/ThrottleReqsManager.java index 2c80194d6327..d38401c68714 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/throttling/ThrottleReqsManager.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/throttling/ThrottleReqsManager.java @@ -92,7 +92,7 @@ public List managedThrottles() { return allReqs.stream().map(Pair::getLeft).toList(); } - String asReadableRequirements() { + public String asReadableRequirements() { return "min{" + allReqs.stream().map(this::readable).collect(Collectors.joining(", ")) + "}"; } diff --git a/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties b/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties index a3c451d6f5bf..713d4f425383 100644 --- a/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties +++ b/hedera-node/hedera-mono-service/src/test/resources/bootstrap.properties @@ -67,7 +67,7 @@ contracts.allowSystemUseOfHapiSigs=TokenAssociateToAccount,TokenDissociateFromAc contracts.maxNumWithHapiSigsAccess=0 contracts.withSpecialHapiSigsAccess= contracts.allowCreate2=true -contracts.chainId=295 +contracts.chainId=298 contracts.defaultLifetime=7890000 contracts.enforceCreationThrottle=false contracts.evm.version=v0.38 diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index a981cbd8bc5c..98d66e1b8249 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.exec; +import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; @@ -241,7 +242,7 @@ private InvolvedParties computeInvolvedParties( validateTrueOrAbort(isEvmAddress(alias), INVALID_CONTRACT_ID, senderId); // do not attempt to lazy create account with alias that is a long zero address - validateTrueOrAbort(!isLongZeroAddress(alias.toByteArray()), INVALID_CONTRACT_ID, senderId); + validateTrueOrAbort(!isLongZeroAddress(alias.toByteArray()), CONTRACT_EXECUTION_EXCEPTION, senderId); parties = new InvolvedParties(sender, relayer, pbjToBesuAddress(alias)); updater.setupTopLevelLazyCreate(parties.receiverAddress); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java index c3007d2faef8..fee6808f61ad 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.hevm; +import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; @@ -81,6 +82,7 @@ public record HederaEvmTransactionResult( private static final Bytes INSUFFICIENT_TX_FEE_REASON = Bytes.wrap(INSUFFICIENT_TX_FEE.name()); private static final Bytes INSUFFICIENT_PAYER_BALANCE_REASON = Bytes.wrap(INSUFFICIENT_PAYER_BALANCE.name()); private static final Bytes WRONG_NONCE_REASON = Bytes.wrap(WRONG_NONCE.name()); + private static final Bytes CONTRACT_EXECUTION_EXCEPTION_REASON = Bytes.wrap(CONTRACT_EXECUTION_EXCEPTION.name()); /** * Converts this result to a {@link ContractFunctionResult} for a transaction based on the given @@ -153,6 +155,8 @@ public ResponseCodeEnum finalStatus() { return WRONG_NONCE; } else if (revertReason.equals(INSUFFICIENT_PAYER_BALANCE_REASON)) { return INSUFFICIENT_PAYER_BALANCE; + } else if (revertReason.equals(CONTRACT_EXECUTION_EXCEPTION_REASON)) { + return CONTRACT_EXECUTION_EXCEPTION; } else { return CONTRACT_REVERT_EXECUTED; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java index 242afd0cd5b5..f6effcc274ab 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.test.hevm; +import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hedera.hapi.node.base.ResponseCodeEnum.OBTAINER_SAME_CONTRACT_ID; @@ -126,6 +127,13 @@ void finalStatusFromWrongNonceAbortTranslated() { assertEquals(WRONG_NONCE, subject.finalStatus()); } + @Test + void finalStatusFromExecutionExceptionAbortTranslated() { + final var subject = + HederaEvmTransactionResult.fromAborted(SENDER_ID, wellKnownHapiCall(), CONTRACT_EXECUTION_EXCEPTION); + assertEquals(CONTRACT_EXECUTION_EXCEPTION, subject.finalStatus()); + } + @Test void finalStatusFromIpbAbortTranslated() { final var subject = diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java index 33a67ead3d77..e49358295b4a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/transactions/HapiTxnOp.java @@ -74,6 +74,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -742,7 +743,7 @@ public T usePresetTimestamp() { return self(); } - public T scrambleTxnBody(Function func) { + public T withTxnTransform(UnaryOperator func) { fiddler = Optional.of(func); return self(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java index a9b78754ece2..5afe524b4d56 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/RecordAssertions.java @@ -37,7 +37,10 @@ public class RecordAssertions extends UtilOp { private static final Logger LOG = LogManager.getLogger(RecordAssertions.class); - private static final Duration DEFAULT_INTER_CHECK_DELAY = Duration.ofMillis(2_000L); + // Wait just a bit longer than the 2-second block period to be certain we've ended the period + private static final Duration END_OF_BLOCK_PERIOD_SLEEP_PERIOD = Duration.ofMillis(2_200L); + // Wait just over a second to give the record stream file a chance to close + private static final Duration BLOCK_CREATION_SLEEP_PERIOD = Duration.ofMillis(1_100L); @Nullable private final String loc; @@ -72,7 +75,7 @@ protected boolean submitOp(final HapiSpec spec) throws Throwable { } public static void triggerAndCloseAtLeastOneFile(final HapiSpec spec) throws InterruptedException { - Thread.sleep(DEFAULT_INTER_CHECK_DELAY.toMillis()); + Thread.sleep(END_OF_BLOCK_PERIOD_SLEEP_PERIOD.toMillis()); // Should trigger a new record to be written if we have crossed a 2-second boundary final var triggerOp = cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L)) .deferStatusResolution() @@ -84,8 +87,8 @@ public static void triggerAndCloseAtLeastOneFile(final HapiSpec spec) throws Int public static void triggerAndCloseAtLeastOneFileIfNotInterrupted(final HapiSpec spec) { try { RecordAssertions.triggerAndCloseAtLeastOneFile(spec); - LOG.info("Sleeping for a second to give the record stream a (very generous) chance to close"); - Thread.sleep(1000L); + LOG.info("Sleeping a bit to give the record stream a chance to close"); + Thread.sleep(BLOCK_CREATION_SLEEP_PERIOD.toMillis()); } catch (final InterruptedException ignore) { Thread.currentThread().interrupt(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java index f364ac684815..5293bd49428b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java @@ -1410,7 +1410,7 @@ final HapiSpec multipleSelfDestructsAreSafe() { final var contract = "Fuse"; return defaultHapiSpec("MultipleSelfDestructsAreSafe", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(contract), contractCreate(contract).gas(600_000)) - .when(contractCall(contract, "light").via("lightTxn").scrambleTxnBody(tx -> tx)) + .when(contractCall(contract, "light").via("lightTxn").withTxnTransform(tx -> tx)) .then(getTxnRecord("lightTxn").logged()); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java index b43a0dc2906b..ad5e02d02acc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PrngSeedOperationSuite.java @@ -17,7 +17,7 @@ package com.hedera.services.bdd.suites.contract.opcodes; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isRandomResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; @@ -93,7 +93,8 @@ final HapiSpec multipleCallsHaveIndependentResults() { final var gasToOffer = 400_000; final var numCalls = 5; final List prngSeeds = new ArrayList<>(); - return defaultHapiSpec("MultipleCallsHaveIndependentResults") + return propertyPreservingHapiSpec("MultipleCallsHaveIndependentResults") + .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) .given( uploadInitCode(prng), contractCreate(prng), @@ -140,7 +141,8 @@ final HapiSpec multipleCallsHaveIndependentResults() { final HapiSpec prngPrecompileHappyPathWorks() { final var prng = THE_PRNG_CONTRACT; final var randomBits = "randomBits"; - return defaultHapiSpec("prngPrecompileHappyPathWorks") + return propertyPreservingHapiSpec("prngPrecompileHappyPathWorks") + .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) .given( overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_34), @@ -164,7 +166,8 @@ GET_SEED, prng, isRandomResult((new Object[] {new byte[32]}))))) final HapiSpec prngPrecompileDisabledInV030() { final var prng = THE_PRNG_CONTRACT; final var randomBits = "randomBits"; - return defaultHapiSpec("prngPrecompileDisabledInV_0_30") + return propertyPreservingHapiSpec("prngPrecompileDisabledInV_0_30") + .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) .given( overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_30), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java index 5eba21840990..2fff7cfd8a08 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/PushZeroOperationSuite.java @@ -18,6 +18,7 @@ import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; @@ -104,7 +105,8 @@ final HapiSpec pushZeroHappyPathWorks() { final HapiSpec pushZeroDisabledInV034() { final var pushZeroContract = CONTRACT; final var pushResult = "pushResult"; - return defaultHapiSpec("pushZeroDisabledInV034") + return propertyPreservingHapiSpec("pushZeroDisabledInV034") + .preserving(CONTRACTS_DYNAMIC_EVM_VERSION, CONTRACTS_EVM_VERSION) .given( overriding(CONTRACTS_DYNAMIC_EVM_VERSION, TRUE_VALUE), overriding(CONTRACTS_EVM_VERSION, EVM_VERSION_0_34), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java index 56a67713c422..f24f9e19ed88 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/openzeppelin/ERC20ContractInteractions.java @@ -99,7 +99,7 @@ final HapiSpec callsERC20ContractInteractions() { .payingWith(DEFAULT_CONTRACT_SENDER) .hasKnownStatus(SUCCESS) .via(CREATE_TX) - .scrambleTxnBody(tx -> { + .withTxnTransform(tx -> { System.out.println(TX_STR_PREFIX + Bytes.wrap(tx.toByteArray())); return tx; }), @@ -131,7 +131,7 @@ final HapiSpec callsERC20ContractInteractions() { final var transfer = contractCall(CONTRACT, TRANSFER, transferParams) .payingWith(DEFAULT_CONTRACT_SENDER) .via(TRANSFER_TX) - .scrambleTxnBody(tx -> { + .withTxnTransform(tx -> { System.out.println(TX_STR_PREFIX + Bytes.wrap(tx.toByteArray())); return tx; }); @@ -141,7 +141,7 @@ final HapiSpec callsERC20ContractInteractions() { .payingWith(DEFAULT_CONTRACT_SENDER) .hasKnownStatus(CONTRACT_REVERT_EXECUTED) .via(NOT_ENOUGH_BALANCE_TRANSFER_TX) - .scrambleTxnBody(tx -> { + .withTxnTransform(tx -> { System.out.println(TX_STR_PREFIX + Bytes.wrap(tx.toByteArray())); return tx; }); @@ -149,7 +149,7 @@ final HapiSpec callsERC20ContractInteractions() { final var approve = contractCall(CONTRACT, "approve", approveParams) .payingWith(DEFAULT_CONTRACT_SENDER) .via(APPROVE_TX) - .scrambleTxnBody(tx -> { + .withTxnTransform(tx -> { System.out.println(TX_STR_PREFIX + Bytes.wrap(tx.toByteArray())); return tx; }); @@ -158,7 +158,7 @@ final HapiSpec callsERC20ContractInteractions() { .payingWith(DEFAULT_CONTRACT_RECEIVER) .signingWith(SECP_256K1_RECEIVER_SOURCE_KEY) .via(TRANSFER_FROM_TX) - .scrambleTxnBody(tx -> { + .withTxnTransform(tx -> { System.out.println(TX_STR_PREFIX + Bytes.wrap(tx.toByteArray())); return tx; }); @@ -168,7 +168,7 @@ final HapiSpec callsERC20ContractInteractions() { .payingWith(DEFAULT_CONTRACT_RECEIVER) .hasKnownStatus(CONTRACT_REVERT_EXECUTED) .via(TRANSFER_MORE_THAN_APPROVED_FROM_TX) - .scrambleTxnBody(tx -> { + .withTxnTransform(tx -> { System.out.println(TX_STR_PREFIX + Bytes.wrap(tx.toByteArray())); return tx; }); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java index 1e742cdf8486..3394b9c4d3c4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCornerCasesSuite.java @@ -79,7 +79,7 @@ final HapiSpec invalidTransactionBody() { .balance(10000L) .withProtoStructure(HapiSpecSetup.TxnProtoStructure.OLD) // Ensure legacy construction so // removeTransactionBody() works - .scrambleTxnBody(CryptoCornerCasesSuite::removeTransactionBody) + .withTxnTransform(CryptoCornerCasesSuite::removeTransactionBody) .hasPrecheckFrom(INVALID_TRANSACTION_BODY, INVALID_TRANSACTION)); } @@ -99,7 +99,7 @@ final HapiSpec invalidNodeAccount() { .when() .then(cryptoCreate(NEW_PAYEE) .balance(10000L) - .scrambleTxnBody(CryptoCornerCasesSuite::replaceTxnNodeAccount) + .withTxnTransform(CryptoCornerCasesSuite::replaceTxnNodeAccount) .hasPrecheckFrom(INVALID_NODE_ACCOUNT, INVALID_TRANSACTION)); } @@ -114,7 +114,7 @@ final HapiSpec invalidTransactionDuration() { .when() .then(cryptoCreate(NEW_PAYEE) .balance(10000L) - .scrambleTxnBody(CryptoCornerCasesSuite::replaceTxnDuration) + .withTxnTransform(CryptoCornerCasesSuite::replaceTxnDuration) .hasPrecheckFrom(INVALID_TRANSACTION_DURATION, INVALID_TRANSACTION)); } @@ -130,7 +130,7 @@ final HapiSpec invalidTransactionMemoTooLong() { .when() .then(cryptoCreate(NEW_PAYEE) .balance(10000L) - .scrambleTxnBody(CryptoCornerCasesSuite::replaceTxnMemo) + .withTxnTransform(CryptoCornerCasesSuite::replaceTxnMemo) .hasPrecheckFrom(MEMO_TOO_LONG, INVALID_TRANSACTION)); } @@ -150,7 +150,7 @@ final HapiSpec invalidTransactionPayerAccountNotFound() { .when() .then(cryptoCreate(NEW_PAYEE) .balance(10000L) - .scrambleTxnBody(CryptoCornerCasesSuite::replaceTxnPayerAccount) + .withTxnTransform(CryptoCornerCasesSuite::replaceTxnPayerAccount) .hasPrecheckFrom(PAYER_ACCOUNT_NOT_FOUND, INVALID_TRANSACTION)); } @@ -166,7 +166,7 @@ final HapiSpec invalidTransactionStartTime() { .when() .then(cryptoCreate(NEW_PAYEE) .balance(10000L) - .scrambleTxnBody(CryptoCornerCasesSuite::replaceTxnStartTtime) + .withTxnTransform(CryptoCornerCasesSuite::replaceTxnStartTtime) .hasPrecheckFrom(INVALID_TRANSACTION_START, INVALID_TRANSACTION)); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java index 28c78dcb15f9..4c55d22c6a6e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoGetInfoRegression.java @@ -265,12 +265,15 @@ final HapiSpec failsForUnfundablePayment() { .hasAnswerOnlyPrecheck(INSUFFICIENT_PAYER_BALANCE)); } - // this test failed on mono code too, don't need to enable it + @HapiTest final HapiSpec failsForInsufficientPayment() { return defaultHapiSpec("FailsForInsufficientPayment") - .given() + .given(cryptoCreate(CIVILIAN_PAYER)) .when() - .then(getAccountInfo(GENESIS).nodePayment(1L).hasAnswerOnlyPrecheck(INSUFFICIENT_TX_FEE)); + .then(getAccountInfo(GENESIS) + .payingWith(CIVILIAN_PAYER) + .nodePayment(1L) + .hasAnswerOnlyPrecheck(INSUFFICIENT_TX_FEE)); } @HapiTest // this test needs to be updated for both mono and module code. diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java index bbe094220a9d..b9494911b16e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/NftTransferSuite.java @@ -26,6 +26,7 @@ import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; @@ -173,10 +174,11 @@ private static HapiSpecOperation transferRound(int roundNum) { .between( userAccountName(2 * accountId + fromOffset), userAccountName(2 * accountId + toOffset))) + .noLogging() .payingWith(GENESIS))) .flatMap(List::stream) .toArray(HapiSpecOperation[]::new); - return inParallel(ops); + return blockingOrder(logIt("----- Beginning round #" + roundNum + " -----"), inParallel(ops)); } private static HapiSpecOperation setupNftTest() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java index d9d3b7c09090..d98b2847380e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java @@ -20,6 +20,9 @@ import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; @@ -60,7 +63,7 @@ public List getSpecsInSuite() { returnsInvalidForUnspecifiedTxnId(), returnsNotSupportedForMissingOp(), receiptAvailableWithinCacheTtl(), - // receiptUnavailableAfterCacheTtl(), + receiptUnavailableAfterCacheTtl(), receiptUnavailableIfRejectedInPrecheck(), receiptNotFoundOnUnknownTransactionID(), receiptUnknownBeforeConsensus(), @@ -83,12 +86,19 @@ final HapiSpec returnsNotSupportedForMissingOp() { .then(getReceipt("success").forgetOp().hasAnswerOnlyPrecheck(NOT_SUPPORTED)); } - // FUTURE: revisit this test, which isn't passing in modular or mono code final HapiSpec receiptUnavailableAfterCacheTtl() { return defaultHapiSpec("ReceiptUnavailableAfterCacheTtl") - .given(cryptoCreate("misc").via("success").balance(1_000L)) - .when(sleepFor(200_000L)) - .then(getReceipt("success").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND)); + .given() + .when() + .then( + // This extra three minutes isn't worth adding to mono-service checks, but + // especially as it fails now against mod-service, is worthwhile as HapiTest + ifHapiTest( + cryptoCreate("misc").via("success").balance(1_000L), + sleepFor(181_000L), + // Run a transaction to give receipt expiration a chance to occur + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + getReceipt("success").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND))); } @HapiTest diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java index 7462bc19a4e7..00e41b7e6b25 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnRecordRegression.java @@ -18,21 +18,15 @@ import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECEIPT_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; @@ -69,37 +63,21 @@ public List getSpecsInSuite() { recordNotFoundIfNotInPayerState(), recordUnavailableIfRejectedInPrecheck(), recordUnavailableBeforeConsensus(), - recordAvailableInPayerState(), - deletedAccountRecordsUnavailableAfterTtl(), + recordsStillQueryableWithDeletedPayerId(), }); } // FUTURE: revisit this test, which isn't passing in modular or mono code (even with a 3 second TTL) - final HapiSpec recordAvailableInPayerState() { - return defaultHapiSpec("RecordAvailableInPayerState") - .given( - cryptoCreate("stingyPayer").sendThreshold(1L), - cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .payingWith("stingyPayer") - .via("recordTxn")) - .when(sleepFor(5_000L)) - .then( - getReceipt("recordTxn").hasAnswerOnlyPrecheck(RECEIPT_NOT_FOUND), - getTxnRecord("recordTxn").hasPriority(recordWith().status(SUCCESS))); - } - - // FUTURE: revisit this test, which isn't passing in modular or mono code (even with a 3 second TTL) - final HapiSpec deletedAccountRecordsUnavailableAfterTtl() { + @HapiTest + final HapiSpec recordsStillQueryableWithDeletedPayerId() { return defaultHapiSpec("DeletedAccountRecordsUnavailableAfterTtl") .given( - cryptoCreate("lowThreshPayer").sendThreshold(1L), + cryptoCreate("toBeDeletedPayer"), cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)) - .payingWith("lowThreshPayer") - .via("recordTxn"), - cryptoDelete("lowThreshPayer"), - getTxnRecord("recordTxn")) - .when(sleepFor(5_000L)) - .then(getTxnRecord("recordTxn").hasCostAnswerPrecheck(ACCOUNT_DELETED)); + .payingWith("toBeDeletedPayer") + .via("recordTxn")) + .when(cryptoDelete("toBeDeletedPayer")) + .then(getTxnRecord("recordTxn")); } @HapiTest @@ -130,14 +108,16 @@ final HapiSpec recordUnavailableBeforeConsensus() { getTxnRecord("success").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); } - // FUTURE: revisit this test, which isn't passing in modular or mono code (even with a 3 second TTL) + @HapiTest final HapiSpec recordUnavailableIfRejectedInPrecheck() { return defaultHapiSpec("RecordUnavailableIfRejectedInPrecheck") - .given(usableTxnIdNamed("failingTxn"), cryptoCreate("misc").balance(1_000L)) + .given( + cryptoCreate("misc").balance(1000L), + usableTxnIdNamed("failingTxn").payerId("misc")) .when(cryptoCreate("nope") .payingWith("misc") .hasPrecheck(INSUFFICIENT_PAYER_BALANCE) .txnId("failingTxn")) - .then(getTxnRecord("failingTxn").hasCostAnswerPrecheck(RECORD_NOT_FOUND)); + .then(getTxnRecord("failingTxn").hasAnswerOnlyPrecheck(RECORD_NOT_FOUND)); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java index 2a0533168f3f..1ed82ca19de7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/EthereumSuite.java @@ -89,7 +89,7 @@ import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.TxnUtils; -import com.hedera.services.bdd.suites.BddTestNameDoesNotMatchMethodName; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -359,7 +359,7 @@ HapiSpec matrixedPayerRelayerTest12() { return feePaymentMatrix().get(11); } - @BddTestNameDoesNotMatchMethodName + @BddMethodIsNotATest HapiSpec matrixedPayerRelayerTest( final boolean success, final long senderGasPrice, final long relayerOffered, final long senderCharged) { return defaultHapiSpec("feePaymentMatrix " diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java index 52a2090cf47b..902ac421254a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java @@ -30,7 +30,6 @@ import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.keys.KeyFactory.KeyType.THRESHOLD; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; @@ -50,7 +49,6 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.CONSTRUCTOR; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; @@ -108,7 +106,6 @@ public boolean canRunConcurrent() { List ethereumCalls() { return List.of( - relayerFeeAsExpectedIfSenderCoversGas(), depositSuccess(), badRelayClient(), topLevelBurnToZeroAddressReverts(), @@ -123,6 +120,7 @@ List ethereumCreates() { return List.of(smallContractCreate(), contractCreateWithConstructorArgs(), bigContractCreate()); } + @HapiTest HapiSpec badRelayClient() { final var adminKey = "adminKey"; final var exploitToken = "exploitToken"; @@ -191,39 +189,6 @@ HapiSpec badRelayClient() { getAliasedAccountInfo(maliciousEOA).has(accountWith().nonce(1L))); } - @HapiTest - HapiSpec relayerFeeAsExpectedIfSenderCoversGas() { - final var canonicalTxn = "canonical"; - - return defaultHapiSpec("relayerFeeAsExpectedIfSenderCoversGas") - .given( - uploadDefaultFeeSchedules(GENESIS), - newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), - cryptoCreate(RELAYER).balance(ONE_HUNDRED_HBARS), - cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) - .via("autoAccount"), - getTxnRecord("autoAccount").andAllChildRecords(), - uploadInitCode(PAY_RECEIVABLE_CONTRACT), - contractCreate(PAY_RECEIVABLE_CONTRACT).adminKey(THRESHOLD)) - .when( - // The cost to the relayer to transmit a simple call with sufficient gas - // allowance is ≈ $0.0001 - ethereumCall(PAY_RECEIVABLE_CONTRACT, DEPOSIT, BigInteger.valueOf(depositAmount)) - .type(EthTxData.EthTransactionType.EIP1559) - .signingWith(SECP_256K1_SOURCE_KEY) - .payingWith(RELAYER) - .via(canonicalTxn) - .nonce(0) - .gasPrice(100L) - .maxFeePerGas(100L) - .maxPriorityGas(2_000_000L) - .gasLimit(1_000_000L) - .sending(depositAmount)) - .then(getAccountInfo(RELAYER) - .has(accountWith().expectedBalanceWithChargedUsd(ONE_HUNDRED_HBARS, 0.0001, 0.5)) - .logged()); - } - @HapiTest HapiSpec depositSuccess() { return defaultHapiSpec("depositSuccess") @@ -500,6 +465,7 @@ HapiSpec contractCreateWithConstructorArgs() { private static final String SEND_TO = "sendTo"; + @HapiTest HapiSpec topLevelBurnToZeroAddressReverts() { final var ethBurnAddress = new byte[20]; return defaultHapiSpec("topLevelBurnToZeroAddressReverts") @@ -534,7 +500,7 @@ HapiSpec topLevelLazyCreateOfMirrorAddressReverts() { .maxFeePerGas(50L) .maxPriorityGas(2L) .gasLimit(1_000_000L) - .hasKnownStatus(INVALID_CONTRACT_ID)); + .hasKnownStatusFrom(INVALID_CONTRACT_ID, CONTRACT_EXECUTION_EXCEPTION)); } @HapiTest @@ -566,6 +532,7 @@ HapiSpec topLevelSendToReceiverSigRequiredAccountReverts() { .then(getAccountBalance(receiverSigAccount).hasTinyBars(changeFromSnapshot(preCallBalance, 0L))); } + @HapiTest HapiSpec internalBurnToZeroAddressReverts() { return defaultHapiSpec("internalBurnToZeroAddressReverts") .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java index 02019f4c2ce8..1881029074ac 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileCreateSuite.java @@ -154,7 +154,7 @@ final HapiSpec createFailsWithPayerAccountNotFound() { .withProtoStructure(HapiSpecSetup.TxnProtoStructure.OLD) .waclShape(shape) .sigControl(forKey("test", validSig)) - .scrambleTxnBody(FileCreateSuite::replaceTxnNodeAccount) + .withTxnTransform(FileCreateSuite::replaceTxnNodeAccount) .hasPrecheckFrom(INVALID_NODE_ACCOUNT)); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java index d8a4033cc0d1..986822695511 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileUpdateSuite.java @@ -96,9 +96,13 @@ import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hedera.services.bdd.spec.transactions.TxnVerbs; import com.hedera.services.bdd.spec.utilops.UtilVerbs; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.token.TokenAssociationSpecs; +import com.swirlds.common.utility.CommonUtils; import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; @@ -239,14 +243,22 @@ public HapiSpec notTooManyFeeScheduleCanBeCreated() { .then(overriding(MAX_CUSTOM_FEES_PROP, DEFAULT_MAX_CUSTOM_FEES)); } + @HapiTest final HapiSpec optimisticSpecialFileUpdate() { final var appendsPerBurst = 128; final var specialFile = "0.0.159"; - final var specialFileContents = ByteString.copyFrom(randomUtf8Bytes(64 * BYTES_4K)); + final var contents = randomUtf8Bytes(64 * BYTES_4K); + final var specialFileContents = ByteString.copyFrom(contents); + final byte[] expectedHash; + try { + expectedHash = MessageDigest.getInstance("SHA-384").digest(contents); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } return defaultHapiSpec("OptimisticSpecialFileUpdate") .given() .when(updateSpecialFile(GENESIS, specialFile, specialFileContents, BYTES_4K, appendsPerBurst)) - .then(getFileContents(specialFile).hasContents(ignore -> specialFileContents.toByteArray())); + .then(getFileInfo(specialFile).hasMemo(CommonUtils.hex(expectedHash))); } @HapiTest @@ -576,7 +588,7 @@ final HapiSpec entitiesNotCreatableAfterUsageLimitsReached() { .then(); } - // It is not implemented yet in contracts + @BddMethodIsNotATest final HapiSpec rentItemizedAsExpectedWithOverridePriceTiers() { final var slotUser = "SlotUser"; final var creation = "creation"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java index 50a1665130e4..9b0cb0c51a76 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java @@ -36,6 +36,7 @@ import com.hedera.services.bdd.spec.queries.file.HapiGetFileContents; import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; import com.hedera.services.bdd.spec.utilops.UtilVerbs; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.utils.sysfiles.AddressBookPojo; import com.hederahashgraph.api.proto.java.NodeAddress; @@ -102,6 +103,7 @@ private List negativeTests() { unauthorizedAccountCannotUpdateExchangeRates()); } + @BddMethodIsNotATest final HapiSpec specialAccountCanUpdateSpecialPropertyFile( final String specialAccount, final String specialFile, final String property, final String expected) { return specialAccountCanUpdateSpecialPropertyFile(specialAccount, specialFile, property, expected, true); @@ -140,6 +142,7 @@ private HapiSpecOperation propertyFileValidationOp( }); } + @BddMethodIsNotATest final HapiSpec specialAccountCanUpdateSpecialFile( final String specialAccount, final String specialFile, final String target, final String replacement) { return specialAccountCanUpdateSpecialFile(specialAccount, specialFile, target, replacement, true); @@ -201,6 +204,7 @@ private HapiSpecOperation[] validateAndCleanUpOps( return ArrayUtils.addAll(accountBalanceUnchanged, opsArray); } + @BddMethodIsNotATest final HapiSpec unauthorizedAccountCannotUpdateSpecialFile(final String specialFile, final String newContents) { return defaultHapiSpec("UnauthorizedAccountCannotUpdate" + specialFile) .given(cryptoCreate("unauthorizedAccount")) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java index c7c9d910df38..f13e645d04f9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue305Spec.java @@ -28,18 +28,28 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingThree; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; +import com.google.protobuf.InvalidProtocolBufferException; +import com.hedera.node.app.hapi.utils.CommonUtils; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyFactory; import com.hedera.services.bdd.suites.HapiSuite; +import com.hederahashgraph.api.proto.java.Transaction; +import com.hederahashgraph.api.proto.java.TransactionID; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; @@ -92,30 +102,38 @@ final HapiSpec createDeleteInSameRoundWorks() { final HapiSpec congestionMultipliersRefreshOnPropertyUpdate() { final var civilian = "civilian"; final var preCongestionTxn = "preCongestionTxn"; - final var postCongestionTxn = "postCongestionTxn"; final var multipurposeContract = "Multipurpose"; final var normalPrice = new AtomicLong(); final var multipliedPrice = new AtomicLong(); + final List submittedTxnIds = new ArrayList<>(); return propertyPreservingHapiSpec("CongestionMultipliersRefreshOnPropertyUpdate") - .preserving("fees.percentCongestionMultipliers", "fees.minCongestionPeriod", "contracts.maxGasPerSec") + .preserving( + "fees.percentCongestionMultipliers", + "fees.minCongestionPeriod", + "contracts.maxGasPerSec", + "contracts.throttle.throttleByGas") .given( cryptoCreate(civilian).balance(10 * ONE_HUNDRED_HBARS), uploadInitCode(multipurposeContract), contractCreate(multipurposeContract).payingWith(GENESIS).logging(), contractCall(multipurposeContract) .payingWith(civilian) + .gas(200_000) .fee(10 * ONE_HBAR) .sending(ONE_HBAR) .via(preCongestionTxn), getTxnRecord(preCongestionTxn).providingFeeTo(normalPrice::set), - overridingThree( + overridingAllOf(Map.of( "contracts.maxGasPerSec", "3_000_000", "fees.percentCongestionMultipliers", "1,5x", - "fees.minCongestionPeriod", "1")) + "fees.minCongestionPeriod", "1", + "contracts.throttle.throttleByGas", "true"))) .when(withOpContext((spec, opLog) -> { - for (int i = 0; i < 25; i++) { - TimeUnit.MILLISECONDS.sleep(50); + // We submit 2.5 seconds of transactions with a 1 second congestion period, so + // we should see a 5x multiplier in effect at some point here + for (int i = 0; i < 100; i++) { + TimeUnit.MILLISECONDS.sleep(25); allRunFor( spec, contractCall(multipurposeContract) @@ -123,21 +141,48 @@ final HapiSpec congestionMultipliersRefreshOnPropertyUpdate() { .gas(200_000) .fee(10 * ONE_HBAR) .sending(ONE_HBAR) + .hasPrecheckFrom(BUSY, OK) + .withTxnTransform(txn -> { + submittedTxnIds.add(idOf(txn)); + return txn; + }) + .noLogging() .deferStatusResolution()); } })) - .then( - contractCall(multipurposeContract) - .payingWith(civilian) - .fee(10 * ONE_HBAR) - .sending(ONE_HBAR) - .via(postCongestionTxn), - getTxnRecord(postCongestionTxn).providingFeeTo(multipliedPrice::set), - withOpContext((spec, opLog) -> Assertions.assertEquals( - 5.0, - (1.0 * multipliedPrice.get()) / normalPrice.get(), - 0.1, - "~5x multiplier should be in affect!"))); + .then(withOpContext((spec, opLog) -> { + final var congestionInEffect = new AtomicBoolean(); + submittedTxnIds.reversed().forEach(id -> { + if (congestionInEffect.get()) { + return; + } + final var lookup = getTxnRecord(id) + .payingWith(GENESIS) + .assertingNothing() + .nodePayment(1L) + .hasAnswerOnlyPrecheckFrom(OK, RECORD_NOT_FOUND) + .providingFeeTo(multipliedPrice::set); + allRunFor(spec, lookup); + try { + Assertions.assertEquals(5.0, (1.0 * multipliedPrice.get()) / normalPrice.get(), 0.1); + // As soon as any transaction is observed to have the 5x multiplier, + // we can stop looking + congestionInEffect.set(true); + } catch (Throwable ignore) { + } + }); + if (!congestionInEffect.get()) { + Assertions.fail("~5x multiplier was never observed"); + } + })); + } + + private TransactionID idOf(@NonNull final Transaction txn) { + try { + return CommonUtils.extractTransactionBody(txn).getTransactionID(); + } catch (InvalidProtocolBufferException e) { + throw new IllegalArgumentException(e); + } } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 743afbebd1fe..198f07ee7065 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -25,6 +25,7 @@ import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; import static com.hedera.services.bdd.spec.HapiSpec.propertyPreservingHapiSpec; import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; +import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.AssertUtils.inOrder; import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith; @@ -57,6 +58,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumContractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; @@ -66,6 +68,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadSingleInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddressArray; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromAccountToAlias; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeTests.fixedHbarFeeInSchedule; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeTests.fixedHtsFeeInSchedule; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeTests.fractionalFeeInSchedule; @@ -94,6 +97,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.tokenTransferList; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.tokenTransferLists; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; @@ -246,6 +250,7 @@ public class LeakyContractTestsSuite extends HapiSuite { public static final String CREATE_TX = "createTX"; public static final String CREATE_TX_REC = "createTXRec"; public static final String FALSE = "false"; + private static final long depositAmount = 20_000L; public static final int GAS_TO_OFFER = 1_000_000; private static final Logger log = LogManager.getLogger(LeakyContractTestsSuite.class); private static final String PAYER = "payer"; @@ -885,7 +890,8 @@ final HapiSpec getErc20TokenNameExceedingLimits() { getAccountDetails(ACCOUNT) .has(accountDetailsWith() .balanceLessThan( - INIT_ACCOUNT_BALANCE - REDUCED_NETWORK_FEE - REDUCED_NODE_FEE))); + INIT_ACCOUNT_BALANCE - REDUCED_NETWORK_FEE - REDUCED_NODE_FEE)), + uploadDefaultFeeSchedules(GENESIS)); } @HapiTest @@ -2409,6 +2415,39 @@ final HapiSpec shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled( .then(); } + @HapiTest + HapiSpec relayerFeeAsExpectedIfSenderCoversGas() { + final var canonicalTxn = "canonical"; + + return defaultHapiSpec("relayerFeeAsExpectedIfSenderCoversGas") + .given( + uploadDefaultFeeSchedules(GENESIS), + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + cryptoCreate(RELAYER).balance(ONE_HUNDRED_HBARS), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) + .via("autoAccount"), + getTxnRecord("autoAccount").andAllChildRecords(), + uploadInitCode(PAY_RECEIVABLE_CONTRACT), + contractCreate(PAY_RECEIVABLE_CONTRACT).adminKey(THRESHOLD)) + .when( + // The cost to the relayer to transmit a simple call with sufficient gas + // allowance is ≈ $0.0001 + ethereumCall(PAY_RECEIVABLE_CONTRACT, DEPOSIT, BigInteger.valueOf(depositAmount)) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .via(canonicalTxn) + .nonce(0) + .gasPrice(100L) + .maxFeePerGas(100L) + .maxPriorityGas(2_000_000L) + .gasLimit(1_000_000L) + .sending(depositAmount)) + .then(getAccountInfo(RELAYER) + .has(accountWith().expectedBalanceWithChargedUsd(ONE_HUNDRED_HBARS, 0.0001, 0.5)) + .logged()); + } + @Override protected Logger getResultsLogger() { return log; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java index b3b881d47cfc..637e93578efc 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java @@ -32,6 +32,7 @@ import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import java.util.Arrays; import java.util.List; @@ -163,6 +164,7 @@ final HapiSpec systemDeleteAdminCannotSystemFileDeleteFileIds() { return systemDeleteCannotDeleteSystemFiles(sysFileIds, SYSTEM_DELETE_ADMIN); } + @BddMethodIsNotATest final HapiSpec systemUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount, String sysUser) { return defaultHapiSpec("systemUserCannotDeleteSystemAccounts") .given( @@ -193,6 +195,7 @@ final HapiSpec normalUserCannotDeleteSystemAccounts(int firstAccount, int lastAc .toArray(HapiSpecOperation[]::new))); } + @BddMethodIsNotATest final HapiSpec systemUserCannotDeleteSystemFiles(int[] fileIds, String sysUser) { return defaultHapiSpec("systemUserCannotDeleteSystemFiles") .given() @@ -205,6 +208,7 @@ final HapiSpec systemUserCannotDeleteSystemFiles(int[] fileIds, String sysUser) .toArray(HapiSpecOperation[]::new))); } + @BddMethodIsNotATest final HapiSpec normalUserCannotDeleteSystemFiles(int[] fileIds) { return defaultHapiSpec("normalUserCannotDeleteSystemFiles") .given(newKeyNamed("normalKey")) @@ -217,6 +221,7 @@ final HapiSpec normalUserCannotDeleteSystemFiles(int[] fileIds) { .toArray(HapiSpecOperation[]::new))); } + @BddMethodIsNotATest final HapiSpec systemDeleteCannotDeleteSystemFiles(int[] fileIds, String sysUser) { return defaultHapiSpec("systemDeleteCannotDeleteSystemFiles") .given() diff --git a/hedera-node/test-clients/src/main/resource/bootstrap.properties b/hedera-node/test-clients/src/main/resource/bootstrap.properties index 83ef79f71414..0a6884ad2a38 100644 --- a/hedera-node/test-clients/src/main/resource/bootstrap.properties +++ b/hedera-node/test-clients/src/main/resource/bootstrap.properties @@ -66,7 +66,7 @@ contracts.allowSystemUseOfHapiSigs=TokenAssociateToAccount,TokenDissociateFromAc contracts.maxNumWithHapiSigsAccess=0 contracts.withSpecialHapiSigsAccess= contracts.allowCreate2=true -contracts.chainId=295 +contracts.chainId=298 contracts.defaultLifetime=7890000 contracts.enforceCreationThrottle=false contracts.evm.version=v0.38 From 85d1006fb87737f051a3ca4ecf2be539ae12c5b4 Mon Sep 17 00:00:00 2001 From: Deyan Dimitrov Date: Mon, 25 Dec 2023 03:11:03 +0200 Subject: [PATCH 44/80] fix: `DuplicateManagementTest` tests (#10622) Signed-off-by: dikel Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../app/workflows/handle/HandleWorkflow.java | 32 +++++++++---------- .../bdd/suites/issues/Issue1765Suite.java | 2 +- .../records/DuplicateManagementTest.java | 21 ++++++------ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 6d32033e85b6..7634cfd0f877 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -608,11 +608,6 @@ private ValidationResult validate( final var txBody = txInfo.txBody(); boolean isPayerHollow; - // Check if pre-handle was successful - if (preHandleResult.status() != SO_FAR_SO_GOOD) { - return new ValidationResult(preHandleResult.status(), preHandleResult.responseCode()); - } - // Check for duplicate transactions. It is perfectly normal for there to be duplicates -- it is valid for // a user to intentionally submit duplicates to multiple nodes as a hedge against dishonest nodes, or for // other reasons. If we find a duplicate, we *will not* execute the transaction, we will simply charge @@ -625,12 +620,19 @@ private ValidationResult validate( DUPLICATE_TRANSACTION); } - // Check the status and solvency of the payer - + // Check the status and solvency of the payer (assuming their signature is valid) try { final var payer = solvencyPreCheck.getPayerAccount(storeFactory, payerID); - solvencyPreCheck.checkSolvency(txInfo, payer, fees, false); isPayerHollow = isHollow(payer); + // Check all signature verifications. This will also wait, if validation is still ongoing. + // If the payer is hollow the key will be null, so we skip the payer signature verification. + if (!isPayerHollow) { + final var payerKeyVerification = verifier.verificationFor(preHandleResult.getPayerKey()); + if (payerKeyVerification.failed()) { + return new ValidationResult(NODE_DUE_DILIGENCE_FAILURE, INVALID_PAYER_SIGNATURE); + } + } + solvencyPreCheck.checkSolvency(txInfo, payer, fees, false); } catch (final InsufficientServiceFeeException e) { return new ValidationResult(PAYER_UNWILLING_OR_UNABLE_TO_PAY_SERVICE_FEE, e.responseCode()); } catch (final InsufficientNonFeeDebitsException e) { @@ -655,6 +657,11 @@ private ValidationResult validate( return new ValidationResult(PRE_HANDLE_FAILURE, ResponseCodeEnum.UNAUTHORIZED); } + // Check if pre-handle was successful + if (preHandleResult.status() != SO_FAR_SO_GOOD) { + return new ValidationResult(preHandleResult.status(), preHandleResult.responseCode()); + } + // Check if the transaction is privileged and if the payer has the required privileges final var privileges = authorizer.hasPrivilegedAuthorization(payerID, functionality, txBody); if (privileges == SystemPrivilege.UNAUTHORIZED) { @@ -664,15 +671,6 @@ private ValidationResult validate( return new ValidationResult(PRE_HANDLE_FAILURE, ResponseCodeEnum.ENTITY_NOT_ALLOWED_TO_DELETE); } - // Check all signature verifications. This will also wait, if validation is still ongoing. - // If the payer is hollow the key will be null, so we skip the payer signature verification. - if (!isPayerHollow) { - final var payerKeyVerification = verifier.verificationFor(preHandleResult.getPayerKey()); - if (payerKeyVerification.failed()) { - return new ValidationResult(NODE_DUE_DILIGENCE_FAILURE, INVALID_PAYER_SIGNATURE); - } - } - // verify all the keys for (final var key : preHandleResult.getRequiredKeys()) { final var verification = verifier.verificationFor(key); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java index 0ec45d67c065..2958952ff4c7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/issues/Issue1765Suite.java @@ -73,7 +73,7 @@ private static HapiSpec get950Balance() { } @HapiTest - private static HapiSpec recordOfInvalidContractUpdateSanityChecks() { + final HapiSpec recordOfInvalidContractUpdateSanityChecks() { final long ADEQUATE_FEE = 100_000_000L; final String INVALID_CONTRACT = IMAGINARY; final String THE_MEMO_IS = MEMO_IS; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java index cf4c207acbc0..695198b40ce9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java @@ -32,6 +32,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PAYER_SIGNATURE; @@ -39,6 +40,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.suites.HapiSuite; @@ -60,13 +62,14 @@ public static void main(String... args) { @Override public List getSpecsInSuite() { - return List.of(new HapiSpec[] { - usesUnclassifiableIfNoClassifiableAvailable(), - hasExpectedDuplicates(), - classifiableTakesPriorityOverUnclassifiable(), - }); + return List.of( + usesUnclassifiableIfNoClassifiableAvailable(), + hasExpectedDuplicates(), + classifiableTakesPriorityOverUnclassifiable()); } + @HapiTest + @SuppressWarnings("java:S5960") final HapiSpec hasExpectedDuplicates() { return defaultHapiSpec("HasExpectedDuplicates") .given( @@ -78,7 +81,7 @@ final HapiSpec hasExpectedDuplicates() { .txnId(TXN_ID)) .payingWith(CIVILIAN) .fee(ONE_HBAR) - .hasPrecheck(NOT_SUPPORTED), + .hasPrecheckFrom(NOT_SUPPORTED, BUSY), uncheckedSubmit( cryptoCreate(REPEATED).payingWith(CIVILIAN).txnId(TXN_ID)), uncheckedSubmit( @@ -111,7 +114,6 @@ final HapiSpec hasExpectedDuplicates() { var cheapGet = getTxnRecord("cheapTxn").assertingNothingAboutHashes(); var costlyGet = getTxnRecord("costlyTxn").assertingNothingAboutHashes(); allRunFor(spec, cheapGet, costlyGet); - var payer = spec.registry().getAccountID(CIVILIAN); var cheapRecord = cheapGet.getResponseRecord(); var costlyRecord = costlyGet.getResponseRecord(); opLog.info("cheapRecord: {}", cheapRecord); @@ -119,7 +121,7 @@ final HapiSpec hasExpectedDuplicates() { var cheapPrice = getNonFeeDeduction(cheapRecord).orElse(0); var costlyPrice = getNonFeeDeduction(costlyRecord).orElse(0); assertEquals( - 3 * cheapPrice, + 3 * cheapPrice - 1, costlyPrice, String.format( "Costly (%d) should be 3x more expensive than" + " cheap (%d)!", @@ -127,13 +129,14 @@ final HapiSpec hasExpectedDuplicates() { })); } + @HapiTest final HapiSpec usesUnclassifiableIfNoClassifiableAvailable() { return defaultHapiSpec("UsesUnclassifiableIfNoClassifiableAvailable") .given( newKeyNamed("wrongKey"), cryptoCreate(CIVILIAN), usableTxnIdNamed(TXN_ID).payerId(CIVILIAN), - cryptoTransfer(tinyBarsFromTo(GENESIS, TO, 100_000_000L))) + cryptoTransfer(tinyBarsFromTo(GENESIS, TO, ONE_HBAR))) .when( uncheckedSubmit(cryptoCreate("nope") .payingWith(CIVILIAN) From 155cb351d87a641f320eea82bc5bf97a409ffb84 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 26 Dec 2023 15:48:00 -0600 Subject: [PATCH 45/80] fix: bug when node is removed (#10643) Signed-off-by: Cody Littley --- .../gossip/shadowgraph/LatestEventTipsetTracker.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java index d2a996a94fe7..4e3d8876e9ed 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java @@ -36,6 +36,7 @@ public class LatestEventTipsetTracker { private final TipsetTracker tipsetTracker; private final NodeId selfId; + private final AddressBook addressBook; private Tipset latestSelfEventTipset; /** @@ -53,6 +54,7 @@ public LatestEventTipsetTracker( this.tipsetTracker = new TipsetTracker(time, addressBook); this.selfId = Objects.requireNonNull(selfId); + this.addressBook = addressBook; } /** @@ -80,6 +82,12 @@ public synchronized Tipset getLatestSelfEventTipset() { * @param event The event to insert. */ public synchronized void addEvent(final EventImpl event) { + if (!addressBook.contains(event.getCreatorId())) { + // Ignore this event. Possible in scenarios where a node is removed or a state is moved from a + // network with a different address book. + return; + } + final List parentDescriptors = new ArrayList<>(2); if (event.getSelfParent() != null) { parentDescriptors.add(event.getSelfParent().getBaseEvent().getDescriptor()); From 967c97f7300a9b8af006fa07997c739e9561e813 Mon Sep 17 00:00:00 2001 From: Alexander Gadzhalov Date: Wed, 27 Dec 2023 00:44:12 +0200 Subject: [PATCH 46/80] fix: SigningReqsSuite (#10592) Signed-off-by: dikel Signed-off-by: Alexander Gadzhalov Signed-off-by: Michael Tinker Co-authored-by: dikel Co-authored-by: Michael Tinker Co-authored-by: Petar Tonev --- .../app/workflows/TransactionChecker.java | 50 +++---- .../app/workflows/handle/HandleWorkflow.java | 6 +- .../handle/record/RecordListBuilder.java | 6 +- .../SingleTransactionRecordBuilderImpl.java | 9 +- .../prehandle/DueDiligenceException.java | 44 ++++++ .../prehandle/PreHandleWorkflowImpl.java | 18 ++- .../java/contract/CreatesERC20XTest.java | 113 +++++++++++++-- .../java/contract/CreatesERC721XTest.java | 59 +++++++- .../node/config/data/ContractsConfig.java | 4 +- .../exec/gas/SystemContractGasCalculator.java | 21 +++ .../scope/EitherOrVerificationStrategy.java | 45 ++++++ .../systemcontracts/HtsSystemContract.java | 3 - .../systemcontracts/hts/AbstractHtsCall.java | 5 + .../exec/systemcontracts/hts/ReturnTypes.java | 2 + .../hts/create/ClassicCreatesCall.java | 106 ++++++++++---- .../hts/create/CreateTranslator.java | 25 ++-- .../contract/impl/exec/utils/FrameUtils.java | 22 +++ .../impl/hevm/HederaEvmTransactionResult.java | 21 +-- .../records/ContractCallRecordBuilder.java | 8 ++ .../contract/impl/test/TestHelpers.java | 1 + .../gas/SystemContractGasCalculatorTest.java | 26 +++- .../EitherOrVerificationStrategyTest.java | 65 +++++++++ .../HtsSystemContractTest.java | 13 -- .../hts/create/ClassicCreatesCallTest.java | 131 ++++++++++++------ .../impl/test/exec/utils/FrameUtilsTest.java | 26 ++++ .../hevm/HederaEvmTransactionResultTest.java | 7 + .../records/TokenCreateRecordBuilder.java | 5 +- .../contract/precompile/SigningReqsSuite.java | 4 +- .../suites/fees/CostOfEverythingSuite.java | 13 +- .../file/ProtectedFilesUpdateSuite.java | 3 + .../suites/leaky/LeakyContractTestsSuite.java | 1 + .../suites/leaky/LeakyCryptoTestsSuite.java | 7 +- .../misc/CannotDeleteSystemEntitiesSuite.java | 1 + .../suites/records/RecordCreationSuite.java | 7 +- 34 files changed, 701 insertions(+), 176 deletions(-) create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/DueDiligenceException.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionChecker.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionChecker.java index 0a02288f6666..6820fa920c40 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionChecker.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionChecker.java @@ -49,6 +49,7 @@ import com.hedera.node.app.spi.HapiUtils; import com.hedera.node.app.spi.UnknownHederaFunctionality; import com.hedera.node.app.spi.workflows.PreCheckException; +import com.hedera.node.app.workflows.prehandle.DueDiligenceException; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.Codec; @@ -207,19 +208,15 @@ public Transaction parse(@NonNull final Bytes buffer) throws PreCheckException { */ @NonNull public TransactionInfo check(@NonNull final Transaction tx) throws PreCheckException { - - // NOTE: Since we've already parsed the transaction, we assume that the transaction was not too many - // bytes. This is a safe assumption because the code that receives the transaction bytes and parses - // the transaction also verifies that the transaction is not too large. - - // 1. Validate that either the transaction is using deprecated fields, or not, but not both + // NOTE: Since we've already parsed the transaction, we assume that the + // transaction was not too many bytes. This is a safe assumption because + // the code that receives the transaction bytes and parses/ the transaction + // also verifies that the transaction is not too large. checkTransactionDeprecation(tx); - // 2. Get the transaction body and the signature map final Bytes bodyBytes; final SignatureMap signatureMap; if (tx.signedTransactionBytes().length() > 0) { - // 2a. Parse and validate the signed transaction (if available). Throws PreCheckException if not parsable. final var signedTransaction = parseStrict( tx.signedTransactionBytes().toReadableSequentialData(), SignedTransaction.PROTOBUF, @@ -227,31 +224,34 @@ public TransactionInfo check(@NonNull final Transaction tx) throws PreCheckExcep bodyBytes = signedTransaction.bodyBytes(); signatureMap = signedTransaction.sigMap(); } else { - // 2b. Use the deprecated fields instead bodyBytes = tx.bodyBytes(); signatureMap = tx.sigMap(); } - - // 2b. There has to be a signature map. Every transaction has at least one signature for the payer. if (signatureMap == null) { throw new PreCheckException(INVALID_TRANSACTION_BODY); } - - // 2c. Check that the signature map does not have any entries that could apply to the same key - checkPrefixMismatch(signatureMap.sigPairOrElse(emptyList())); - - // 3. Parse and validate TransactionBody final var txBody = parseStrict(bodyBytes.toReadableSequentialData(), TransactionBody.PROTOBUF, INVALID_TRANSACTION_BODY); - checkTransactionBody(txBody); - - // 4. Return TransactionInfo + final HederaFunctionality functionality; try { - final var functionality = HapiUtils.functionOf(txBody); - return new TransactionInfo(tx, txBody, signatureMap, bodyBytes, functionality); + functionality = HapiUtils.functionOf(txBody); } catch (UnknownHederaFunctionality e) { throw new PreCheckException(INVALID_TRANSACTION_BODY); } + if (!txBody.hasTransactionID()) { + throw new PreCheckException(INVALID_TRANSACTION_ID); + } + return checkParsed(new TransactionInfo(tx, txBody, signatureMap, bodyBytes, functionality)); + } + + public TransactionInfo checkParsed(@NonNull final TransactionInfo txInfo) throws PreCheckException { + try { + checkPrefixMismatch(txInfo.signatureMap().sigPairOrElse(emptyList())); + checkTransactionBody(txInfo.txBody()); + return txInfo; + } catch (PreCheckException e) { + throw new DueDiligenceException(e.responseCode(), txInfo); + } } /** @@ -309,7 +309,7 @@ private void checkTransactionDeprecation(@NonNull final Transaction tx) throws P */ public void checkTransactionBody(@NonNull final TransactionBody txBody) throws PreCheckException { final var config = props.getConfiguration().getConfigData(HederaConfig.class); - checkTransactionID(txBody.transactionID()); + checkTransactionID(txBody.transactionIDOrThrow()); checkMemo(txBody.memo(), config.transactionMaxMemoUtf8Bytes()); // You cannot have a negative transaction fee!! We're not paying you, buddy. @@ -369,11 +369,7 @@ public void checkTimeBox(@NonNull final TransactionBody txBody, @NonNull final I * @throws PreCheckException if validation fails * @throws NullPointerException if any of the parameters is {@code null} */ - private void checkTransactionID(@Nullable final TransactionID txnId) throws PreCheckException { - if (txnId == null) { - throw new PreCheckException(INVALID_TRANSACTION_ID); - } - + private void checkTransactionID(@NonNull final TransactionID txnId) throws PreCheckException { // Determines whether the given {@link AccountID} can possibly be valid. This method does not refer to state, // it simply looks at the {@code accountID} itself to determine whether it might be valid. An ID is valid if // the shard and realm match the shard and realm of this node, AND if the account number is positive or if diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 7634cfd0f877..6f081d770a0d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -291,7 +291,7 @@ private void handleUserTransaction( // the user transaction blockRecordManager.advanceConsensusClock(consensusNow, state); - TransactionBody txBody = null; + TransactionBody txBody; AccountID payer = null; Fees fees = null; try { @@ -601,6 +601,10 @@ private ValidationResult validate( @NonNull final ReadableStoreFactory storeFactory, @NonNull final Fees fees, final long nodeID) { + if (preHandleResult.status() == NODE_DUE_DILIGENCE_FAILURE) { + // We can stop immediately if the pre-handle result was a node due diligence failure + return new ValidationResult(preHandleResult.status(), preHandleResult.responseCode()); + } final var txInfo = preHandleResult.txInfo(); final var payerID = txInfo.payerID(); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java index 974fca772924..38d306c630a7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java @@ -376,9 +376,11 @@ public void revertChildrenOf(@NonNull final SingleTransactionRecordBuilderImpl r childRecordBuilders.set(i, null); followingChildRemoved = true; } else { - if (child.reversingBehavior() == ReversingBehavior.REVERSIBLE && SUCCESSES.contains(child.status())) { + if (child.reversingBehavior() == ReversingBehavior.REVERSIBLE) { child.nullOutSideEffectFields(); - child.status(ResponseCodeEnum.REVERTED_SUCCESS); + if (SUCCESSES.contains(child.status())) { + child.status(ResponseCodeEnum.REVERTED_SUCCESS); + } } if (into != i) { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index ab17a05b9cc4..df10d82fae95 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -176,6 +176,8 @@ public class SingleTransactionRecordBuilderImpl // CryptoCreate transactions as ContractCreate synthetic transactions private final ExternalizedRecordCustomizer customizer; + private TokenID tokenID; + /** * Possible behavior of a {@link SingleTransactionRecord} when a parent transaction fails, * and it is asked to be reverted @@ -333,7 +335,7 @@ public void nullOutSideEffectFields() { transactionReceiptBuilder.newTotalSupply(0L); transactionReceiptBuilder.topicRunningHashVersion(0L); transactionReceiptBuilder.topicSequenceNumber(0L); - transactionRecordBuilder.contractCreateResult((ContractFunctionResult) null); + // Note that internal contract creations are removed instead of reversed transactionRecordBuilder.scheduleRef((ScheduleID) null); transactionRecordBuilder.alias(Bytes.EMPTY); transactionRecordBuilder.ethereumHash(Bytes.EMPTY); @@ -894,10 +896,15 @@ public SingleTransactionRecordBuilderImpl topicRunningHashVersion(final long top @NonNull public SingleTransactionRecordBuilderImpl tokenID(@NonNull final TokenID tokenID) { requireNonNull(tokenID, "tokenID must not be null"); + this.tokenID = tokenID; transactionReceiptBuilder.tokenID(tokenID); return this; } + public TokenID tokenID() { + return tokenID; + } + /** * Sets the receipt newTotalSupply. * diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/DueDiligenceException.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/DueDiligenceException.java new file mode 100644 index 000000000000..2543db60efcc --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/DueDiligenceException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.workflows.prehandle; + +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.node.app.spi.workflows.PreCheckException; +import com.hedera.node.app.workflows.TransactionInfo; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * A transaction that captures a node due diligence failure; including the + * associated status and (if it was parseable) the information on the + * offending transaction. + */ +public class DueDiligenceException extends PreCheckException { + @Nullable + private final TransactionInfo transactionInfo; + + public DueDiligenceException( + @NonNull final ResponseCodeEnum responseCode, @Nullable TransactionInfo transactionInfo) { + super(responseCode); + this.transactionInfo = transactionInfo; + } + + @Nullable + public TransactionInfo txInfo() { + return transactionInfo; + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java index 61ac2987d29a..cdf977230e27 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/prehandle/PreHandleWorkflowImpl.java @@ -172,7 +172,8 @@ public PreHandleResult preHandleTransaction( // 1. Parse the Transaction and check the syntax final TransactionInfo txInfo; try { - // Transaction info is a pure function of the transaction, so we can always reuse it from a prior result + // Transaction info is a pure function of the transaction, so we can + // always reuse it from a prior result txInfo = previousResult == null ? transactionChecker.parseAndCheck(Bytes.wrap(platformTx.getContents())) : previousResult.txInfo(); @@ -180,17 +181,24 @@ public PreHandleResult preHandleTransaction( // In particular, a null transaction info means we already know the transaction's final failure status return previousResult; } + // But we still re-check for node diligence failures + transactionChecker.checkParsed(txInfo); // The transaction account ID MUST have matched the creator! if (!creator.equals(txInfo.txBody().nodeAccountID())) { - throw new PreCheckException(INVALID_NODE_ACCOUNT); + throw new DueDiligenceException(INVALID_NODE_ACCOUNT, txInfo); } - } catch (PreCheckException preCheck) { + } catch (DueDiligenceException e) { // The node SHOULD have verified the transaction before it was submitted to the network. // Since it didn't, it has failed in its due diligence and will be charged accordingly. - logger.debug("Transaction failed pre-check", preCheck); return nodeDueDiligenceFailure( creator, - preCheck.responseCode(), + e.responseCode(), + e.txInfo(), + configProvider.getConfiguration().getVersion()); + } catch (PreCheckException e) { + return nodeDueDiligenceFailure( + creator, + e.responseCode(), null, configProvider.getConfiguration().getVersion()); } diff --git a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java index 3e9e5e3dfb92..02c0039ad4a5 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java @@ -19,6 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_RENEWAL_PERIOD; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static contract.CreatesXTestConstants.DECIMALS; import static contract.CreatesXTestConstants.DECIMALS_BIG_INT; import static contract.CreatesXTestConstants.DECIMALS_LONG; @@ -50,7 +51,7 @@ import static contract.XTestConstants.SENDER_CONTRACT_ID_KEY; import static contract.XTestConstants.SENDER_ID; import static contract.XTestConstants.addErc20Relation; -import static contract.XTestConstants.assertSuccess; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.hapi.node.base.AccountID; @@ -64,6 +65,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.CreateTranslator; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.tuweni.bytes.Bytes; /** @@ -127,13 +129,21 @@ public class CreatesERC20XTest extends AbstractContractXTest { @Override protected void doScenarioOperations() { + final AtomicInteger tokenId = new AtomicInteger(1004); // should successfully create fungible token v1 runHtsCallAndExpectOnSuccess( SENDER_BESU_ADDRESS, Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN, INITIAL_TOTAL_SUPPLY_BIG_INT, DECIMALS_BIG_INT) .array()), - assertSuccess("createFungibleTokenV1")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should successfully create fungible token without TokenKeys (empty array) runHtsCallAndExpectOnSuccess( @@ -153,7 +163,14 @@ protected void doScenarioOperations() { INITIAL_TOTAL_SUPPLY_BIG_INT, DECIMALS_BIG_INT) .array()), - assertSuccess("createFungibleTokenV1 - sans keys")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert on invalid account address runHtsCallAndExpectRevert( @@ -215,7 +232,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V2 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN, INITIAL_TOTAL_SUPPLY_BIG_INT, DECIMALS_LONG) .array()), - assertSuccess("createFungibleTokenV2")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should successfully create fungible token without TokenKeys (empty array) runHtsCallAndExpectOnSuccess( @@ -235,7 +259,14 @@ protected void doScenarioOperations() { INITIAL_TOTAL_SUPPLY_BIG_INT, DECIMALS_LONG) .array()), - assertSuccess("createFungibleTokenV2 - sans keys")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert on invalid account address runHtsCallAndExpectRevert( @@ -297,7 +328,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V3 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN, INITIAL_TOTAL_SUPPLY, DECIMALS) .array()), - assertSuccess("createFungibleTokenV3")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should successfully create fungible token without TokenKeys (empty array) runHtsCallAndExpectOnSuccess( @@ -317,7 +355,14 @@ protected void doScenarioOperations() { INITIAL_TOTAL_SUPPLY, DECIMALS) .array()), - assertSuccess("createFungibleTokenV3 - sans keys")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert on invalid account address runHtsCallAndExpectRevert( @@ -385,7 +430,14 @@ protected void doScenarioOperations() { // FractionalFee new Tuple[] {FRACTIONAL_FEE}) .array()), - assertSuccess("createFungibleWithCustomFeesV1")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should successfully create fungible token without TokenKeys (empty array) runHtsCallAndExpectOnSuccess( @@ -409,7 +461,14 @@ protected void doScenarioOperations() { // FractionalFee new Tuple[] {FRACTIONAL_FEE}) .array()), - assertSuccess("createFungibleWithCustomFeesV1")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert on invalid account address runHtsCallAndExpectRevert( @@ -492,7 +551,14 @@ protected void doScenarioOperations() { // FractionalFee new Tuple[] {FRACTIONAL_FEE}) .array()), - assertSuccess("createFungibleWithCustomFeesV2")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should successfully create fungible token without TokenKeys (empty array) runHtsCallAndExpectOnSuccess( @@ -516,7 +582,14 @@ protected void doScenarioOperations() { // FractionalFee new Tuple[] {FRACTIONAL_FEE}) .array()), - assertSuccess("createFungibleWithCustomFeesV2")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert on invalid account address runHtsCallAndExpectRevert( @@ -599,7 +672,14 @@ protected void doScenarioOperations() { // FractionalFee new Tuple[] {FRACTIONAL_FEE}) .array()), - assertSuccess("createFungibleWithCustomFeesV3")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should successfully create fungible token without TokenKeys (empty array) runHtsCallAndExpectOnSuccess( @@ -623,7 +703,14 @@ protected void doScenarioOperations() { // FractionalFee new Tuple[] {FRACTIONAL_FEE}) .array()), - assertSuccess("createFungibleWithCustomFeesV3")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert on invalid account address runHtsCallAndExpectRevert( diff --git a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java index 958be3676578..6fef4556da35 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java @@ -19,6 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_RENEWAL_PERIOD; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SUPPLY_KEY; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; import static contract.CreatesXTestConstants.EXPIRY; import static contract.CreatesXTestConstants.FIXED_FEE; @@ -47,7 +48,7 @@ import static contract.XTestConstants.SENDER_CONTRACT_ID_KEY; import static contract.XTestConstants.SENDER_ID; import static contract.XTestConstants.addErc20Relation; -import static contract.XTestConstants.assertSuccess; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.hapi.node.base.AccountID; @@ -61,6 +62,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.CreateTranslator; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.tuweni.bytes.Bytes; /** @@ -124,13 +126,21 @@ public class CreatesERC721XTest extends AbstractContractXTest { @Override protected void doScenarioOperations() { + final AtomicInteger tokenId = new AtomicInteger(1004); // should successfully create non-fungible token without custom fees v1 runHtsCallAndExpectOnSuccess( SENDER_BESU_ADDRESS, Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN) .array()), - assertSuccess("createNonFungibleTokenV1")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert without provided supply key runHtsCallAndExpectRevert( @@ -203,7 +213,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V2 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN) .array()), - assertSuccess("createNonFungibleTokenV2")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert without provided supply key runHtsCallAndExpectRevert( @@ -276,7 +293,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V3 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN) .array()), - assertSuccess("createNonFungibleTokenV3")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert without provided supply key runHtsCallAndExpectRevert( @@ -349,7 +373,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN, new Tuple[] {FIXED_FEE}, new Tuple[] {ROYALTY_FEE}) .array()), - assertSuccess("createNonFungibleWithCustomFeesV1")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert without provided supply key runHtsCallAndExpectRevert( @@ -432,7 +463,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V2 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN, new Tuple[] {FIXED_FEE}, new Tuple[] {ROYALTY_FEE}) .array()), - assertSuccess("createNonFungibleWithCustomFeesV2")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert without provided supply key runHtsCallAndExpectRevert( @@ -515,7 +553,14 @@ protected void doScenarioOperations() { Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V3 .encodeCallWithArgs(DEFAULT_HEDERA_TOKEN, new Tuple[] {FIXED_FEE}, new Tuple[] {ROYALTY_FEE}) .array()), - assertSuccess("createNonFungibleWithCustomFeesV3")); + output -> assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) SUCCESS.protoOrdinal(), + asLongZeroHeadlongAddress(new TokenID(0, 0, tokenId.getAndIncrement()))) + .array()), + output)); // should revert without provided supply key runHtsCallAndExpectRevert( diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java index 25f4cb5b85db..9432d62b4f24 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java @@ -31,8 +31,8 @@ public record ContractsConfig( @ConfigProperty(defaultValue = "0til100M,2000til450M") @NetworkProperty String storageSlotPriceTiers, @ConfigProperty(defaultValue = "7890000") @NetworkProperty long defaultLifetime, // @ConfigProperty(defaultValue = "") KnownBlockValues knownBlockHash, - // @ConfigProperty(value = "keys.legacyActivations", defaultValue="1058134by[1062784]") - // LegacyContractIdActivations keysLegacyActivations, + @ConfigProperty(value = "keys.legacyActivations", defaultValue = "1058134by[1062784]") + String keysLegacyActivations, @ConfigProperty(value = "localCall.estRetBytes", defaultValue = "32") @NetworkProperty int localCallEstRetBytes, @ConfigProperty(defaultValue = "true") @NetworkProperty boolean allowCreate2, @ConfigProperty(defaultValue = "false") @NetworkProperty boolean allowAutoAssociations, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java index 75f4a39128ce..074784a41cb2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java @@ -111,6 +111,27 @@ public long canonicalPriceInTinybars(@NonNull final DispatchType dispatchType) { return tinybarValues.asTinybars(dispatchPrices.canonicalPriceInTinycents(dispatchType)); } + /** + * Given a dispatch, returns the canonical price for that dispatch. + * + * @param body the transaction body to be dispatched + * @param payer the payer account + * @return the canonical price for that dispatch + */ + public long canonicalPriceInTinybars(@NonNull final TransactionBody body, @NonNull final AccountID payer) { + return feeCalculator.applyAsLong(body, payer); + } + + /** + * Given a gas requirement, returns the equivalent tinybar cost at the current gas price. + * + * @param gas the gas requirement + * @return the equivalent tinybar cost at the current gas price + */ + public long gasCostInTinybars(final long gas) { + return gas * tinybarValues.childTransactionTinybarGasPrice(); + } + /** * Given a tinybar price, returns the equivalent gas requirement at the current gas price. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java new file mode 100644 index 000000000000..b99890b563b7 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/EitherOrVerificationStrategy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.scope; + +import com.hedera.hapi.node.base.Key; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A {@link VerificationStrategy} that verifies signatures as if both of two contracts are "active; but never + * using top-level signatures. + * + *

    This is the verification strategy used to support the {@code contracts.keys.legacyActivations} property. + */ +public class EitherOrVerificationStrategy implements VerificationStrategy { + private final VerificationStrategy firstStrategy; + private final VerificationStrategy secondStrategy; + + public EitherOrVerificationStrategy( + @NonNull final VerificationStrategy firstStrategy, @NonNull final VerificationStrategy secondStrategy) { + this.firstStrategy = firstStrategy; + this.secondStrategy = secondStrategy; + } + + @Override + public Decision decideForPrimitive(@NonNull final Key key) { + return firstStrategy.decideForPrimitive(key) == Decision.VALID + || secondStrategy.decideForPrimitive(key) == Decision.VALID + ? Decision.VALID + : Decision.INVALID; + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java index 492a084e82ed..89e450a5a50d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java @@ -137,9 +137,6 @@ private static FullResult resultOfExecuting( log.error("Unhandled failure for input {} to HTS system contract", input, internal); return haltResult(ExceptionalHaltReason.PRECOMPILE_ERROR, frame.getRemainingGas()); } - if (pricedResult.nonGasCost() > 0) { - throw new AssertionError("Not implemented"); - } return pricedResult.fullResult(); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java index 7d5a9d444c4b..d10110dfdeeb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/AbstractHtsCall.java @@ -27,6 +27,7 @@ import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; +import com.hedera.node.app.service.contract.impl.exec.scope.HederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; @@ -52,6 +53,10 @@ protected AbstractHtsCall( this.isViewCall = isViewCall; } + protected HederaOperations operations() { + return enhancement.operations(); + } + protected HederaNativeOperations nativeOperations() { return enhancement.nativeOperations(); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java index 644d31fe0357..73952456606b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java @@ -59,6 +59,7 @@ private ReturnTypes() { public static final String BOOL = "(bool)"; public static final String STRING = "(string)"; public static final String ADDRESS = "(address)"; + public static final String RESPONSE_CODE_BOOL = "(int32,bool)"; public static final String RESPONSE_CODE_INT32 = "(int32,int32)"; public static final String RESPONSE_CODE_UINT256 = "(int64,uint256)"; @@ -173,6 +174,7 @@ private ReturnTypes() { + ")"; // spotless:on + public static final TupleType RC_AND_ADDRESS_ENCODER = TupleType.parse("(int64,address)"); private static final TupleType RC_ENCODER = TupleType.parse(INT_64); public static Bytes tuweniEncodedRc(@NonNull final ResponseCodeEnum status) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index 507285bb448f..99e7873c04f5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -24,8 +24,16 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.RC_AND_ADDRESS_ENCODER; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.stackIncludesActiveAddress; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asHeadlongAddress; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.headlongAddressOf; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjToBesuAddress; import static com.hedera.node.app.service.contract.impl.utils.SystemContractUtils.contractFunctionResultFailedFor; import static java.util.Objects.requireNonNull; @@ -35,14 +43,18 @@ import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; +import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; +import com.hedera.node.app.service.contract.impl.exec.scope.EitherOrVerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import com.hedera.node.config.data.ContractsConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; -import java.math.BigInteger; import java.nio.ByteBuffer; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -50,43 +62,53 @@ public class ClassicCreatesCall extends AbstractHtsCall { /** - * The mono-service stipulated minimum gas requirement for a token creation. + * The mono-service stipulated gas cost for a token creation (remaining fee is collected by sent value) */ - private static final long MINIMUM_TINYBAR_PRICE = 100_000L; + private static final long FIXED_GAS_COST = 100_000L; @NonNull final TransactionBody syntheticCreate; - private final AddressIdConverter addressIdConverter; private final VerificationStrategy verificationStrategy; private final AccountID spenderId; - private final long gasRequirement; + private final long nonGasCost; public ClassicCreatesCall( @NonNull final SystemContractGasCalculator systemContractGasCalculator, @NonNull final HederaWorldUpdater.Enhancement enhancement, @NonNull final TransactionBody syntheticCreate, @NonNull final VerificationStrategy verificationStrategy, - @NonNull final org.hyperledger.besu.datatypes.Address spender, + @NonNull final Address spender, @NonNull final AddressIdConverter addressIdConverter) { super(systemContractGasCalculator, enhancement, false); this.syntheticCreate = requireNonNull(syntheticCreate); this.verificationStrategy = requireNonNull(verificationStrategy); - this.addressIdConverter = requireNonNull(addressIdConverter); - this.spenderId = addressIdConverter.convert(asHeadlongAddress(spender.toArrayUnsafe())); - this.gasRequirement = gasCalculator.gasRequirement(syntheticCreate, spenderId, MINIMUM_TINYBAR_PRICE); + final var baseCost = gasCalculator.canonicalPriceInTinybars(syntheticCreate, spenderId); + // The non-gas cost is a 20% surcharge on the HAPI TokenCreate price, minus the fee taken as gas + this.nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); } + private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besuAddress) {} + @Override - public @NonNull PricedResult execute() { + public @NonNull PricedResult execute(@NonNull final MessageFrame frame) { + if (frame.getValue().lessThan(Wei.of(nonGasCost))) { + return completionWith( + FIXED_GAS_COST, + systemContractOperations().externalizePreemptedDispatch(syntheticCreate, INSUFFICIENT_TX_FEE), + RC_AND_ADDRESS_ENCODER.encodeElements((long) INSUFFICIENT_TX_FEE.protoOrdinal(), ZERO_ADDRESS)); + } else { + operations().collectFee(spenderId, nonGasCost); + } + final var token = ((TokenCreateTransactionBody) syntheticCreate.data().value()); if (token.symbol().isEmpty()) { return externalizeUnsuccessfulResult(MISSING_TOKEN_SYMBOL, gasCalculator.viewGasRequirement()); } final var treasuryAccount = - nativeOperations().getAccount(token.treasury().accountNum()); + nativeOperations().getAccount(token.treasuryOrThrow().accountNumOrThrow()); if (treasuryAccount == null) { return externalizeUnsuccessfulResult(INVALID_ACCOUNT_ID, gasCalculator.viewGasRequirement()); } @@ -94,57 +116,83 @@ public ClassicCreatesCall( return externalizeUnsuccessfulResult(INVALID_EXPIRATION_TIME, gasCalculator.viewGasRequirement()); } + // Choose a dispatch verification strategy based on whether the legacy activation address is active + final var dispatchVerificationStrategy = verificationStrategyFor(frame); final var recordBuilder = systemContractOperations() - .dispatch(syntheticCreate, verificationStrategy, spenderId, ContractCallRecordBuilder.class); + .dispatch(syntheticCreate, dispatchVerificationStrategy, spenderId, ContractCallRecordBuilder.class); + recordBuilder.status(standardized(recordBuilder.status())); + final var customFees = ((TokenCreateTransactionBody) syntheticCreate.data().value()).customFees(); final var tokenType = ((TokenCreateTransactionBody) syntheticCreate.data().value()).tokenType(); final var status = recordBuilder.status(); if (status != ResponseCodeEnum.SUCCESS) { - return gasOnly(revertResult(recordBuilder, MINIMUM_TINYBAR_PRICE), status, false); + return gasOnly(revertResult(recordBuilder, FIXED_GAS_COST), status, false); } else { final var isFungible = tokenType == TokenType.FUNGIBLE_COMMON; ByteBuffer encodedOutput; - if (isFungible && customFees.size() == 0) { + if (isFungible && customFees.isEmpty()) { encodedOutput = CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal())); - } else if (isFungible && customFees.size() > 0) { + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); + } else if (isFungible && !customFees.isEmpty()) { encodedOutput = CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal())); - } else if (customFees.size() == 0) { + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); + } else if (customFees.isEmpty()) { encodedOutput = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal())); + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); } else { encodedOutput = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal())); + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); } - return gasOnly(successResult(encodedOutput, gasRequirement), status, false); + return gasOnly(successResult(encodedOutput, FIXED_GAS_COST, recordBuilder), status, false); } } - @Override - public @NonNull PricedResult execute(final MessageFrame frame) { - if (!frame.getValue().greaterOrEqualThan(Wei.of(gasRequirement))) { - return externalizeUnsuccessfulResult(INSUFFICIENT_TX_FEE, gasCalculator.viewGasRequirement()); - } - return execute(); + private VerificationStrategy verificationStrategyFor(@NonNull final MessageFrame frame) { + final var legacyActivation = legacyActivationIn(frame); + + // Choose a dispatch verification strategy based on whether the legacy + // activation address is active (somewhere on the stack) + return stackIncludesActiveAddress(frame, legacyActivation.besuAddress()) + ? new EitherOrVerificationStrategy( + verificationStrategy, + new ActiveContractVerificationStrategy( + legacyActivation.contractNum(), + legacyActivation.pbjAddress(), + false, + UseTopLevelSigs.NO)) + : verificationStrategy; + } + + private LegacyActivation legacyActivationIn(@NonNull final MessageFrame frame) { + final var literal = configOf(frame).getConfigData(ContractsConfig.class).keysLegacyActivations(); + final var contractNum = Long.parseLong(literal.substring(literal.indexOf("[") + 1, literal.indexOf("]"))); + final var pbjAddress = com.hedera.pbj.runtime.io.buffer.Bytes.wrap(asEvmAddress(contractNum)); + return new LegacyActivation(contractNum, pbjAddress, pbjToBesuAddress(pbjAddress)); } // @TODO extract externalizeResult() calls into a single location on a higher level private PricedResult externalizeUnsuccessfulResult(ResponseCodeEnum responseCode, long gasRequirement) { final var result = gasOnly(FullResult.revertResult(responseCode, gasRequirement), responseCode, false); final var contractID = asEvmContractId(Address.fromHexString(HTS_EVM_ADDRESS)); - enhancement .systemOperations() .externalizeResult( - contractFunctionResultFailedFor(MINIMUM_TINYBAR_PRICE, responseCode.toString(), contractID), + contractFunctionResultFailedFor(FIXED_GAS_COST, responseCode.toString(), contractID), responseCode); return result; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java index 671501d1280b..040f7892bdc8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java @@ -31,7 +31,6 @@ import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -39,11 +38,11 @@ public class CreateTranslator extends AbstractHtsCallTranslator { public static final Function CREATE_FUNGIBLE_TOKEN_V1 = - new Function("createFungibleToken(" + HEDERA_TOKEN_V1 + ",uint,uint)", ReturnTypes.INT); + new Function("createFungibleToken(" + HEDERA_TOKEN_V1 + ",uint,uint)", "(int64,address)"); public static final Function CREATE_FUNGIBLE_TOKEN_V2 = - new Function("createFungibleToken(" + HEDERA_TOKEN_V2 + ",uint64,uint32)", ReturnTypes.INT); + new Function("createFungibleToken(" + HEDERA_TOKEN_V2 + ",uint64,uint32)", "(int64,address)"); public static final Function CREATE_FUNGIBLE_TOKEN_V3 = - new Function("createFungibleToken(" + HEDERA_TOKEN_V3 + ",int64,int32)", ReturnTypes.INT); + new Function("createFungibleToken(" + HEDERA_TOKEN_V3 + ",int64,int32)", "(int64,address)"); public static final Function CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V1 = new Function( "createFungibleTokenWithCustomFees(" + HEDERA_TOKEN_V1 @@ -54,7 +53,7 @@ public class CreateTranslator extends AbstractHtsCallTranslator { + FRACTIONAL_FEE + ARRAY_BRACKETS + ")", - ReturnTypes.INT); + "(int64,address)"); public static final Function CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V2 = new Function( "createFungibleTokenWithCustomFees(" + HEDERA_TOKEN_V2 @@ -65,7 +64,7 @@ public class CreateTranslator extends AbstractHtsCallTranslator { + FRACTIONAL_FEE + ARRAY_BRACKETS + ")", - ReturnTypes.INT); + "(int64,address)"); public static final Function CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V3 = new Function( "createFungibleTokenWithCustomFees(" + HEDERA_TOKEN_V3 @@ -76,14 +75,14 @@ public class CreateTranslator extends AbstractHtsCallTranslator { + FRACTIONAL_FEE_V2 + ARRAY_BRACKETS + ")", - ReturnTypes.INT); + "(int64,address)"); public static final Function CREATE_NON_FUNGIBLE_TOKEN_V1 = - new Function("createNonFungibleToken(" + HEDERA_TOKEN_V1 + ")", ReturnTypes.INT); + new Function("createNonFungibleToken(" + HEDERA_TOKEN_V1 + ")", "(int64,address)"); public static final Function CREATE_NON_FUNGIBLE_TOKEN_V2 = - new Function("createNonFungibleToken(" + HEDERA_TOKEN_V2 + ")", ReturnTypes.INT); + new Function("createNonFungibleToken(" + HEDERA_TOKEN_V2 + ")", "(int64,address)"); public static final Function CREATE_NON_FUNGIBLE_TOKEN_V3 = - new Function("createNonFungibleToken(" + HEDERA_TOKEN_V3 + ")", ReturnTypes.INT); + new Function("createNonFungibleToken(" + HEDERA_TOKEN_V3 + ")", "(int64,address)"); public static final Function CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 = new Function( "createNonFungibleTokenWithCustomFees(" @@ -95,7 +94,7 @@ public class CreateTranslator extends AbstractHtsCallTranslator { + ROYALTY_FEE + ARRAY_BRACKETS + ")", - ReturnTypes.INT); + "(int64,address)"); public static final Function CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V2 = new Function( "createNonFungibleTokenWithCustomFees(" + HEDERA_TOKEN_V2 @@ -106,7 +105,7 @@ public class CreateTranslator extends AbstractHtsCallTranslator { + ROYALTY_FEE + ARRAY_BRACKETS + ")", - ReturnTypes.INT); + "(int64,address)"); public static final Function CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V3 = new Function( "createNonFungibleTokenWithCustomFees(" + HEDERA_TOKEN_V3 @@ -117,7 +116,7 @@ public class CreateTranslator extends AbstractHtsCallTranslator { + ROYALTY_FEE_V2 + ARRAY_BRACKETS + ")", - ReturnTypes.INT); + "(int64,address)"); private final CreateDecoder decoder; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java index a29639c5410d..4a75b9adab31 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java @@ -172,6 +172,28 @@ public static Optional maybeNext(@NonNull final MessageFrame frame return frames.hasNext() ? Optional.of(frames.next()) : Optional.empty(); } + /** + * Given a frame and an address, returns whether any frame in its + * stack has the given receiver address. + * + * @param frame the frame whose stack to travers + * @param address the receiver address to seek + * @return if the stack includes a frame with the given receive + */ + public static boolean stackIncludesActiveAddress( + @NonNull final MessageFrame frame, @NonNull final Address address) { + final var iter = frame.getMessageFrameStack().iterator(); + // We skip the frame at the top of the stack (recall that a deque representing + // a stack stores the top at the front of its internal list) + for (iter.next(); iter.hasNext(); ) { + final var ancestor = iter.next(); + if (address.equals(ancestor.getRecipientAddress())) { + return true; + } + } + return false; + } + public static boolean unqualifiedDelegateDetected(final MessageFrame frame) { if (!isDelegateCall(frame)) { return false; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java index fee6808f61ad..9ef769810941 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java @@ -26,7 +26,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CONTRACT_STORAGE_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_STORAGE_IN_PRICE_REGIME_HAS_BEEN_USED; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; -import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.errorMessageFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; @@ -66,7 +65,8 @@ public record HederaEvmTransactionResult( @Nullable ExceptionalHaltReason haltReason, @Nullable Bytes revertReason, @NonNull List logs, - @Nullable ContractStateChanges stateChanges) { + @Nullable ContractStateChanges stateChanges, + @Nullable ResponseCodeEnum finalStatus) { public HederaEvmTransactionResult { requireNonNull(senderId); requireNonNull(output); @@ -81,7 +81,6 @@ public record HederaEvmTransactionResult( private static final Bytes MAX_CHILD_RECORDS_EXCEEDED_REASON = Bytes.wrap(MAX_CHILD_RECORDS_EXCEEDED.name()); private static final Bytes INSUFFICIENT_TX_FEE_REASON = Bytes.wrap(INSUFFICIENT_TX_FEE.name()); private static final Bytes INSUFFICIENT_PAYER_BALANCE_REASON = Bytes.wrap(INSUFFICIENT_PAYER_BALANCE.name()); - private static final Bytes WRONG_NONCE_REASON = Bytes.wrap(WRONG_NONCE.name()); private static final Bytes CONTRACT_EXECUTION_EXCEPTION_REASON = Bytes.wrap(CONTRACT_EXECUTION_EXCEPTION.name()); /** @@ -136,7 +135,9 @@ public ContractFunctionResult asQueryResult() { * @return the status */ public ResponseCodeEnum finalStatus() { - if (haltReason != null) { + if (finalStatus != null) { + return finalStatus; + } else if (haltReason != null) { return CustomExceptionalHaltReason.statusFor(haltReason); } else if (revertReason != null) { if (revertReason.equals(MAX_STORAGE_EXCEEDED_REASON)) { @@ -151,8 +152,6 @@ public ResponseCodeEnum finalStatus() { return MAX_CHILD_RECORDS_EXCEEDED; } else if (revertReason.equals(INSUFFICIENT_TX_FEE_REASON)) { return INSUFFICIENT_TX_FEE; - } else if (revertReason.equals(WRONG_NONCE_REASON)) { - return WRONG_NONCE; } else if (revertReason.equals(INSUFFICIENT_PAYER_BALANCE_REASON)) { return INSUFFICIENT_PAYER_BALANCE; } else if (revertReason.equals(CONTRACT_EXECUTION_EXCEPTION_REASON)) { @@ -208,7 +207,8 @@ public static HederaEvmTransactionResult successFrom( null, null, requireNonNull(logs), - stateChanges); + stateChanges, + null); } /** @@ -234,7 +234,8 @@ public static HederaEvmTransactionResult failureFrom( frame.getExceptionalHaltReason().orElse(null), frame.getRevertReason().map(ConversionUtils::tuweniToPbjBytes).orElse(null), Collections.emptyList(), - stateReadsFrom(frame)); + stateReadsFrom(frame), + null); } /** @@ -261,6 +262,7 @@ public static HederaEvmTransactionResult resourceExhaustionFrom( null, Bytes.wrap(reason.name()), Collections.emptyList(), + null, null); } @@ -289,7 +291,8 @@ public static HederaEvmTransactionResult fromAborted( null, Bytes.wrap(reason.name().getBytes()), List.of(), - null); + null, + reason); } private ContractFunctionResult withMaybeEthFields( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java index 3975d72f47b5..782f78f5a294 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java @@ -18,6 +18,7 @@ import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -56,6 +57,13 @@ public interface ContractCallRecordBuilder extends GasFeeRecordBuilder { @NonNull ContractCallRecordBuilder contractID(@Nullable ContractID contractId); + /** + * Returns the token id created. + * + * @return the token id created + */ + TokenID tokenID(); + /** * Tracks the result of a top-level contract call. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java index fc7317d1f91e..b49c31886ca5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java @@ -501,6 +501,7 @@ public class TestHelpers { INVALID_SIGNATURE, null, Collections.emptyList(), + null, null); public static final StorageAccesses ONE_STORAGE_ACCESSES = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java index 88c0f949d5ed..6194846cca23 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java @@ -17,10 +17,12 @@ package com.hedera.node.app.service.contract.impl.test.exec.gas; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.CanonicalDispatchPrices; +import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import java.util.function.ToLongBiFunction; @@ -39,17 +41,37 @@ class SystemContractGasCalculatorTest { private ToLongBiFunction feeCalculator; @Mock - private CanonicalDispatchPrices canonicalDispatchPrices; + private CanonicalDispatchPrices dispatchPrices; private SystemContractGasCalculator subject; @BeforeEach void setUp() { - subject = new SystemContractGasCalculator(tinybarValues, canonicalDispatchPrices, feeCalculator); + subject = new SystemContractGasCalculator(tinybarValues, dispatchPrices, feeCalculator); } @Test void returnsMinimumGasCostForViews() { assertEquals(100L, subject.viewGasRequirement()); } + + @Test + void computesCanonicalDispatchType() { + given(dispatchPrices.canonicalPriceInTinycents(DispatchType.APPROVE)).willReturn(123L); + given(tinybarValues.asTinybars(123L)).willReturn(321L); + assertEquals(321L, subject.canonicalPriceInTinybars(DispatchType.APPROVE)); + } + + @Test + void computesCanonicalDispatch() { + given(feeCalculator.applyAsLong(TransactionBody.DEFAULT, AccountID.DEFAULT)) + .willReturn(123L); + assertEquals(123L, subject.canonicalPriceInTinybars(TransactionBody.DEFAULT, AccountID.DEFAULT)); + } + + @Test + void computesGasCostInTinybars() { + given(tinybarValues.childTransactionTinybarGasPrice()).willReturn(2L); + assertEquals(6L, subject.gasCostInTinybars(3L)); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java new file mode 100644 index 000000000000..b6ecbb3f872e --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/EitherOrVerificationStrategyTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.scope; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; + +import com.hedera.hapi.node.base.Key; +import com.hedera.node.app.service.contract.impl.exec.scope.EitherOrVerificationStrategy; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class EitherOrVerificationStrategyTest { + @Mock + private VerificationStrategy firstStrategy; + + @Mock + private VerificationStrategy secondStrategy; + + private EitherOrVerificationStrategy subject; + + @BeforeEach + void setUp() { + subject = new EitherOrVerificationStrategy(firstStrategy, secondStrategy); + } + + @Test + void firstStrategyValidSuffices() { + given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.VALID); + assertSame(VerificationStrategy.Decision.VALID, subject.decideForPrimitive(Key.DEFAULT)); + } + + @Test + void secondStrategyValidSuffices() { + given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + given(secondStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.VALID); + assertSame(VerificationStrategy.Decision.VALID, subject.decideForPrimitive(Key.DEFAULT)); + } + + @Test + void oneStrategyMustBeValid() { + given(firstStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + given(secondStrategy.decideForPrimitive(Key.DEFAULT)).willReturn(VerificationStrategy.Decision.INVALID); + assertSame(VerificationStrategy.Decision.INVALID, subject.decideForPrimitive(Key.DEFAULT)); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java index 3214844dcd31..cfeed4f8f220 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/HtsSystemContractTest.java @@ -24,7 +24,6 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.assertSamePrecompileResult; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; @@ -122,18 +121,6 @@ void internalErrorAttemptHaltsAndConsumesRemainingGas() { assertSamePrecompileResult(expected, result); } - @Test - void callWithNonGasCostNotImplemented() { - commonMocks(); - - givenValidCallAttempt(); - final var pricedResult = - new HtsCall.PricedResult(successResult(ByteBuffer.allocate(1), 123L), 456L, SUCCESS, true); - given(call.execute(frame)).willReturn(pricedResult); - - assertThrows(AssertionError.class, () -> subject.computeFully(Bytes.EMPTY, frame)); - } - private void givenValidCallAttempt() { frameUtils.when(() -> isDelegateCall(frame)).thenReturn(false); frameUtils.when(() -> proxyUpdaterFor(frame)).thenReturn(updater); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java index 06564ce257a8..d205fb537101 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java @@ -16,23 +16,28 @@ package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.create; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ADDRESS; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ALIASED_SOMEBODY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; -import static com.hedera.node.app.service.contract.impl.test.TestHelpers.asBytesResult; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.asHeadlongAddress; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.lenient; -import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.esaulpaugh.headlong.abi.Address; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AddressIdConverter; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create.ClassicCreatesCall; @@ -40,6 +45,8 @@ import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hts.HtsCallTestBase; import java.math.BigInteger; +import java.util.ArrayDeque; +import java.util.Deque; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -55,9 +62,6 @@ public class ClassicCreatesCallTest extends HtsCallTestBase { @Mock private AddressIdConverter addressIdConverter; - @Mock - private SystemContractGasCalculator systemContractGasCalculator; - @Mock private ContractCallRecordBuilder recordBuilder; @@ -71,6 +75,11 @@ public class ClassicCreatesCallTest extends HtsCallTestBase { private ClassicCreatesCall subject; + private final Deque stack = new ArrayDeque<>(); + + private final Address tokenId = + Address.wrap(Address.toChecksumAddress(BigInteger.valueOf(FUNGIBLE_TOKEN_ID.tokenNum()))); + @Test void createFungibleTokenHappyPathV1() { commonGivens(); @@ -80,9 +89,10 @@ void createFungibleTokenHappyPathV1() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -95,9 +105,10 @@ void createFungibleTokenHappyPathV2() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V2 + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V2 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -110,9 +121,10 @@ void createFungibleTokenHappyPathV3() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V3 + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_TOKEN_V3 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -125,9 +137,10 @@ void createFungibleTokenWithCustomFeesHappyPathV1() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V1 + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -140,9 +153,10 @@ void createFungibleTokenWithCustomFeesHappyPathV2() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V2 + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V2 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -155,9 +169,10 @@ void createFungibleTokenWithCustomFeesHappyPathV3() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V3 + Bytes.wrap(CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V3 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -170,9 +185,10 @@ void createNonFungibleTokenHappyPathV1() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -185,9 +201,10 @@ void createNonFungibleTokenHappyPathV2() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V2 + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V2 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -200,9 +217,10 @@ void createNonFungibleTokenHappyPathV3() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V3 + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V3 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -215,9 +233,10 @@ void createNonFungibleTokenWithCustomFeesHappyPathV1() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -230,9 +249,10 @@ void createNonFungibleTokenWithCustomFeesHappyPathV2() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V2 + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V2 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), result.getOutput()); } @@ -245,9 +265,28 @@ void createNonFungibleTokenWithCustomFeesHappyPathV3() { assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - asBytesResult(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V3 + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V3 + .getOutputs() + .encodeElements((long) SUCCESS.protoOrdinal(), tokenId) + .array()), + result.getOutput()); + } + + @Test + void requiresNonGasCostToBeProvidedAsValue() { + commonGivens(200_000L, 99_999L, true); + given(recordBuilder.status()).willReturn(SUCCESS); + given(systemContractOperations.externalizePreemptedDispatch(any(), eq(INSUFFICIENT_TX_FEE))) + .willReturn(recordBuilder); + + final var result = subject.execute(frame).fullResult().result(); + + assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + Bytes.wrap(CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V3 .getOutputs() - .encodeElements(BigInteger.valueOf(ResponseCodeEnum.SUCCESS.protoOrdinal()))), + .encodeElements((long) INSUFFICIENT_TX_FEE.protoOrdinal(), ZERO_ADDRESS) + .array()), result.getOutput()); } @@ -263,24 +302,38 @@ void createFungibleTokenUnhappyPathRevertsWithReason() { } private void commonGivens() { - given(frame.getValue()).willReturn(Wei.ZERO); + commonGivens(0L, 0L, false); + } + + private void commonGivens(long baseCost, long value, boolean shouldBePreempted) { + given(frame.getValue()).willReturn(Wei.of(value)); + given(gasCalculator.canonicalPriceInTinybars(any(), any())).willReturn(baseCost); + System.out.println(gasCalculator.canonicalPriceInTinybars(TransactionBody.DEFAULT, AccountID.DEFAULT)); + stack.push(frame); given(addressIdConverter.convert(asHeadlongAddress(FRAME_SENDER_ADDRESS))) .willReturn(A_NEW_ACCOUNT_ID); - given(nativeOperations.getAccount(A_NEW_ACCOUNT_ID.accountNumOrThrow())).willReturn(ALIASED_SOMEBODY); + + if (!shouldBePreempted) { + given(frame.getMessageFrameStack()).willReturn(stack); + given(frame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(DEFAULT_CONFIG); + given(nativeOperations.getAccount(A_NEW_ACCOUNT_ID.accountNumOrThrow())) + .willReturn(ALIASED_SOMEBODY); + given(systemContractOperations.dispatch( + any(TransactionBody.class), + eq(verificationStrategy), + eq(A_NEW_ACCOUNT_ID), + eq(ContractCallRecordBuilder.class))) + .willReturn(recordBuilder); + } subject = new ClassicCreatesCall( - systemContractGasCalculator, + gasCalculator, mockEnhancement(), PRETEND_CREATE_TOKEN, verificationStrategy, FRAME_SENDER_ADDRESS, addressIdConverter); - given(systemContractOperations.dispatch( - any(TransactionBody.class), - eq(verificationStrategy), - eq(A_NEW_ACCOUNT_ID), - eq(ContractCallRecordBuilder.class))) - .willReturn(recordBuilder); + lenient().when(recordBuilder.tokenID()).thenReturn(FUNGIBLE_TOKEN_ID); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java index e8671431254e..b5af60a93ce9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java @@ -20,6 +20,7 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TRACKER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.stackIncludesActiveAddress; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; @@ -95,6 +96,31 @@ void initialFrameIsNotDelegated() { assertFalse(FrameUtils.acquiredSenderAuthorizationViaDelegateCall(initialFrame)); } + @Test + void singleFrameStackHasNoActiveAddress() { + stack.add(frame); + given(frame.getMessageFrameStack()).willReturn(stack); + assertFalse(stackIncludesActiveAddress(frame, EIP_1014_ADDRESS)); + } + + @Test + void detectsTargetAddressInTwoFrameStack() { + stack.push(initialFrame); + stack.add(frame); + given(frame.getRecipientAddress()).willReturn(EIP_1014_ADDRESS); + given(frame.getMessageFrameStack()).willReturn(stack); + assertTrue(stackIncludesActiveAddress(frame, EIP_1014_ADDRESS)); + } + + @Test + void detectsLackOfTargetAddressInTwoFrameStack() { + stack.push(initialFrame); + stack.add(frame); + given(frame.getRecipientAddress()).willReturn(NON_SYSTEM_LONG_ZERO_ADDRESS); + given(frame.getMessageFrameStack()).willReturn(stack); + assertFalse(stackIncludesActiveAddress(frame, EIP_1014_ADDRESS)); + } + @Test void onlyExecutingFrameCanBeEvaluatedForDelegateSenderAuthorization() { stack.push(frame); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java index f6effcc274ab..b6594bfa4bbc 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java @@ -19,6 +19,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_EXECUTION_EXCEPTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.OBTAINER_SAME_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; @@ -127,6 +128,12 @@ void finalStatusFromWrongNonceAbortTranslated() { assertEquals(WRONG_NONCE, subject.finalStatus()); } + @Test + void finalStatusFromInvalidAccountIdAbortTranslated() { + final var subject = HederaEvmTransactionResult.fromAborted(SENDER_ID, wellKnownHapiCall(), INVALID_ACCOUNT_ID); + assertEquals(INVALID_ACCOUNT_ID, subject.finalStatus()); + } + @Test void finalStatusFromExecutionExceptionAbortTranslated() { final var subject = diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java index 32c93c14303a..fd9621390872 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/records/TokenCreateRecordBuilder.java @@ -19,13 +19,14 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.TokenAssociation; import com.hedera.hapi.node.base.TokenID; +import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; /** * A {@code RecordBuilder} specialization for tracking the side effects of a {@code TokenCreate} * transaction. */ -public interface TokenCreateRecordBuilder { +public interface TokenCreateRecordBuilder extends SingleTransactionRecordBuilder { /** * Tracks creation of a new token by number. Even if someday we support creating multiple * tokens within a smart contract call, we will still only need to track one created token @@ -37,6 +38,8 @@ public interface TokenCreateRecordBuilder { @NonNull TokenCreateRecordBuilder tokenID(@NonNull TokenID tokenID); + TokenID tokenID(); + /** * Adds the token relations that are created by auto associations. * This information is needed while setting record cache. diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java index 58d2bac204b8..c339d903111d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/SigningReqsSuite.java @@ -29,6 +29,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE; import com.esaulpaugh.headlong.abi.Address; +import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.*; import com.hedera.services.bdd.spec.assertions.*; @@ -73,6 +74,7 @@ public List getSpecsInSuite() { return List.of(autoRenewAccountCanUseLegacySigActivationIfConfigured()); } + @HapiTest final HapiSpec autoRenewAccountCanUseLegacySigActivationIfConfigured() { final var autoRenew = AUTO_RENEW; final AtomicReference

    autoRenewMirrorAddr = new AtomicReference<>(); @@ -115,7 +117,7 @@ final HapiSpec autoRenewAccountCanUseLegacySigActivationIfConfigured() { final var propertyUpdate = overriding(LEGACY_ACTIVATIONS_PROP, overrideValue); CustomSpecAssert.allRunFor(spec, propertyUpdate); }), - // Succeeds with the full-prefix signature + // Succeeds now because the called contract received legacy activation privilege sourcing(() -> contractCall( MINIMAL_CREATIONS_CONTRACT, "makeRenewableTokenIndirectly", diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java index 46b07f90d5e8..7f827eed0370 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java @@ -138,8 +138,11 @@ HapiSpec canonicalScheduleOpsHaveExpectedUsdFees() { validateChargedUsdWithin("canonicalDeletion", 0.001, 3.0)); } + @HapiTest HapiSpec miscContractCreatesAndCalls() { - Object[] donationArgs = new Object[] {2, "Hey, Ma!"}; + // Note that contracts are prohibited to sending value to system + // accounts below 0.0.750 + Object[] donationArgs = new Object[] {800L, "Hey, Ma!"}; final var multipurposeContract = "Multipurpose"; final var lookupContract = "BalanceLookup"; @@ -154,18 +157,20 @@ HapiSpec miscContractCreatesAndCalls() { .balance(652), contractCreate(lookupContract).payingWith(CIVILIAN).balance(256)) .then( - contractCall(multipurposeContract, "believeIn", 256).payingWith(CIVILIAN), + contractCall(multipurposeContract, "believeIn", 256L).payingWith(CIVILIAN), contractCallLocal(multipurposeContract, "pick") .payingWith(CIVILIAN) .logged() .has(resultWith() .resultThruAbi( getABIFor(FUNCTION, "pick", multipurposeContract), - isLiteralResult(new Object[] {BigInteger.valueOf(256)}))), + isLiteralResult(new Object[] {256L}))), contractCall(multipurposeContract, "donate", donationArgs) .payingWith(CIVILIAN), contractCallLocal(lookupContract, "lookup", spec -> new Object[] { - spec.registry().getAccountID(CIVILIAN).getAccountNum() + BigInteger.valueOf(spec.registry() + .getAccountID(CIVILIAN) + .getAccountNum()) }) .payingWith(CIVILIAN) .logged()); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java index 9b0cb0c51a76..bd5b5ecdfbc4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/ProtectedFilesUpdateSuite.java @@ -109,6 +109,7 @@ final HapiSpec specialAccountCanUpdateSpecialPropertyFile( return specialAccountCanUpdateSpecialPropertyFile(specialAccount, specialFile, property, expected, true); } + @BddMethodIsNotATest final HapiSpec specialAccountCanUpdateSpecialPropertyFile( final String specialAccount, final String specialFile, @@ -148,6 +149,7 @@ final HapiSpec specialAccountCanUpdateSpecialFile( return specialAccountCanUpdateSpecialFile(specialAccount, specialFile, target, replacement, true); } + @BddMethodIsNotATest final HapiSpec specialAccountCanUpdateSpecialFile( final String specialAccount, final String specialFile, @@ -163,6 +165,7 @@ final HapiSpec specialAccountCanUpdateSpecialFile( : (new String(contents).replace(target, replacement)).getBytes()); } + @BddMethodIsNotATest final HapiSpec specialAccountCanUpdateSpecialFile( final String specialAccount, final String specialFile, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 198f07ee7065..3ff6d0f12a6d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -1260,6 +1260,7 @@ final HapiSpec fungibleTokenCreateWithFeesHappyPath() { })); } + @HapiTest final HapiSpec etx026AccountWithoutAliasCanMakeEthTxnsDueToAutomaticAliasCreation() { final String ACCOUNT = "account"; return propertyPreservingHapiSpec("etx026AccountWithoutAliasCanMakeEthTxnsDueToAutomaticAliasCreation") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 8288c0e96c98..6cb50bc55f12 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -155,6 +155,7 @@ import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.TxnVerbs; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; @@ -314,7 +315,7 @@ final HapiSpec getsInsufficientPayerBalanceIfSendingAccountCanPayEverythingButSe .hasKnownStatusFrom(INSUFFICIENT_PAYER_BALANCE, INSUFFICIENT_ACCOUNT_BALANCE))); } - // Cannot be enabled as HapiTest because long term schedule transactions is not implemented in mod service + @BddMethodIsNotATest final HapiSpec scheduledCryptoApproveAllowanceWaitForExpiryTrue() { return defaultHapiSpec("ScheduledCryptoApproveAllowanceWaitForExpiryTrue") .given( @@ -442,7 +443,7 @@ final HapiSpec txnsUsingHip583FunctionalitiesAreNotAcceptedWhenFlagsAreDisabled( .then(); } - // Cannot be enabled as HapiTest because tokens.maxPerAccount is not being used in mod service + @BddMethodIsNotATest final HapiSpec maxAutoAssociationSpec() { final int MONOGAMOUS_NETWORK = 1; final int maxAutoAssociations = 100; @@ -466,7 +467,7 @@ final HapiSpec maxAutoAssociationSpec() { overriding("tokens.maxPerAccount", "" + ADVENTUROUS_NETWORK)); } - // Cannot be enabled as HapiTest because expiration is not implemented in mod service + @BddMethodIsNotATest public HapiSpec canDissociateFromMultipleExpiredTokens() { final var civilian = "civilian"; final long initialSupply = 100L; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java index 637e93578efc..699023d9ba2e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/misc/CannotDeleteSystemEntitiesSuite.java @@ -182,6 +182,7 @@ final HapiSpec systemUserCannotDeleteSystemAccounts(int firstAccount, int lastAc .toArray(HapiSpecOperation[]::new))); } + @BddMethodIsNotATest final HapiSpec normalUserCannotDeleteSystemAccounts(int firstAccount, int lastAccount) { return defaultHapiSpec("normalUserCannotDeleteSystemAccounts") .given(newKeyNamed("normalKey"), cryptoCreate("unluckyReceiver").balance(0L)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java index 61f1d30b99df..52938fd6ffef 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/RecordCreationSuite.java @@ -146,13 +146,18 @@ final HapiSpec submittingNodeStillPaidIfServiceFeesOmitted() { .logged())); } + @HapiTest final HapiSpec submittingNodeChargedNetworkFeeForLackOfDueDiligence() { final String comfortingMemo = THIS_IS_OK_IT_S_FINE_IT_S_WHATEVER; final String disquietingMemo = "\u0000his is ok, it's fine, it's whatever."; final AtomicReference feeObs = new AtomicReference<>(); - return defaultHapiSpec("SubmittingNodeChargedNetworkFeeForLackOfDueDiligence") + return propertyPreservingHapiSpec("SubmittingNodeChargedNetworkFeeForLackOfDueDiligence") + .preserving(STAKING_FEES_NODE_REWARD_PERCENTAGE, STAKING_FEES_STAKING_REWARD_PERCENTAGE) .given( + overridingTwo( + STAKING_FEES_NODE_REWARD_PERCENTAGE, "10", + STAKING_FEES_STAKING_REWARD_PERCENTAGE, "10"), cryptoTransfer(tinyBarsFromTo(GENESIS, TO_ACCOUNT, ONE_HBAR)) .payingWith(GENESIS), cryptoCreate(PAYER), From 6ae3b0c402b39f77068f2fb6744d5916dd2c652a Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Wed, 27 Dec 2023 04:16:48 -0600 Subject: [PATCH 47/80] fix: bind prehandle signature transactions to correct handle (#10649) Signed-off-by: Cody Littley --- .../wiring/components/StateSignatureCollectorWiring.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java index d4f2c92e6b65..055089c5e99b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateSignatureCollectorWiring.java @@ -99,7 +99,7 @@ public void bind(@NonNull final SignedStateManager signedStateManager) { Objects.requireNonNull(signedStateManager); stateSignatureTransactionInput.bind(scopedTransaction -> { - signedStateManager.handlePostconsensusSignatureTransaction( + signedStateManager.handlePreconsensusSignatureTransaction( scopedTransaction.submitterId(), scopedTransaction.transaction()); }); } From fb4c5934912d4bd1962b5879a4906fb45a2e4c42 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Wed, 27 Dec 2023 08:12:29 -0600 Subject: [PATCH 48/80] fix: rate limit spammy log (#10646) Signed-off-by: Cody Littley --- .../common/stream/EventStreamManager.java | 17 +++++++++++++++-- .../common/stream/EventStreamManagerTest.java | 7 +++++-- .../com/swirlds/platform/SwirldsPlatform.java | 1 + .../platform/recovery/RecoveryTestUtils.java | 2 ++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/EventStreamManager.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/EventStreamManager.java index b9511cdd8799..3162aa4ab908 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/EventStreamManager.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/stream/EventStreamManager.java @@ -20,6 +20,7 @@ import static com.swirlds.common.metrics.Metrics.INFO_CATEGORY; import static com.swirlds.logging.legacy.LogMarker.EVENT_STREAM; +import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.DigestType; import com.swirlds.common.crypto.Hash; @@ -30,10 +31,12 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.stream.internal.TimestampStreamFileWriter; import com.swirlds.common.threading.manager.ThreadManager; +import com.swirlds.common.utility.throttle.RateLimitedLogger; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.time.Duration; import java.util.List; import java.util.function.Predicate; import org.apache.logging.log4j.LogManager; @@ -76,8 +79,11 @@ public class EventStreamManager isLastEventInFreezeCheck) { + eventAfterFreezeLogger = new RateLimitedLogger(logger, time, Duration.ofMinutes(1)); + if (enableEventStreaming) { // the directory to which event stream files are written final String eventStreamDir = eventsLogDir + "/events_" + nodeName; @@ -162,11 +171,14 @@ public EventStreamManager( } /** + * @param time provides wall clock time * @param multiStream the instance which receives consensus events from ConsensusRoundHandler, then * passes to nextStreams * @param isLastEventInFreezeCheck a predicate which checks whether this event is the last event before restart */ - public EventStreamManager(final MultiStream multiStream, final Predicate isLastEventInFreezeCheck) { + public EventStreamManager( + @NonNull final Time time, final MultiStream multiStream, final Predicate isLastEventInFreezeCheck) { + eventAfterFreezeLogger = new RateLimitedLogger(logger, time, Duration.ofMinutes(1)); this.multiStream = multiStream; multiStream.setRunningHash(initialHash); this.isLastEventInFreezeCheck = isLastEventInFreezeCheck; @@ -207,7 +219,8 @@ public void addEvent(final T event) { multiStream.close(); } } else { - logger.warn(EVENT_STREAM.getMarker(), "Event {} dropped after freezePeriodStarted!", event.getTimestamp()); + eventAfterFreezeLogger.warn( + EVENT_STREAM.getMarker(), "Event {} dropped after freezePeriodStarted!", event.getTimestamp()); } } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/stream/EventStreamManagerTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/stream/EventStreamManagerTest.java index 2c6c03ede980..7519d4f90100 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/stream/EventStreamManagerTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/stream/EventStreamManagerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import com.swirlds.base.time.Time; import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; @@ -54,7 +55,7 @@ class EventStreamManagerTest { private static final MultiStream multiStreamMock = mock(MultiStream.class); private static final EventStreamManager EVENT_STREAM_MANAGER = - new EventStreamManager<>(multiStreamMock, EventStreamManagerTest::isFreezeEvent); + new EventStreamManager<>(Time.getCurrent(), multiStreamMock, EventStreamManagerTest::isFreezeEvent); private static final ObjectForTestStream freezeEvent = mock(ObjectForTestStream.class); @@ -62,6 +63,7 @@ class EventStreamManagerTest { static void init() throws Exception { disableStreamingInstance = new EventStreamManager<>( TestPlatformContextBuilder.create().build(), + Time.getCurrent(), getStaticThreadManager(), selfId, mock(Signer.class), @@ -74,6 +76,7 @@ static void init() throws Exception { enableStreamingInstance = new EventStreamManager<>( TestPlatformContextBuilder.create().build(), + Time.getCurrent(), getStaticThreadManager(), selfId, mock(Signer.class), @@ -114,7 +117,7 @@ void setInitialHashTest() { @Test void addEventTest() throws InterruptedException { EventStreamManager eventStreamManager = - new EventStreamManager<>(multiStreamMock, EventStreamManagerTest::isFreezeEvent); + new EventStreamManager<>(Time.getCurrent(), multiStreamMock, EventStreamManagerTest::isFreezeEvent); assertFalse( eventStreamManager.getFreezePeriodStarted(), "freezePeriodStarted should be false after initialization"); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 9474a1fbf977..79666efb749c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -620,6 +620,7 @@ public class SwirldsPlatform implements Platform { final EventStreamManager eventStreamManager = new EventStreamManager<>( platformContext, + time, threadManager, getSelfId(), this, diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java index 1c28666a8d30..044ee47aaff5 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/RecoveryTestUtils.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import com.swirlds.base.test.fixtures.time.FakeTime; +import com.swirlds.base.time.Time; import com.swirlds.common.io.IOIterator; import com.swirlds.common.io.SelfSerializable; import com.swirlds.common.io.extendable.ExtendableInputStream; @@ -193,6 +194,7 @@ public static void writeRandomEventStream( final EventStreamManager eventEventStreamManager = new EventStreamManager<>( TestPlatformContextBuilder.create().build(), + Time.getCurrent(), getStaticThreadManager(), new NodeId(0L), x -> randomSignature(random), From 9f7c74c4d78f104e5eadddd995ec683bbb44e537 Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Wed, 27 Dec 2023 10:41:54 -0600 Subject: [PATCH 49/80] chore: create new production-next docker image definitions (#10652) Signed-off-by: Nathan Klick --- .../zxc-verify-gradle-build-determinism.yaml | 2 +- .../main-network-node/Dockerfile | 227 ++++++++++++++++++ .../main-network-node/entrypoint.sh | 73 ++++++ .../main-network-node/repro-sources-list.sh | 99 ++++++++ 4 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile create mode 100644 hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh create mode 100755 hedera-node/infrastructure/docker/containers/production-next/main-network-node/repro-sources-list.sh diff --git a/.github/workflows/zxc-verify-gradle-build-determinism.yaml b/.github/workflows/zxc-verify-gradle-build-determinism.yaml index e1fc0df5f1d0..d1f30f51be2e 100644 --- a/.github/workflows/zxc-verify-gradle-build-determinism.yaml +++ b/.github/workflows/zxc-verify-gradle-build-determinism.yaml @@ -147,7 +147,7 @@ jobs: - [self-hosted, Linux, medium, ephemeral] - [self-hosted, Linux, large, ephemeral] steps: - - name: Set git to use LF + - name: Standardize Git Line Endings run: | git config --global core.autocrlf false git config --global core.eol lf diff --git a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile new file mode 100644 index 000000000000..0388b161813b --- /dev/null +++ b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile @@ -0,0 +1,227 @@ +######################################################################################################################## +# +# Define Global Build Arguments +# +######################################################################################################################## +ARG UBUNTU_TAG="lunar-20231128" +ARG SOURCE_DATE_EPOCH="0" + +######################################################################################################################## +# +# Setup Ephemeral Java Downloader Layer +# +######################################################################################################################## +FROM ubuntu:${UBUNTU_TAG} AS java-builder-interim +# Define Build Arguments +ARG SOURCE_DATE_EPOCH + +# Define Standard Environment Variables +ENV DEBIAN_FRONTEND noninteractive +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 + +# Install basic OS utilities for building +RUN --mount=type=bind,source=./repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ + repro-sources-list.sh && \ + apt-get update && \ + apt-get install --yes --no-install-recommends tar gzip curl ca-certificates && \ + apt-get autoclean --yes && \ + apt-get clean all --yes && \ + rm -rf /var/log/ && \ + rm -rf /var/cache/ + +########################## +#### Java Setup #### +########################## +RUN set -eux; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + ESUM='e184dc29a6712c1f78754ab36fb48866583665fa345324f1a79e569c064f95e9'; \ + BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.1%2B12/OpenJDK21U-jdk_aarch64_linux_hotspot_21.0.1_12.tar.gz'; \ + ;; \ + amd64|i386:x86-64) \ + ESUM='1a6fa8abda4c5caed915cfbeeb176e7fbd12eb6b222f26e290ee45808b529aa1'; \ + BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.1%2B12/OpenJDK21U-jdk_x64_linux_hotspot_21.0.1_12.tar.gz'; \ + ;; \ + ppc64el|powerpc:common64) \ + ESUM='9574828ef3d735a25404ced82e09bf20e1614f7d6403956002de9cfbfcb8638f'; \ + BINARY_URL='https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.1%2B12/OpenJDK21U-jdk_ppc64le_linux_hotspot_21.0.1_12.tar.gz'; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + curl -LfsSo /tmp/openjdk.tar.gz ${BINARY_URL}; \ + echo "${ESUM} */tmp/openjdk.tar.gz" | sha256sum -c -; \ + mkdir -p /usr/local/java; \ + tar --extract \ + --file /tmp/openjdk.tar.gz \ + --directory "/usr/local/java" \ + --strip-components 1 \ + --no-same-owner \ + ; \ + rm -f /tmp/openjdk.tar.gz /usr/local/java/lib/src.zip; + +######################################## +#### Deterministic Build Hack #### +######################################## + +# === Workarounds below will not be needed when https://github.com/moby/buildkit/pull/4057 is merged === +# NOTE: PR #4057 has been merged but will not be available until the v0.13.x series of releases. +# Limit the timestamp upper bound to SOURCE_DATE_EPOCH. +# Workaround for https://github.com/moby/buildkit/issues/3180 +RUN find $( ls / | grep -E -v "^(dev|mnt|proc|sys)$" ) \ + -newermt "@${SOURCE_DATE_EPOCH}" -writable -xdev \ + | xargs touch --date="@${SOURCE_DATE_EPOCH}" --no-dereference + +FROM scratch AS java-builder +COPY --from=java-builder-interim / / + +######################################################################################################################## +# +# Setup OS Base Layer +# +######################################################################################################################## +FROM ubuntu:${UBUNTU_TAG} AS operating-system-base-interim +# Define Build Arguments +ARG SOURCE_DATE_EPOCH + +# Define Standard Environment Variables +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 +ENV DEBIAN_FRONTEND noninteractive + +# Install basic OS utilities +RUN --mount=type=bind,source=./repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ + repro-sources-list.sh && \ + apt-get update && \ + apt-get install -y tar gzip openssl zlib1g libsodium23 libreadline8 sudo netcat-traditional net-tools && \ + apt-get autoclean --yes && \ + apt-get clean all --yes && \ + rm -rf /var/log/ && \ + rm -rf /var/cache/ + +# Create Application Folders +RUN mkdir -p "/opt/hgcapp" && \ + mkdir -p "/opt/hgcapp/accountBalances" && \ + mkdir -p "/opt/hgcapp/eventsStreams" && \ + mkdir -p "/opt/hgcapp/recordStreams" && \ + mkdir -p "/opt/hgcapp/services-hedera" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/apps" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/config" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/diskFs" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/keys" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/lib" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/onboard" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/stats" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/saved" && \ + mkdir -p "/opt/hgcapp/services-hedera/HapiApp2.0/data/upgrade" + +# Configure the standard user account +RUN groupadd --gid 2000 hedera && \ + useradd --no-user-group --create-home --uid 2000 --gid 2000 --shell /bin/bash hedera && \ + chown -R hedera:hedera /opt/hgcapp + +# Configure SUDO support +RUN echo >> /etc/sudoers && \ + echo "%hedera ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +######################################## +#### Deterministic Build Hack #### +######################################## + +# === Workarounds below will not be needed when https://github.com/moby/buildkit/pull/4057 is merged === +# NOTE: PR #4057 has been merged but will not be available until the v0.13.x series of releases. +# Limit the timestamp upper bound to SOURCE_DATE_EPOCH. +# Workaround for https://github.com/moby/buildkit/issues/3180 +RUN find $( ls / | grep -E -v "^(dev|mnt|proc|sys)$" ) \ + -newermt "@${SOURCE_DATE_EPOCH}" -writable -xdev \ + | xargs touch --date="@${SOURCE_DATE_EPOCH}" --no-dereference + +FROM scratch AS operating-system-base +COPY --from=operating-system-base-interim / / + +######################################################################################################################## +# +# Setup Production Container Image +# +######################################################################################################################## +FROM operating-system-base AS production-image-interim +# Define Build Arguments +ARG SOURCE_DATE_EPOCH + +# Define Standard Environment Variables +ENV JAVA_VERSION "jdk-21.0.1+12" +ENV JAVA_HOME /usr/local/java +ENV PATH ${JAVA_HOME}/bin:${PATH} + +# Install Java +COPY --from=java-builder ${JAVA_HOME}/ ${JAVA_HOME}/ + +# Add SDK components +COPY sdk/data/apps/* /opt/hgcapp/services-hedera/HapiApp2.0/data/apps/ +COPY sdk/data/lib/* /opt/hgcapp/services-hedera/HapiApp2.0/data/lib/ + +# Add the entrypoint script +ADD entrypoint.sh /opt/hgcapp/services-hedera/HapiApp2.0/ + +# Ensure proper file permissions +RUN chmod -R +x /opt/hgcapp/services-hedera/HapiApp2.0/entrypoint.sh && \ + chown -R 2000:2000 /opt/hgcapp/services-hedera/HapiApp2.0 + +######################################## +#### Deterministic Build Hack #### +######################################## + +# === Workarounds below will not be needed when https://github.com/moby/buildkit/pull/4057 is merged === +# NOTE: PR #4057 has been merged but will not be available until the v0.13.x series of releases. +# Limit the timestamp upper bound to SOURCE_DATE_EPOCH. +# Workaround for https://github.com/moby/buildkit/issues/3180 +RUN find $( ls / | grep -E -v "^(dev|mnt|proc|sys)$" ) \ + -newermt "@${SOURCE_DATE_EPOCH}" -writable -xdev \ + | xargs touch --date="@${SOURCE_DATE_EPOCH}" --no-dereference + +FROM scratch AS production-image +COPY --from=production-image-interim / / + +# Define Standard Environment Variables +ENV JAVA_VERSION "jdk-21.0.1+12" +ENV JAVA_HOME /usr/local/java +ENV PATH ${JAVA_HOME}/bin:${PATH} + +# Define Application Specific Variables +ENV JAVA_HEAP_MIN "" +ENV JAVA_HEAP_MAX "" +ENV JAVA_OPTS "" +ENV JAVA_MAIN_CLASS "" +ENV JAVA_CLASS_PATH "" + +# Performance Tuning for Malloc +ENV MALLOC_ARENA_MAX 4 + +# Log Folder Name Override +ENV LOG_DIR_NAME "" + +# Define Volume Bindpoints +VOLUME "/opt/hgcapp/accountBalances" +VOLUME "/opt/hgcapp/eventsStreams" +VOLUME "/opt/hgcapp/recordStreams" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/config" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/diskFs" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/keys" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/onboard" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/stats" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/saved" +VOLUME "/opt/hgcapp/services-hedera/HapiApp2.0/data/upgrade" + +# Expose TCP/UDP Port Definitions +EXPOSE 50111/tcp 50211/tcp 50212/tcp + +# Set Final Working Directory, User, and Entrypoint +USER 2000 +WORKDIR "/opt/hgcapp" +ENTRYPOINT ["/opt/hgcapp/services-hedera/HapiApp2.0/entrypoint.sh"] diff --git a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh new file mode 100644 index 000000000000..731ec150df18 --- /dev/null +++ b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +######################################################################################################################## +# Copyright 2016-2022 Hedera Hashgraph, LLC # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +######################################################################################################################## + +set -eo pipefail + +SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "${SCRIPT_PATH}" || exit 64 + +if [[ -z "${JAVA_OPTS}" ]]; then + JAVA_OPTS="" +fi + +# Setup Heap Options +JAVA_HEAP_OPTS="" + +if [[ -n "${JAVA_HEAP_MIN}" ]]; then + JAVA_HEAP_OPTS="${JAVA_HEAP_OPTS} -Xms${JAVA_HEAP_MIN}" +fi + +if [[ -n "${JAVA_HEAP_MAX}" ]]; then + JAVA_HEAP_OPTS="${JAVA_HEAP_OPTS} -Xmx${JAVA_HEAP_MAX}" +fi + +# Setup Main Class +[[ -z "${JAVA_MAIN_CLASS}" ]] && JAVA_MAIN_CLASS="com.swirlds.platform.Browser" + +# Setup Classpath +JCP_OVERRIDDEN="false" +if [[ -z "${JAVA_CLASS_PATH}" ]]; then + JAVA_CLASS_PATH="data/lib/*" +else + JCP_OVERRIDDEN="true" +fi + +if [[ "${JCP_OVERRIDDEN}" != true && "${JAVA_MAIN_CLASS}" != "com.swirlds.platform.Browser" ]]; then + JAVA_CLASS_PATH="${JAVA_CLASS_PATH}:data/apps/*" +fi + +# Override Log Directory Name (if provided) +LOG_DIR_NAME="${LOG_DIR_NAME:-output}" + +# Ensure the log directory exists +if [[ ! -d "${SCRIPT_PATH}/${LOG_DIR_NAME}" ]]; then + mkdir -p "${SCRIPT_PATH}/${LOG_DIR_NAME}" +fi + +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN USER IDENT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +id +echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END USER IDENT <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" +echo + +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN JAVA VERSION >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +/usr/bin/env java -version +echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END JAVA VERSION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" +echo + +/usr/bin/env java ${JAVA_HEAP_OPTS} ${JAVA_OPTS} -cp "${JAVA_CLASS_PATH}" "${JAVA_MAIN_CLASS}" +printf "java exit code %s" "${?}\n" diff --git a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/repro-sources-list.sh b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/repro-sources-list.sh new file mode 100755 index 000000000000..e822b7a9a1bf --- /dev/null +++ b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/repro-sources-list.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Copyright The repro-sources-list.sh Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ----------------------------------------------------------------------------- +# repro-sources-list.sh: +# configures /etc/apt/sources.list and similar files for installing packages from a snapshot. +# +# This script is expected to be executed inside Dockerfile. +# +# The following distributions are supported: +# - debian:11 (/etc/apt/sources.list) +# - debian:12 (/etc/apt/sources.list.d/debian.sources) +# - ubuntu:22.04 (/etc/apt/sources.list) +# - ubuntu:23.10 (/etc/apt/sources.list) +# - archlinux (/etc/pacman.d/mirrorlist) +# +# For the further information, see https://github.com/reproducible-containers/repro-sources-list.sh +# ----------------------------------------------------------------------------- + +set -eux -o pipefail + +. /etc/os-release + +keep_apt_cache() { + rm -f /etc/apt/apt.conf.d/docker-clean + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache +} + +case "${ID}" in +"debian") + # : "${SNAPSHOT_ARCHIVE_BASE:=http://snapshot.debian.org/archive/}" + : "${SNAPSHOT_ARCHIVE_BASE:=http://snapshot-cloudflare.debian.org/archive/}" + : "${BACKPORTS:=}" + case "${VERSION_ID}" in + "10" | "11") + : "${SOURCE_DATE_EPOCH:=$(stat --format=%Y /etc/apt/sources.list)}" + ;; + *) + : "${SOURCE_DATE_EPOCH:=$(stat --format=%Y /etc/apt/sources.list.d/debian.sources)}" + rm -f /etc/apt/sources.list.d/debian.sources + ;; + esac + snapshot="$(printf "%(%Y%m%dT%H%M%SZ)T\n" "${SOURCE_DATE_EPOCH}")" + # TODO: use the new format for Debian >= 12 + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}debian/${snapshot} ${VERSION_CODENAME} main" >/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}debian-security/${snapshot} ${VERSION_CODENAME}-security main" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}debian/${snapshot} ${VERSION_CODENAME}-updates main" >>/etc/apt/sources.list + if [ "${BACKPORTS}" = 1 ]; then echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}debian/${snapshot} ${VERSION_CODENAME}-backports main" >>/etc/apt/sources.list; fi + keep_apt_cache + ;; +"ubuntu") + : "${SNAPSHOT_ARCHIVE_BASE:=http://snapshot.ubuntu.com/}" + : "${SOURCE_DATE_EPOCH:=$(stat --format=%Y /etc/apt/sources.list)}" + snapshot="$(printf "%(%Y%m%dT%H%M%SZ)T\n" "${SOURCE_DATE_EPOCH}")" + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME} main restricted" >/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-updates main restricted" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME} universe" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-updates universe" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME} multiverse" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-updates multiverse" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-security main restricted" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-security universe" >>/etc/apt/sources.list + echo "deb [check-valid-until=no] ${SNAPSHOT_ARCHIVE_BASE}ubuntu/${snapshot} ${VERSION_CODENAME}-security multiverse" >>/etc/apt/sources.list + keep_apt_cache + # http://snapshot.ubuntu.com is redirected to https, so we have to install ca-certificates + export DEBIAN_FRONTEND=noninteractive + apt-get -o Acquire::https::Verify-Peer=false update >&2 + apt-get -o Acquire::https::Verify-Peer=false install -y ca-certificates >&2 + ;; +"arch") + : "${SNAPSHOT_ARCHIVE_BASE:=http://archive.archlinux.org/}" + : "${SOURCE_DATE_EPOCH:=$(stat --format=%Y /var/log/pacman.log)}" + export SOURCE_DATE_EPOCH + # shellcheck disable=SC2016 + date -d "@${SOURCE_DATE_EPOCH}" "+Server = ${SNAPSHOT_ARCHIVE_BASE}repos/%Y/%m/%d/\$repo/os/\$arch" >/etc/pacman.d/mirrorlist + ;; +*) + echo >&2 "Unsupported distribution: ${ID}" + exit 1 + ;; +esac + +: "${WRITE_SOURCE_DATE_EPOCH:=/dev/null}" +echo "${SOURCE_DATE_EPOCH}" >"${WRITE_SOURCE_DATE_EPOCH}" +echo "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}" From 509fe746c5e5a1f397371dc84b2f0b3eae6ce3f3 Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Wed, 27 Dec 2023 14:38:00 -0600 Subject: [PATCH 50/80] chore: accommodate unclassifiable statuses in mod-service (#10651) Signed-off-by: dikel Signed-off-by: Alexander Gadzhalov Signed-off-by: Michael Tinker Co-authored-by: dikel Co-authored-by: Alexander Gadzhalov Co-authored-by: Petar Tonev --- .../node/app/spi/records/RecordCache.java | 55 +++++++++++++++---- .../state/recordcache/RecordCacheImpl.java | 10 +++- .../recordcache/RecordCacheImplTest.java | 34 ++++++++++++ .../bdd/spec/queries/meta/HapiGetReceipt.java | 10 ++++ .../records/DuplicateManagementTest.java | 1 + 5 files changed, 96 insertions(+), 14 deletions(-) diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/RecordCache.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/RecordCache.java index 47cdb0cdf7e1..2f01ebd578ab 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/RecordCache.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/RecordCache.java @@ -16,11 +16,14 @@ package com.hedera.node.app.spi.records; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NODE_ACCOUNT; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAYER_SIGNATURE; import static com.hedera.hapi.node.base.ResponseCodeEnum.UNKNOWN; import static com.hedera.node.app.spi.HapiUtils.TIMESTAMP_COMPARATOR; import static java.util.Collections.emptyList; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.transaction.TransactionReceipt; @@ -29,6 +32,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -44,6 +49,27 @@ * transaction. The receipt will no longer be "UNKNOWN" unless an unhandled exception occurred. */ public interface RecordCache { + /** + * For mono-service fidelity, records with these statuses do not prevent valid transactions with + * the same id from reaching consensus and being handled. + */ + Set UNCLASSIFIABLE_STATUSES = EnumSet.of(INVALID_NODE_ACCOUNT, INVALID_PAYER_SIGNATURE); + /** + * And when ordering records for queries, we treat records with unclassifiable statuses as the + * lowest "priority"; so that e.g. if a transaction with id {@code X} resolves to {@link ResponseCodeEnum#SUCCESS} + * after we previously resolved an {@link ResponseCodeEnum#INVALID_NODE_ACCOUNT} for {@code X}, + * then {@link com.hedera.hapi.node.base.HederaFunctionality#TRANSACTION_GET_RECEIPT} will return + * the success record. + */ + // nested ternary expressions + @SuppressWarnings("java:S3358") + Comparator RECORD_COMPARATOR = Comparator.comparing( + rec -> rec.receiptOrThrow().status(), + (a, b) -> UNCLASSIFIABLE_STATUSES.contains(a) == UNCLASSIFIABLE_STATUSES.contains(b) + ? 0 + : (UNCLASSIFIABLE_STATUSES.contains(b) ? -1 : 1)) + .thenComparing(rec -> rec.consensusTimestampOrElse(Timestamp.DEFAULT), TIMESTAMP_COMPARATOR); + /** * An item stored in the cache. * @@ -56,8 +82,8 @@ public interface RecordCache { * Duplicate transactions never have child transactions. * * @param nodeIds The IDs of every node that submitted a transaction with the txId that came to consensus and was - * handled. This is an unordered set, since deterministic ordering is not required for this in-memory - * data structure + * handled. This is an unordered set, since deterministic ordering is not required for this in-memory + * data structure * @param records Every {@link TransactionRecord} handled for every user transaction that came to consensus * @param childRecords The list of child records */ @@ -88,7 +114,7 @@ public History() { */ @Nullable public TransactionRecord userTransactionRecord() { - return records.isEmpty() ? null : records.get(0); + return records.isEmpty() ? null : sortedRecords().getFirst(); } /** @@ -99,7 +125,9 @@ public TransactionRecord userTransactionRecord() { */ @Nullable public TransactionReceipt userTransactionReceipt() { - return records.isEmpty() ? PENDING_RECEIPT : records.get(0).receipt(); + return records.isEmpty() + ? PENDING_RECEIPT + : sortedRecords().getFirst().receipt(); } /** @@ -110,7 +138,7 @@ public TransactionReceipt userTransactionReceipt() { */ @NonNull public List duplicateRecords() { - return records.isEmpty() ? emptyList() : records.subList(1, records.size()); + return records.isEmpty() ? emptyList() : sortedRecords().subList(1, records.size()); } /** @@ -125,16 +153,19 @@ public int duplicateCount() { /** * Returns a list of all records, ordered by consensus timestamp. Some elements of {@link #childRecords} may * come before those in {@link #records}, while some may come after some elements in {@link #records}. + * * @return The list of all records, ordered by consensus timestamp. */ public List orderedRecords() { final var ordered = new ArrayList<>(records); ordered.addAll(childRecords); - ordered.sort((a, b) -> TIMESTAMP_COMPARATOR.compare( - a.consensusTimestampOrElse(Timestamp.DEFAULT), // Comparator doesn't currently handle nulls - b.consensusTimestampOrElse(Timestamp.DEFAULT))); + ordered.sort(RECORD_COMPARATOR); return ordered; } + + private List sortedRecords() { + return records.stream().sorted(RECORD_COMPARATOR).toList(); + } } /** @@ -142,10 +173,10 @@ public List orderedRecords() { * * @param transactionID The transaction ID to look up * @return the history, if any, stored in this cache for the given transaction ID. If the history does not exist - * (i.e. it is null), then we have never heard of this transactionID. If the history is not null, but there - * are no records within it, then we have heard of this transactionID (i.e. in pre-handle or ingest), but - * we do not yet have a record for it (i.e. in handle). If there are records, then the first record will - * be the "primary" or user-transaction record, and the others will be the duplicates. + * (i.e. it is null), then we have never heard of this transactionID. If the history is not null, but there + * are no records within it, then we have heard of this transactionID (i.e. in pre-handle or ingest), but + * we do not yet have a record for it (i.e. in handle). If there are records, then the first record will + * be the "primary" or user-transaction record, and the others will be the duplicates. */ @Nullable History getHistory(@NonNull TransactionID transactionID); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java index af24949be6e8..065836d83ee0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java @@ -80,6 +80,7 @@ @Singleton public class RecordCacheImpl implements HederaRecordCache { private static final Logger logger = LogManager.getLogger(RecordCacheImpl.class); + /** * This empty History is returned whenever a transaction is known to the deduplication cache, but not yet * added to this cache. @@ -194,7 +195,9 @@ public void add( @Override public DuplicateCheckResult hasDuplicate(@NonNull TransactionID transactionID, long nodeId) { final var history = histories.get(transactionID); - if (history == null) { + // If there is no history for this transaction id; or all its history consists of + // unclassifiable records, return that it is effectively a unique id + if (history == null || history.nodeIds().isEmpty()) { return DuplicateCheckResult.NO_DUPLICATE; } return history.nodeIds().contains(nodeId) ? DuplicateCheckResult.SAME_NODE : DuplicateCheckResult.OTHER_NODE; @@ -235,7 +238,10 @@ private void addToInMemoryCache( // sent the first transaction will get "credit" for all the genesis records. But it will be deterministic, and // doesn't actually matter. final var history = histories.computeIfAbsent(userTxId, ignored -> new History()); - history.nodeIds().add(nodeId); + final var status = transactionRecord.receiptOrThrow().status(); + if (!UNCLASSIFIABLE_STATUSES.contains(status)) { + history.nodeIds().add(nodeId); + } // Either we add this tx to the main records list if it is a user/preceding transaction, or to the child // transactions list of its parent. Note that scheduled transactions are always child transactions, but diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java index abee88da174e..f1bf4a3addfe 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/recordcache/RecordCacheImplTest.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_IS_IMMUTABLE; import static com.hedera.hapi.node.base.ResponseCodeEnum.DUPLICATE_TRANSACTION; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NODE_ACCOUNT; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.hapi.node.base.ResponseCodeEnum.UNKNOWN; @@ -56,6 +57,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -624,6 +626,38 @@ final var record = TransactionRecord.newBuilder() assertThat(getRecords(cache, txId)).containsExactly(record); } + @Test + void unclassifiableStatusIsNotPriority() { + // Given a transaction known to the de-duplication cache but not the record cache + final var cache = new RecordCacheImpl(dedupeCache, wsa, props); + final var txId = transactionID(); + final var tx = simpleCryptoTransfer(txId); + final var unclassifiableReceipt = + TransactionReceipt.newBuilder().status(INVALID_NODE_ACCOUNT).build(); + final var unclassifiableRecord = TransactionRecord.newBuilder() + .transactionID(txId) + .receipt(unclassifiableReceipt) + .build(); + final var classifiableReceipt = + TransactionReceipt.newBuilder().status(SUCCESS).build(); + final var classifiableRecord = TransactionRecord.newBuilder() + .transactionID(txId) + .receipt(classifiableReceipt) + .build(); + + // When the unclassifiable record is added to the cache + cache.add(0, PAYER_ACCOUNT_ID, List.of(new SingleTransactionRecord(tx, unclassifiableRecord, List.of()))); + // It does not prevent a "good" record from using this transaction id + assertThat(cache.hasDuplicate(txId, 0L)).isEqualTo(NO_DUPLICATE); + cache.add(0, PAYER_ACCOUNT_ID, List.of(new SingleTransactionRecord(tx, classifiableRecord, List.of()))); + + // And we get the success record from userTransactionRecord() + assertThat(cache.getHistory(txId)).isNotNull(); + final var userRecord = + Objects.requireNonNull(cache.getHistory(txId)).userTransactionRecord(); + assertThat(userRecord).isEqualTo(classifiableRecord); + } + @ParameterizedTest @MethodSource("receiptStatusCodes") @DisplayName("Query for records for an account ID with a proper record") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java index 3806c921d540..5152be9a4066 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetReceipt.java @@ -138,6 +138,7 @@ protected void submitWith(HapiSpec spec, Transaction payment) { forgetOp ? Query.newBuilder().build() : txnReceiptQueryFor(txnId, requestDuplicates, getChildReceipts); response = spec.clients().getCryptoSvcStub(targetNodeFor(spec), useTls).getTransactionReceipts(query); childReceipts = response.getTransactionGetReceipt().getChildTransactionReceiptsList(); + final var duplicateReceipts = response.getTransactionGetReceipt().getDuplicateTransactionReceiptsList(); if (verboseLoggingOn) { String message = String.format( "Receipt: %s", response.getTransactionGetReceipt().getReceipt()); @@ -147,6 +148,15 @@ protected void submitWith(HapiSpec spec, Transaction payment) { spec.logPrefix(), childReceipts.size(), childReceipts.size() > 1 ? "s" : "", childReceipts); log.info(message2); + + String message3 = String.format( + "%s And %d duplicate receipts%s: %s", + spec.logPrefix(), + duplicateReceipts.size(), + duplicateReceipts.size() > 1 ? "s" : "", + duplicateReceipts); + + log.info(message3); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java index 695198b40ce9..2543f6e023b5 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/records/DuplicateManagementTest.java @@ -152,6 +152,7 @@ final HapiSpec usesUnclassifiableIfNoClassifiableAvailable() { .transfers(includingDeduction("node payment", TO)))); } + @HapiTest final HapiSpec classifiableTakesPriorityOverUnclassifiable() { return defaultHapiSpec("ClassifiableTakesPriorityOverUnclassifiable") .given( From 36618383f9cb069ff8e8c530d33def7ba6a5d2a7 Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Wed, 27 Dec 2023 15:56:47 -0600 Subject: [PATCH 51/80] chore(ci): removes the legacy CircleCI configuration files (#10655) Signed-off-by: Nathan Klick --- .circleci.settings.xml | 17 - .circleci/config.yml | 1718 ----------------- .circleci/deployer/Dockerfile | 35 - .circleci/java-builder/Dockerfile | 51 - .circleci/java-builder/run-test | 344 ---- .circleci/java-builder/wait-for-it | 177 -- .circleci/scripts/clean_up_instances.sh | 16 - .circleci/scripts/sonar_check_quality_gate.pl | 297 --- .github/scripts/healthcheck.sh | 9 - .gitignore.hedera | 167 -- .gitignore.platform | 462 ----- platform-sdk/LICENSE | 201 -- 12 files changed, 3494 deletions(-) delete mode 100644 .circleci.settings.xml delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/deployer/Dockerfile delete mode 100644 .circleci/java-builder/Dockerfile delete mode 100755 .circleci/java-builder/run-test delete mode 100755 .circleci/java-builder/wait-for-it delete mode 100755 .circleci/scripts/clean_up_instances.sh delete mode 100644 .circleci/scripts/sonar_check_quality_gate.pl delete mode 100644 .github/scripts/healthcheck.sh delete mode 100644 .gitignore.hedera delete mode 100644 .gitignore.platform delete mode 100644 platform-sdk/LICENSE diff --git a/.circleci.settings.xml b/.circleci.settings.xml deleted file mode 100644 index 1de6545f4bbc..000000000000 --- a/.circleci.settings.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - artifactory-central - circleci - ${env.ART_PASS} - - - artifactory-snapshots - circleci - ${env.ART_PASS} - - - diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4536971afbc6..000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,1718 +0,0 @@ -version: 2.1 - -######################################################## -# Orbs Definitions -######################################################## -orbs: - slack: circleci/slack@4.0.2 - -commands: - install-tools: - description: Install tools for JRS regression - steps: - - run: - name: Install necessary tools - command: | - apt update -y - apt install -y net-tools apt-utils python3.7 python3.7-venv - - run: - name: Authenticate gcloud cli - command: | - echo $GCLOUD_SERVICE_KEY > /tmp/gcloud-service-key.json; - gcloud auth activate-service-account --key-file=/tmp/gcloud-service-key.json; - gcloud --quiet config set project ${GOOGLE_PROJECT_ID}; - gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}; - - ###################################################################################################################### - # Command Name: shell_extract_file_into_variable - # Command Version: 1.1 - # - # Parameters: - # before_extraction a list of steps to be executed prior to extracting the data, optional - # after_extraction a list of steps to be executed after extracting the data, optional - # src_file_name the file containing the input data to be read - # dst_variable_name the name of the bash environment variable used to store the version - # - # - # Description: - # - # Extracts the contents of the file as indicated by the `src_file_name` parameter into the variable - # specified by the `dst_variable_name` parameters. - # - # Command Requirements: - # - perl (>= 5.26.1) - # - ###################################################################################################################### - shell_extract_file_into_variable: - parameters: - before_extraction: - type: steps - default: [ ] - after_extraction: - type: steps - default: [ ] - src_file_name: - type: string - dst_variable_name: - type: string - steps: - - steps: << parameters.before_extraction >> - - run: - name: Extract Data from File << parameters.src_file_name >> To Variable << parameters.dst_variable_name >> - command: | - set -x - if [[ ! -f "<< parameters.src_file_name >>" ]]; then exit 0; fi - FILE_CONTENT=$(cat "<< parameters.src_file_name >>") - echo "export << parameters.dst_variable_name >>=\"${FILE_CONTENT}\"" >> "${BASH_ENV}" - when: always - - steps: << parameters.after_extraction >> - - ###################################################################################################################### - # Command Name: sonar_check_quality_gate - # Command Version: 1.0 - # - # Parameters: - # repo_name the name of the Git repository used during `git_repo_checkout`, default: primary - # - # - # Description: - # - # Fails the CircleCI build if the SonarCloud quality gate criteria is not met. - # - # Command Requirements: - # - perl (>= 5.26.1) - # - Mozilla::CA (>= 20200520) [Perl CPAN Module] - # - JSON::Parse (>= 0.57) [Perl CPAN Module] - # - ###################################################################################################################### - sonar_check_quality_gate: - parameters: - repo_name: - type: string - default: "" - steps: - - run: - name: Check SonarCloud Quality Gate - command: | - set -x - REPO_PATH="/repo<< parameters.repo_name >>" - if [[ ! -d "${REPO_PATH}" ]]; then exit 15; fi - SONAR_QG_SCRIPT="${REPO_PATH}/.circleci/scripts/sonar_check_quality_gate.pl" - if [[ ! -f "${SONAR_QG_SCRIPT}" ]]; then exit 38; fi - SONAR_REPORT_FILE="${REPO_PATH}/target/sonar/report-task.txt" - if [[ ! -f "${SONAR_REPORT_FILE}" ]]; then exit 40; fi - export SONAR_TASK_ID="$(cat "${SONAR_REPORT_FILE}" | perl -0777 -e 'while (my $line = <>) { if ($line =~ /ceTaskId=([A-Za-z0-9_-]+)/ig) { print "$1"; } }')" - if [[ -z "${SONAR_TASK_ID}" ]]; then exit 41; fi - echo "export SONAR_CHECK_EXECUTED=\"1\"" >> "${BASH_ENV}" - perl ${SONAR_QG_SCRIPT} - - shell_extract_file_into_variable: - src_file_name: "/tmp/sonar_dashboard_link.txt" - dst_variable_name: "SONAR_DASHBOARD_LINK" - after_extraction: - - run: - name: Setup Slack Channel for Notifications - command: | - set -x - if [[ -z "${SONAR_CHECK_EXECUTED}" ]]; then - echo "Skipping Slack notifications because SonarCloud Validation was not performed......" - exit 0; - fi - echo "export SLACK_DEFAULT_CHANNEL=\"${SONAR_SLACK_CHANNEL}\"" >> "${BASH_ENV}" - when: on_fail - - slack/notify: - event: fail - custom: | - { - "attachments": [ - { - "color": "#c92808", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*SonarCloud Scan Failure*" - }, - "fields": [ - { - "type": "mrkdwn", - "text": "*_Commit Author:_*" - }, - { - "type": "mrkdwn", - "text": "*_Branch:_*" - }, - { - "type": "mrkdwn", - "text": "${CIRCLE_USERNAME}" - }, - { - "type": "mrkdwn", - "text": "${CIRCLE_BRANCH}" - } - ] - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*_CircleCI Build Number:_*" - }, - { - "type": "mrkdwn", - "text": "*_SonarCloud Analysis:_*" - }, - { - "type": "mrkdwn", - "text": "<${CIRCLE_BUILD_URL}|${CIRCLE_BUILD_NUM}>" - }, - { - "type": "mrkdwn", - "text": "<${SONAR_DASHBOARD_LINK}|${CIRCLE_BRANCH}>" - } - ] - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*_Git Commit:_*\n${CIRCLE_SHA1}" - } - }, - { - "type": "context", - "elements": [ - { - "type": "mrkdwn", - "text": ":rotating_light: Please correct the issues identified by SonarCloud before merging!" - } - ] - } - ] - } - ] - } - - - ###################################################################################################################### - # Command Name: gcp_storage_rsync - # Command Version: 1.0 - # - # Parameters: - # src the source local or remote path, mandatory - # dest the destination local or remote path, mandatory - # - # - # Description: - # - # Copies files or folders (recursively) from the path specified by `src` parameter to the path specified by the - # `dest` parameter. The paths specified by both the `src` and `dest` parameters may be local or remote paths. - # - # This command uses a differential rsync and will only transfer new or changed files. - # - # Example Supported Paths: - # - /home/myPath - # - /home/myPath/file.txt - # - gs://my-bucket/folder - # - gs://my-bucket/file.txt - # - # NOTE: The build will fail if the `gcloud` command is not available or no CRC32 system library is available. - # - # Command Requirements: - # - gcloud (>= 323.0.0) - # - ###################################################################################################################### - gcp_storage_rsync: - parameters: - src: - type: string - dest: - type: string - steps: - - run: - name: "GCP Storage RSync [Source: '<< parameters.src >>', Destination: '<< parameters.dest >>']" - command: | - set -x; - cd /swirlds-platform/regression - gsutil -m rsync -r "<< parameters.src >>" "<< parameters.dest >>" - - ###################################################################################################################### - # Command Name: gcp_import_credentials - # Command Version: 1.0 - # - # Parameters: - # username_variable the name of the bash environment variable holding the user name, optional - # keyfile_variable the name of the bash environment variable holding the key file, optional - # project_variable the name of the bash environment variable holding the project name, optional - # - # - # Description: - # - # Imports the gcloud keypair defined in the CircleCI environment variables specified by the - # `username_variable`, `keyfile_variable`, and `project_variable` parameters. - # The default variable names `GCP_USER_NAME`, `GCP_KEY_FILE`, and `GCP_PROJECT_ID` will be used if not specified. - # - # NOTE: The build will fail if the `gcloud` command is not available or the environment variable is not - # properly formatted. - # - # Command Requirements: - # - gcloud (>= 323.0.0) - # - ###################################################################################################################### - gcp_import_credentials: - parameters: - username_variable: - type: string - default: "GCP_USER_NAME" - keyfile_variable: - type: string - default: "GCP_KEY_FILE" - project_variable: - type: string - default: "GCP_PROJECT_ID" - steps: - - run: - name: Import the Google Cloud Service Account - command: | - set -x - echo "${<< parameters.keyfile_variable >>}" > "/tmp/gcloud_account_key.json" - gcloud auth activate-service-account "${<< parameters.username_variable >>}" --key-file="/tmp/gcloud_account_key.json" --project="${<< parameters.project_variable >>}" - - ###################################################################################################################### - # Command Name: jrs_history_retrieve - # Command Version: 1.0 - # - # Parameters: - # bucket_name the name of the GCP bucket containing the JRS summary history files, mandatory - # summary_path the local path of the JRS summary history files, mandatory - # - # - # Description: - # - # Retrieves the JRS Summary History files from prior runs using a combination of CircleCI caching and GCP bucket - # storage as specified by the `bucket_name` parameter. The local summary history path specified by the - # `summary_path` parameter will be created if it does not exist. - # - # The GCP bucket should be a dedicated storage bucket that must only contain JRS summary history files. - # - # NOTE: The build will fail if the `gcloud` command is not available or no CRC32 system library is available. - # - # Command Requirements: - # - gcloud (>= 323.0.0) - # - ###################################################################################################################### - jrs_history_retrieve: - parameters: - bucket_name: - type: string - summary_path: - type: string - steps: - - run: - name: Ensure JRS Summary Folder Exists - command: | - set -x - cd /swirlds-platform/regression - if [[ ! -d "<< parameters.summary_path >>" ]]; then - mkdir -p "<< parameters.summary_path >>" - fi - - restore_cache: - name: Restoring JRS Summary History Cache - keys: - - v1-jrs-summary-history-{{ epoch }} - - v1-jrs-summary-history- - - gcp_storage_rsync: - src: "gs://<< parameters.bucket_name >>/" - dest: "<< parameters.summary_path >>" - - - ###################################################################################################################### - # Command Name: jrs_history_store - # Command Version: 1.0 - # - # Parameters: - # bucket_name the name of the GCP bucket containing the JRS summary history files, mandatory - # summary_path the local path of the JRS summary history files, mandatory - # - # - # Description: - # - # Saves the JRS Summary History files from the current run using a combination of CircleCI caching and GCP bucket - # storage as specified by the `bucket_name` parameter. The local summary history path specified by the - # `summary_path` parameter must exist. - # - # The GCP bucket should be a dedicated storage bucket that must only contain JRS summary history files. - # - # NOTE: The build will fail if the `gcloud` command is not available or no CRC32 system library is available. - # - # Command Requirements: - # - gcloud (>= 323.0.0) - # - ###################################################################################################################### - jrs_history_store: - parameters: - bucket_name: - type: string - summary_path: - type: string - steps: - - save_cache: - name: Saving JRS Summary History Cache - key: v1-jrs-summary-history-{{ epoch }} - paths: - - << parameters.summary_path >> - when: always - - gcp_storage_rsync: - src: "<< parameters.summary_path >>" - dest: "gs://<< parameters.bucket_name >>/" - - - ###################################################################################################################### - # Command Name: jrs_results_store - # Command Version: 1.0 - # - # Parameters: - # bucket_name the name of the GCP bucket containing the JRS regression results, mandatory - # result_path the local path of the JRS regression result files, mandatory - # - # - # Description: - # - # Saves the JRS Regression result files from the current run to the GCP bucket storage as specified by the - # `bucket_name` parameter. The local result path specified by the `result_path` parameter must exist. - # - # The GCP bucket should be a dedicated storage bucket that must only contain JRS Regression result files. - # - # The final path used to the store the result files in the GCP bucket is as follows: - # - gs://${BUCKET_NAME}/${JRS_USER}/${JRS_BRANCH} - # - # The ${JRS_USER} variable will default to a value of `hedera-services-automation` if not otherwise provided by CircleCI. - # This will be the case with all nightly or other jobs run via the CircleCI cron scheduler. All nightly automated - # regression runs should have results stored under this default username. - # - # The ${JRS_BRANCH} variable will be set to the actual branch name provided by the ${CIRCLE_BRANCH} variable, if - # available, otherwise it will default to the tag name specified by the ${CIRCLE_TAG} variable, if available. If - # neither the ${CIRCLE_BRANCH} or ${CIRCLE_TAG} values are available then then CircleCI job name provided by the - # ${CIRCLE_JOB} variable will be used. - # - # NOTE: The build will fail if the `gcloud` command is not available or no CRC32 system library is available. - # - # Command Requirements: - # - gcloud (>= 323.0.0) - # - ###################################################################################################################### - jrs_results_store: - parameters: - bucket_name: - type: string - result_path: - type: string - steps: - - run: - name: Saving JRS Regression Results - command: | - set -x - cd /swirlds-platform/regression; - - if [[ ! -d "<< parameters.result_path >>" ]]; then exit 13; fi - - if [[ -z "${CIRCLE_USERNAME}" ]]; then - JRS_USER="hedera-services-automation" - else - JRS_USER="${CIRCLE_USERNAME}" - fi - - if [[ -z "${CIRCLE_BRANCH}" ]]; then - if [[ -n "${CIRCLE_TAG}" ]]; then - JRS_BRANCH="${CIRCLE_TAG}" - else - JRS_BRANCH="${CIRCLE_JOB}" - fi - else - JRS_BRANCH="${CIRCLE_BRANCH}" - fi - - cd assets - original_path="<< parameters.result_path >>" - prefix_removed=${original_path//assets\//} - gsutil -m rsync -r ${prefix_removed} "gs://<< parameters.bucket_name >>/${JRS_USER}/${JRS_BRANCH}/${prefix_removed}/" - cd - - - tar -czvf /results.tar.gz /swirlds-platform/regression/<< parameters.result_path >> - when: always - - - - ###################################################################################################################### - # Command Name: jrs_regression_execute - # Command Version: 1.0 - # - # Parameters: - # config_file the relative path to the JRS regression config, mandatory - # regression_path the local path to the regression folder, optional (default: "regression") - # slack_results_channel the slack test results channel override, optional (default: "") - # slack_summary_channel the slack summary channel override, optional (default: "") - # - # - # Description: - # - # Executes the JRS regression run using the provided `config_file` parameter. The `config_file` parameter is treated - # as a path relative to the `regression_path` parameter. The `regression_path` parameter may be either a path - # relative to the current working directory or an absolute path. - # - # The optional `slack_results_channel` and `slack_summary_channel` parameters allow using an alternate slack channel - # for the respective notification types. If one or both of these are provided, then the related setting in the JSON - # files will be ignored. - # - # This command depends on the following CircleCI Context environment variables: - # - JRS_SSH_USER_NAME: the username associated with the `JRS_SSH_KEY_FILE` used by JRS to connect to the remote instances - # - JRS_SSH_KEY_FILE: the base64 encoded private SSH key used by JRS to connect to remote instances - # - JRS_WEB_HOSTNAME: the IP address or hostname of the JRS web server - # - JRS_WEB_PORT: the port number on which the JRS web server is listening - # - # - # NOTE: The build will fail if the `gcloud` command is not available. - # - # Command Requirements: - # - gcloud (>= 323.0.0) - # - openjdk (>= 12.0.2) - # - ###################################################################################################################### - jrs_regression_execute: - parameters: - config_file: - type: string - regression_path: - type: string - default: "regression/assets" - slack_results_channel: - type: string - default: "" - slack_summary_channel: - type: string - default: "" - platform_repo_path: - type: string - default: "../platform-swirlds" - hedera_services_path: - type: string - default: "/repo" - continuous_integration: - type: boolean - default: false - steps: - - run: - name: Configure JRS Regression Keys - command: | - set -x - cp ~/.ssh/id_rsa_e7a63e343ed8fe642c7fb657450344ac /tmp/jrs-ssh-keyfile - cp "/tmp/jrs-ssh-keyfile" "/tmp/jrs-ssh-keyfile.pem" - chmod 0600 /tmp/jrs-ssh-keyfile* - ssh-keygen -p -N "" -m pem -f "/tmp/jrs-ssh-keyfile.pem" - ssh-keygen -y -f "/tmp/jrs-ssh-keyfile" > "/tmp/jrs-ssh-keyfile.pub" - - - run: - name: "Execute JRS Regression (<< parameters.config_file >>)" - command: | - set -x - ls -l /swirlds-platform/regression - cd /swirlds-platform - REGRESSION_PATH="<< parameters.regression_path >>" - if [[ ! -d "${REGRESSION_PATH}" ]]; then exit 15; fi - - if [[ -z "${CIRCLE_USERNAME}" ]]; then - JRS_USER="hedera-services-automation" - else - JRS_USER="${CIRCLE_USERNAME}" - fi - - if [[ -z "${CIRCLE_BRANCH}" ]]; then - if [[ -n "${CIRCLE_TAG}" ]]; then - JRS_BRANCH="${CIRCLE_TAG}" - else - JRS_BRANCH="${CIRCLE_JOB}" - fi - else - JRS_BRANCH="${CIRCLE_BRANCH}" - fi - - JRS_OPTIONS="-po" - SLACK_SUMMARY="<< parameters.slack_summary_channel >>" - SLACK_RESULTS="<< parameters.slack_results_channel >>" - - if [[ -n "${SLACK_SUMMARY}" ]]; then - JRS_OPTIONS="${JRS_OPTIONS} -sc ${SLACK_SUMMARY}" - fi - - if [[ -n "${SLACK_RESULTS}" ]]; then - JRS_OPTIONS="${JRS_OPTIONS} -rc ${SLACK_RESULTS}" - fi - - CI_PARAMETERS="" - if [[ "<< parameters.continuous_integration >>" == "true" ]]; then - CI_PARAMETERS="${CI_PARAMETERS} ${JRS_USER}" - CI_PARAMETERS="${CI_PARAMETERS}_${CIRCLE_BUILD_URL}" - fi - - pushd "${REGRESSION_PATH}" > /dev/null 2>&1 - CONFIG_PATH="<< parameters.config_file >>" - if [[ ! -f "${CONFIG_PATH}" ]]; then - echo - echo "Configuration File '${CONFIG_PATH}' does not exist......" - echo - exit 20 - fi - - if [[ -z "${JAVA_OPTS}" ]]; then - JAVA_OPTS="-Xmx4g" - fi - - JRS_OPTIONS="${JRS_OPTIONS} -u ${JRS_USER}" - JRS_OPTIONS="${JRS_OPTIONS} -b ${JRS_BRANCH}" - JRS_OPTIONS="${JRS_OPTIONS} -sl ${JRS_SSH_USER_NAME}" - JRS_OPTIONS="${JRS_OPTIONS} -sk /tmp/jrs-ssh-keyfile" - - java ${JAVA_OPTS} \ - -Dlog4j.configurationFile=log4j2-fsts-enhanced.xml \ - -Dspring.output.ansi.enabled=ALWAYS \ - -jar regression.jar "${CONFIG_PATH}" -pr "<< parameters.platform_repo_path >>" \ - -r "<< parameters.hedera_services_path >>" -w DOCKER_REMOTE -ci "${CI_PARAMETERS}" \ - --slack-api-token="${SLACK_API_TOKEN}" ${JRS_OPTIONS} - popd > /dev/null 2>&1 - no_output_timeout: 30m - -executors: - build-executor: - resource_class: xlarge - parameters: - workflow-name: - type: string - default: "" - docker: - - image: gcr.io/swirlds-registry/cci-openjdk-infer:17-b20211206 - - image: postgres:10.17-alpine - environment: - POSTGRES_USER: swirlds - POSTGRES_PASSWORD: password - POSTGRES_DB: fcfs - environment: - MAVEN_OPTS: -Xmx5200m - LC_ALL: C.UTF-8 - DEBIAN_FRONTEND: noninteractive - IN_CIRCLE_CI: true - REPO: /repo - WORKFLOW-NAME: << parameters.workflow-name >> - working_directory: /repo - - ci-test-executor: - parameters: - tf_workspace: - type: string - default: "" - tf_dir: - type: string - default: "/infrastructure/terraform/deployments/aws-4-node-spot-net-swirlds" - use_existing_network: - type: string - default: "" - workflow-name: - type: string - default: "" - docker: - - image: gcr.io/swirlds-registry/cci-openjdk-regression:17-b20211206 - environment: - TF_DIR: << parameters.tf_dir >> - IN_CIRCLE_CI: true - USE_EXISTING_NETWORK: <> - TF_WORKSPACE: << parameters.tf_workspace >> - REPO: /repo - INFRASTRUCTURE_REPO: /infrastructure - WORKFLOW-NAME: << parameters.workflow-name >> - working_directory: /repo - -workflows: - GCP_Machine_Cleanup: - triggers: - - schedule: - cron: "0 2,8,14,20 * * *" - filters: - branches: - only: - - develop - jobs: - - jrs_gcp_machine_cleanup: - name: GCP-Machine-Cleanup - context: Slack - project_name: "hedera-regression" - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Weekly-Services-Crypto-Restart-Performance-15N-15C: - triggers: - - schedule: - cron: "5 5 * * 6" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/15N_15C/ - config_type: "weekly" - workflow-name: "GCP-Weekly-Services-Crypto-Restart-Performance-15N-15C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - GCP-Weekly-Services-MixedOps-Restart-Performance-21N-21C: - triggers: - - schedule: - cron: "0 2 * * 3" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/20N_20C/ - config_type: "weekly" - workflow-name: "GCP-Weekly-Services-MixedOps-Restart-Performance-21N-21C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Weekly-Services-HCS-Restart-Performance-15N-15C: - triggers: - - schedule: - cron: "5 10 * * 6" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/15N_15C/ - config_type: "weekly" - workflow-name: "GCP-Weekly-Services-HCS-Restart-Performance-15N-15C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Weekly-Services-HTS-Restart-Performance-15N-15C: - triggers: - - schedule: - cron: "5 15 * * 6" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/15N_15C/ - config_type: "weekly" - workflow-name: "GCP-Weekly-Services-HTS-Restart-Performance-15N-15C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-NetError-4N-1C: - triggers: - - schedule: - cron: "30 5 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_1C/NetError - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-NetError-4N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - # This workflow is for generating state file with large volume of NFTs - # It's not intended to run on a daily basis. - GCP-Create-Large-Volume-NFTs-SavedState: - triggers: - - schedule: - cron: "58 3 * * *" - filters: - branches: - only: - - - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/1N-1C/LargeVolumeNFTs - config_type: "ci" - workflow-name: "GCP-Create-Large-Volume-NFTs-SavedState" - slack_results_channel: "hedera-regression-test" - slack_summary_channel: "hedera-regression-test" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - - # This workflow will be running normally in develop branch on a daily basis - - GCP-Daily-Services-Crypto-Migration-7N-1C: - triggers: - - schedule: - cron: "45 5 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/7N_1C/Migration - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Migration-7N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - -# The mainnet migration test needs to be run in a 18-node test network. -# Enable this workflow to run on a daily only when a new release branch is tagged and branched. -# Once this branch is released, disable this workflow. - GCP-Daily-Services-Crypto-Migration-18N-1C: - triggers: - - schedule: - cron: "0 23,8 * * *" - filters: - branches: - only: - - release-branch-N - jobs: - - build-platform-and-services - - update-start-up-key-for-mainnet: - requires: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/18N_1C/MainnetMigration - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Migration-18N-1C" - requires: - - update-start-up-key-for-mainnet - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Crypto-Restart-4N-1C: - triggers: - - schedule: - cron: "15 6 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_1C/Restart - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Restart-4N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-Ubuntu1804-4N-2C: - triggers: - - schedule: - cron: "0 3 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/Update - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Update-Ubuntu1804-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-Rhel7-4N-2C: - triggers: - - schedule: - cron: "15 3 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/Update - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Update-Rhel7-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-Rhel8-4N-2C: - triggers: - - schedule: - cron: "30 3 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/Update - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Update-Rhel8-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-CentOS7-4N-2C: - triggers: - - schedule: - cron: "45 3 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/Update - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Update-CentOS7-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Crypto-Invalid-Accounts-4N-4C: - triggers: - - schedule: - cron: "0 4 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_4C/CryptoInvalidAccounts - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Invalid-Accounts-4N-4C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-4N-2C: - triggers: - - schedule: - cron: "30 22 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/Update - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Update-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-Abort-4N-2C: - triggers: - - schedule: - cron: "15 22 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/Update - config_type: "daily" - workflow-name: "GCP-Daily-Services-Crypto-Update-Abort-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-Update-DisPreUpdate-4N-2C: - triggers: - - schedule: - cron: "45 22 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/UpdateDisPreUpdate - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Update-DisPreUpdate-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Update-Reconnect-4N-2C: - triggers: - - schedule: - cron: "15 22 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/UpdateReconnect - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Update-Reconnect-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-Update-Reconnect-Abort-4N-2C: - triggers: - - schedule: - cron: "15 23 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/UpdateReconnectAbort - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Update-Reconnect-Abort-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-Update-Reconnect-Abort-DisPreUpdate-4N-2C: - triggers: - - schedule: - cron: "30 23 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/4N_2C/UpdateReconnectAbortDisPreUpdate - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Update-Reconnect-Abort-DisPreUpdate-4N-2C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Account-Balances-client-validation-6N-1C: - triggers: - - schedule: - cron: "30 6 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/6N_1C/BalanceValid - config_type: "daily" - workflow-name: "GCP-Daily-Services-Balances-Validation-6N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - - GCP-Daily-Services-Reconnect-6N-4C: - triggers: - - schedule: - cron: "40 5 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/6N_4C/Reconnect - config_type: "daily" - workflow-name: "GCP-Daily-Services-Reconnect-6N-4C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - - GCP-Daily-Services-Global-3NReconnect-15N-4C: - triggers: - - schedule: - cron: "40 7 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/15N_4C/Global3NReconnect - config_type: "daily" - workflow-name: "GCP-Daily-Services-Global-3NReconnect-15N-4C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Weekly-Services-NetDelay-15N-1C: - triggers: - - schedule: - cron: "0 5 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/15N_1C/NetDelay - config_type: "weekly" - workflow-name: "GCP-Weekly-Services-Comp-NetDelay-15N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - -# GCP-Daily-Services-Recovery-4N-1C: -# triggers: -# - schedule: -# cron: "55 6 * * *" -# filters: -# branches: -# only: -# - develop -# jobs: -# - build-platform-and-services -# - jrs-regression: -# context: Slack -# regression_path: /swirlds-platform/regression/assets -# result_path: assets/results/4N_1C/Recovery -# config_type: "daily" -# workflow-name: "GCP-Daily-Services-Recovery-4N-1C" -# requires: -# - build-platform-and-services -# pre-steps: -# - install-tools -# - attach_workspace: -# at: / - - GCP-Weekly-Services-Query-Restart-Performance-7N-7C: - triggers: - - schedule: - cron: "0 6 * * 2" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/7N_7C/QueryPerf - config_type: "weekly" - workflow-name: "GCP-Weekly-Services-Query-Restart-Performance-7N-7C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-Restart-Performance-Hotspot-6N-6C: - triggers: - - schedule: - cron: "35 7 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/6N_6C/Performance - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Restart-Performance-Hotspot-6N-6C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-Restart-Performance-Random-7N-7C: - triggers: - - schedule: - cron: "40 4 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/7N_7C/Performance - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Restart-Performance-Random-7N-7C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - - GCP-Daily-Services-HTS-Restart-Performance-7N-7C: - triggers: - - schedule: - cron: "30 4 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/7N_7C/Performance - config_type: "daily" - workflow-name: "GCP-Daily-Services-HTS-Restart-Performance-7N-7C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-Reconnect-6N-1C: - triggers: - - schedule: - cron: "15 7 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/6N_1C/Reconnect - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-Reconnect-6N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-NI-Reconnect-Correctness-6N-1C: - triggers: - - schedule: - cron: "25 7 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/6N_1C/NIReconnectCorrectness - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-NI-Reconnect-Correctness-6N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-ND-Reconnect-Correctness-6N-1C: - triggers: - - schedule: - cron: "25 8 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/6N_1C/NDReconnectCorrectness - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-ND-Reconnect-Correctness-6N-1C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - - GCP-Daily-Services-Comp-3NReconnect-15N-4C: - triggers: - - schedule: - cron: "25 8 * * *" - filters: - branches: - only: - - develop - jobs: - - build-platform-and-services - - jrs-regression: - context: Slack - regression_path: /swirlds-platform/regression/assets - result_path: assets/results/15N_4C/3NReconnect - config_type: "daily" - workflow-name: "GCP-Daily-Services-Comp-3NReconnect-15N-4C" - requires: - - build-platform-and-services - pre-steps: - - install-tools - - attach_workspace: - at: / - -jobs: -# build-artifact: -# parameters: -# workflow-name: -# type: string -# default: "" -# executor: -# name: build-executor -# workflow-name: << parameters.workflow-name >> -# steps: -# - checkout -# - run: -# name: prepare log dir -# command: | -# mkdir -p /repo/test-clients/output -# - run: -# name: gradle assemble -# # use double quote otherwise the backslash of line continuation will be treated as part of mvn parameter -# command: | -# ./gradlew assemble copyLib copyApp --scan --parallel \ -# | tee /repo/test-clients/output/hapi-client.log -# -## - run: -## name: Upload codecov -## command: | -## apt update -y -## apt install -y curl -## bash <(wget -O - https://codecov.io/bash) -# - persist_to_workspace: -# root: / -# paths: -# - repo/ -# - root/.m2 - - sonar-check: - parameters: - workflow-name: - type: string - default: "" - executor: - name: build-executor - workflow-name: << parameters.workflow-name >> - steps: - - attach_workspace: - at: / - - run: - name: Install tools for Sonar scan - command: | - export DEBIAN_FRONTEND=noninteractive - apt update -y && \ - apt install -y openssh-client haveged tar gzip git ca-certificates wget zip unzip make gcc liblwp-protocol-https-perl && \ - apt install -y jq libjq1 libonig4 curl && \ - echo | cpan && \ - cpan install CPAN && \ - cpan install Mozilla::CA && \ - cpan install JSON::Parse - - sonar_check_quality_gate - - update-start-up-key-for-mainnet: - parameters: - workflow-name: - type: string - default: "" - executor: - name: build-executor - steps: - - attach_workspace: - at: / - - run: - name: Modify Payer account for Mainnet start from saved state test - command: | - sed -i 's/default.payer=0.0.2/default.payer=0.0.950/g' /repo/test-clients/src/main/resource/spec-default.properties; - echo -n "$KEY_950" > /repo/test-clients/src/main/resource/StartUpAccount.txt; - - persist_to_workspace: - root: / - paths: - - repo/ - - swirlds-platform/ - - build-platform-and-services: - parameters: - workflow-name: - type: string - default: "" - executor: - name: build-executor - steps: - - attach_workspace: - at: / - - add_ssh_keys: - fingerprints: - - "96:47:c4:5c:e7:45:06:c5:26:a5:85:ef:41:22:2f:d6" - - "14:21:e9:81:1f:ae:df:ec:11:60:4a:49:e0:b9:bb:58" - - "e7:a6:3e:34:3e:d8:fe:64:2c:7f:b6:57:45:03:44:ac" - - checkout - - run: - name: Build hedera-services repo - command: ./gradlew assemble copyApp copyLib - - run: - name: Checkout swirlds-platform repos and build - command: | - sed -i -e 's/Host services-jrs-regression/Host services-jrs-regression\n HostName github.com/g' ~/.ssh/config - - # Git Clone for Platform - cd / - export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" - git clone ssh://git@services-jrs-regression/swirlds/swirlds-platform.git; - - # Git Clone JRS Separately - mkdir -p /swirlds-platform/regression >/dev/null 2>&1 || true - git clone ssh://git@services-jrs-regression/swirlds/swirlds-platform-regression.git /swirlds-platform/regression - - # Build Platform - cd /swirlds-platform - ./gradlew assemble --scan - - # Build JRS - cd /swirlds-platform/regression - ./gradlew assemble --scan --parallel - - - run: - name: Save PEM file to keys folder - command: | - mkdir -p /swirlds-platform/regression/keys - cp ~/.ssh/id_rsa_e7a63e343ed8fe642c7fb657450344ac /swirlds-platform/regression/keys/services-regression.pem - - - persist_to_workspace: - root: / - paths: - - repo/ - - swirlds-platform/ - - ###################################################################################################################### - # Job Name: jrs_regression - # Job Version: 1.0 - # Target Operating Systems: Ubuntu 18.04 (bionic), Ubuntu 20.04 (focal), CentOS 7, CentOS 8 - # - # Parameters: - # runtime a CircleCI executor reference used to control the OpenJDK and other tool versions - # - # - # Description: - # - # Description goes here - # - # - # - # Executor Requirements: - # - git (>= 2.17.1) - # - wget (>= 1.19.4) - # - zip (>= 3.0) - # - unzip (>= 3.0) - # - gzip (>= 1.6) - # - tar (>= 1.29) - # - haveged (>= 1.9.1) - # - maven (>= 3.6.1) - # - openjdk (>= 12.0.2) - # - postgresql-server (>= 10.9) - # - gcloud-sdk (>= 323.0.0) - # - # References: - # - OpenJDK: https://jdk.java.net/ - # - Maven: https://maven.apache.org/ - # - ###################################################################################################################### - jrs-regression: - parameters: - runtime: - type: string - default: ci-test-executor - config_type: - type: string - config_file: - type: string - default: "" - regression_path: - type: string - default: "regression/assets" - result_path: - type: string - default: "regression/assets/results" - result_bucket: - type: string - default: "hedera-services-jrs-test-results" - summary_path: - type: string - default: "assets/summaryHistory" - summary_bucket: - type: string - default: "hedera-services-jrs-summary-history/summaryHistory" - automated_run: - type: boolean - default: true - slack_results_channel: - type: string - default: "hedera-regression" - slack_summary_channel: - type: string - default: "hedera-regression-summary" - workflow-name: - type: string - default: "" - platform_repo_path: - type: string - default: "../platform-swirlds" - hedera_services_path: - type: string - default: "/repo" - continuous_integration: - type: boolean - default: false - executor: - name: << parameters.runtime >> - workflow-name: << parameters.workflow-name >> - steps: - - gcp_import_credentials: - username_variable: GCLOUD_SERVICE_ACCOUNT_NAME - keyfile_variable: GCLOUD_SERVICE_KEY - project_variable: GOOGLE_PROJECT_ID - - add_ssh_keys: - fingerprints: - - "96:47:c4:5c:e7:45:06:c5:26:a5:85:ef:41:22:2f:d6" - - "14:21:e9:81:1f:ae:df:ec:11:60:4a:49:e0:b9:bb:58" - - "e7:a6:3e:34:3e:d8:fe:64:2c:7f:b6:57:45:03:44:ac" - - when: - condition: << parameters.automated_run >> - steps: - - jrs_history_retrieve: - bucket_name: << parameters.summary_bucket >> - summary_path: << parameters.summary_path >> - - jrs_regression_execute: - config_file: configs/services/suites/<< parameters.config_type >>/<>.json - regression_path: << parameters.regression_path >> - slack_results_channel: << parameters.slack_results_channel >> - slack_summary_channel: << parameters.slack_summary_channel >> - hedera_services_path: << parameters.hedera_services_path >> - platform_repo_path: << parameters.platform_repo_path >> - continuous_integration: << parameters.continuous_integration >> - - when: - condition: << parameters.automated_run >> - steps: - - jrs_history_store: - bucket_name: << parameters.summary_bucket >> - summary_path: << parameters.summary_path >> - - jrs_results_store: - bucket_name: << parameters.result_bucket >> - result_path: << parameters.result_path >> - - store_artifacts: - path: /results.tar.gz - - run: - name: Check for CI test failures - command: | - set -x; - set +e; - cd /swirlds-platform/regression - CI_RESULTS_LOG=$(find assets/results/4N_1C -name hapi-client-combined.log) - grep $CI_RESULTS_LOG -e 'status=ERROR' -e 'status=FAILED' - if [ "$?" -eq "0" ]; then - exit 1 - else - exit 0 - fi - SAMPLE_NODE_LOG=$(find assets/results/4N_1C -name hgcaa.log | head -1) - grep $SAMPLE_NODE_LOG -e 'IssListener - In round' - if [ "$?" -eq "0" ]; then - exit 1 - else - exit 0 - fi - - jrs_gcp_machine_cleanup: - parameters: - runtime: - type: string - default: ci-test-executor - project_name: - type: string - default: "hedera-regression" - executor: << parameters.runtime >> - steps: - - gcp_import_credentials: - username_variable: GCLOUD_SERVICE_ACCOUNT_NAME - keyfile_variable: GCLOUD_SERVICE_KEY - project_variable: GOOGLE_PROJECT_ID - - checkout - - run: - name: Cleanup GCP Instance Groups - command: | - set -x - cd /repo; - chmod 755 /repo/.circleci/scripts/clean_up_instances.sh; - .circleci/scripts/clean_up_instances.sh << parameters.project_name >>; diff --git a/.circleci/deployer/Dockerfile b/.circleci/deployer/Dockerfile deleted file mode 100644 index bd8e9c6824f8..000000000000 --- a/.circleci/deployer/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM ubuntu:18.04 - -WORKDIR / - -RUN apt update \ - && apt install -y --no-install-recommends \ - ansible \ - python \ - python-pip \ - && apt install -y git \ - && apt autoremove \ - && apt clean \ - && rm -rf /var/lib/apt/lists/ \ - ; - -RUN pip install \ - boto3==1.7.84 \ - boto \ - && pip install \ - awscli \ - ; - -RUN apt update \ - && apt install -y --no-install-recommends \ - unzip \ - && wget https://releases.hashicorp.com/terraform/0.11.8/terraform_0.11.8_linux_amd64.zip \ - && unzip terraform_0.11.8_linux_amd64.zip \ - && chmod +x terraform \ - && mv terraform /usr/local/bin/ \ - && rm terraform_0.11.8_linux_amd64.zip \ - && apt purge -y unzip \ - && apt autoremove \ - && apt clean \ - && rm -rf /var/lib/apt/lists/ \ - ; diff --git a/.circleci/java-builder/Dockerfile b/.circleci/java-builder/Dockerfile deleted file mode 100644 index 9984bcd60e08..000000000000 --- a/.circleci/java-builder/Dockerfile +++ /dev/null @@ -1,51 +0,0 @@ -FROM ubuntu:18.04 - -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y openssh-client haveged tar gzip git ca-certificates wget zip unzip sudo tcptraceroute - -RUN cd /tmp && \ - wget --quiet https://jvm-storage.s3.amazonaws.com/openjdk-12.0.2_linux-x64_bin.tar.gz && \ - mkdir -p /usr/local/java && \ - tar -zxf openjdk-12.0.2_linux-x64_bin.tar.gz -C /usr/local/java && \ - update-alternatives --install "/usr/bin/java" "java" "/usr/local/java/jdk-12.0.2/bin/java" 1500 && \ - update-alternatives --install "/usr/bin/javac" "javac" "/usr/local/java/jdk-12.0.2/bin/javac" 1500 && \ - update-alternatives --install "/usr/bin/javadoc" "javadoc" "/usr/local/java/jdk-12.0.2/bin/javadoc" 1500 && \ - update-alternatives --install "/usr/bin/keytool" "keytool" "/usr/local/java/jdk-12.0.2/bin/keytool" 1500 && \ - rm -f /tmp/jdk-12.0.2_linux-x64_bin.tar.gz - -RUN cd /tmp && \ - wget --quiet https://swirlds-docker-artifacts.s3.amazonaws.com/maven/apache-maven-3.6.1-bin.tar.gz && \ - tar -zxf apache-maven-3.6.1-bin.tar.gz && \ - mv apache-maven-3.6.1 /usr/local/maven && \ - rm -f /tmp/apache-maven-3.6.1-bin.tar.gz && \ - echo 'export M2_HOME="/usr/local/maven"' > /etc/profile.d/mvn.sh && \ - echo 'export JAVA_HOME="/usr/local/java/jdk-12.0.2"' > /etc/profile.d/java.sh && \ - update-alternatives --install "/usr/bin/mvn" "mvn" "/usr/local/maven/bin/mvn" 1500 - -RUN apt update \ - && apt install -y --no-install-recommends \ - make \ - gradle \ - python3-pip \ - ansible \ - postgresql-client-10 \ - git \ - && apt autoremove -y \ - && apt purge -y --auto-remove openjdk-* \ - && rm -rf /var/lib/apt/lists/ \ - && pip3 install setuptools \ - && pip3 install awscli - -RUN wget -nv https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.2.0.1227-linux.zip \ - && unzip sonar-scanner-cli-3.2.0.1227-linux.zip \ - && mv sonar-scanner-3.2.0.1227-linux sonar-scanner \ - && rm -f sonar-scanner-cli-3.2.0.1227-linux.zip - -RUN wget -nv https://releases.hashicorp.com/terraform/0.11.10/terraform_0.11.10_linux_amd64.zip \ - && unzip terraform_0.11.10_linux_amd64.zip \ - && chmod +x terraform \ - && mv terraform /usr/local/bin/ \ - && rm terraform_0.11.10_linux_amd64.zip - -COPY . /usr/bin diff --git a/.circleci/java-builder/run-test b/.circleci/java-builder/run-test deleted file mode 100755 index 8f5a01e1d0c9..000000000000 --- a/.circleci/java-builder/run-test +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env bash - -set -eEx -o pipefail - -ANSIBLE_DIR="/infrastructure/terraform/deployments/ansible" -CLIENT_DIR="/repo/test-clients" -HAPIAPP_DIR="/repo/HapiApp2.0" - -if [[ -z "${TF_WORKSPACE}" ]]; then - TF_WORKSPACE="test-${CIRCLE_BUILD_NUM}" -fi - -if [[ -z "${ENABLE_NEW_RELIC}" ]]; then - ENABLE_NEW_RELIC=false - NEW_RELIC_NAME="" -fi - -if [[ -z "${COMMAND_LINE_ARGS}" ]]; then - COMMAND_LINE_ARGS="" -fi - -function ansible_deploy { - ansible-playbook \ - -i ./inventory/hosts-${TF_WORKSPACE} \ - --private-key /root/.ssh/${ANSIBLE_SSH_KEY} \ - -u ubuntu \ - -e branch=${CIRCLE_BRANCH} \ - -e app_dir=/repo \ - -e enable_newrelic=${ENABLE_NEW_RELIC} \ - -e new_relic_name=${NEW_RELIC_NAME} \ - play-deploy-psql.yml -} - -function ansible_clean { - ansible-playbook \ - -i ./inventory/hosts-${TF_WORKSPACE} \ - --private-key /root/.ssh/${ANSIBLE_SSH_KEY} \ - -u ubuntu \ - play-clean-state.yml -} - -function ansible_reboot { - ansible-playbook \ - -i ./inventory/hosts-${TF_WORKSPACE} \ - --private-key /root/.ssh/${ANSIBLE_SSH_KEY} \ - -u ubuntu \ - play-reboot.yml -} - -function tf_apply { - terraform apply \ - --auto-approve \ - --var-file ci.tfvars \ - --var node_count=${1} -} - -function tf_destroy { - terraform destroy \ - --auto-approve \ - --var-file ci.tfvars \ - --var node_count=0 \ - && terraform workspace select default \ - && terraform workspace delete ${TF_WORKSPACE} \ - ; -} - -function replace_application_properties { - rm -f ${CLIENT_DIR}/src/main/resource/application.properties \ - && cp ${TF_DIR}/nets/${TF_WORKSPACE}/client.application.properties \ - ${CLIENT_DIR}/src/main/resource/application.properties \ - ; -} - -function replace_test_properties { - rm -f ${CLIENT_DIR}/config/umbrellaTest.properties \ - && cp ${CLIENT_DIR}/config/umbrellaTest.properties.alt${1} \ - ${CLIENT_DIR}/config/umbrellaTest.properties \ - ; -} - -function download_hosts_output { - for host in "${HOSTS[@]}"; do - scp -p -o StrictHostKeyChecking=no ubuntu@$host:/opt/hgcapp/services-hedera/HapiApp2.0/*.csv $HAPIAPP_DIR - mkdir -p $HAPIAPP_DIR/$host - scp -p -r -o StrictHostKeyChecking=no ubuntu@$host:/opt/hgcapp/services-hedera/HapiApp2.0/output $HAPIAPP_DIR/$host - done -} - -function print_downloaded_hosts_output { - for host in "${HOSTS[@]}"; do - grep "current platform status = " $HAPIAPP_DIR/$host/output/hgcaa.log - grep "StorageMap roothash on " $HAPIAPP_DIR/$host/output/hgcaa.log - cat $HAPIAPP_DIR/$host/output/swirlds.log - done -} - -function look_for_iss_in_hosts_output { - download_hosts_output - for host in "${HOSTS[@]}"; do - set +e - issCount=$(grep "Received invalid state signature" $HAPIAPP_DIR/$host/output/swirlds.log | wc -l) - set -e - if [[ "$issCount" -gt "0" ]]; then - echo "FAILED: Node $host received invalid state signature" - return -2 - fi - done -} - -function check_postgresql_status { - for host in "${HOSTS[@]}"; do - ssh -o StrictHostKeyChecking=no ubuntu@$host "sudo ls -ltr $POSTGRESQL_LOG_DIR; psql -V; systemctl status postgresql@10-main" -# secondLatestLog=$(ssh -o StrictHostKeyChecking=no ubuntu@$host "sudo ls -tr $POSTGRESQL_LOG_DIR | tail -2 | head -1") -# ssh -o StrictHostKeyChecking=no ubuntu@$host "sudo tail -100 $POSTGRESQL_LOG_DIR/$secondLatestLog" - done -} - -# -# A wrapper for "waitFor" in HapiApp2.0/functions.sh -# to wait for remote nodes -# -# Arguments: -# string to wait for -# log of remote nodes -# expected number of appearances of the string to stop the wait -# -function wait_for_all_remote_nodes { - for host in "${HOSTS[@]}"; do - searchCommand="ssh -o StrictHostKeyChecking=no ubuntu@$host 'grep \"$1\" $2'" - waitFor "$searchCommand" $3 - eval "$searchCommand" - done -} - -function validate_freeze_start_time { - download_hosts_output - for host in "${HOSTS[@]}"; do - local result=$(grep "current platform status = MAINTENANCE" $HAPIAPP_DIR/$host/output/hgcaa.log | tail -1) - local realFreezeStartTime=${result:11:7} - if [[ "$realFreezeStartTime" == "$1" || "$realFreezeStartTime" == "$2" ]]; then - echo "Passed: Node $host entered MAINTENANCE mode at the expected time" - else - echo "FAILED: Node $host did not enter MAINTENANCE mode at the expected time" - return -1 - fi - done -} - -function prepare { - HOSTS=($(cat ${TF_DIR}/nets/${TF_WORKSPACE}/hosts_list)) - TESTS_ARR=(${TESTS}) - TEST_PROPS_NUM_ARR=(${TEST_PROPS_NUM}) - POSTGRESQL_LOG_DIR="/var/lib/postgresql/10/main/log" - . $HAPIAPP_DIR/functions.sh - disallow_postgresql_to_upgrade -} - -function disallow_postgresql_to_upgrade { - set +e - printTimestamp - for host in "${HOSTS[@]}"; do - runningAptCommands="apt" - while [ -n "$runningAptCommands" ]; do - sleep 10 - runningAptCommands=$(ssh -o StrictHostKeyChecking=no ubuntu@$host "ps -ef | grep [a]pt") - done - ssh -o StrictHostKeyChecking=no ubuntu@$host "sudo apt-mark hold postgresql-10" - done - printTimestamp - set -e -} - -function empty_run_only_to_check_postgresql_status { - for i in {1..5} - do - check_postgresql_status - sleep 120 - done -} - -function run_tests { - COUNT=0 - cd ${CLIENT_DIR} - mvn clean install - - for test in "${TESTS_ARR[@]}"; do - check_postgresql_status - if [[ "$test" == "restart" ]]; then - echo "All nodes should be in MAINTENANCE mode" - local hgcaaLog="/opt/hgcapp/services-hedera/HapiApp2.0/output/hgcaa.log" - local searchString="current platform status = MAINTENANCE" - wait_for_all_remote_nodes "$searchString" $hgcaaLog 1 - validate_freeze_start_time "$freezeStartHour:$freezeStartMin:0" "$oneMinuteBeforeFreezeStartTime:5" - - echo "Waiting for all nodes to be back to ACTIVE..." - searchString="$freezeEndHour:$freezeEndMin:0.*current platform status = ACTIVE" - wait_for_all_remote_nodes "$searchString" $hgcaaLog 1 - - # Check for background jobs before restart - jobs - - echo "Restarting all nodes..." - cd $ANSIBLE_DIR - ansible_reboot - wait_for_it 50211 - sleep 30 - - echo "Waiting for all nodes to restart completely..." - searchString="current platform status = ACTIVE" - wait_for_all_remote_nodes "$searchString" $hgcaaLog 3 - - echo "All nodes should restore saved signed state from disk..." - local swirldsLog="/opt/hgcapp/services-hedera/HapiApp2.0/output/swirlds.log" - searchString="Platform - Signed state loaded from disk has a valid hash." - wait_for_all_remote_nodes "$searchString" $swirldsLog 1 - - echo "All nodes should restart with the same events..." - searchString="Hashgraph - lateseq after restart is .*$" - local searchCommand="ssh -o StrictHostKeyChecking=no ubuntu@${HOSTS[0]} 'grep -o \"$searchString\" $swirldsLog'" - waitFor "$searchCommand" 1 - searchString=$(eval "$searchCommand") - searchString="${searchString//[/\\[}" - searchString="${searchString//]/\\]}" - wait_for_all_remote_nodes "$searchString" $swirldsLog 1 - - # Clean up client output after the restart and prepare for the final validations - cd $CLIENT_DIR - rm $CLIENT_DIR/output/* - else - if [[ "${TEST_PROPS_NUM_ARR[${COUNT}]}" != "-1" ]]; then - echo "replacing properties" - replace_test_properties "${TEST_PROPS_NUM_ARR[${COUNT}]}" - else - echo "not replacing properties" - fi - - # Adding an echo line to have a separation of log blocks - # printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - - - chmod -R +x $CLIENT_DIR/* - - local clientLog="$CLIENT_DIR/output/$test$COUNT.log" - if [[ "${TEST_PROPS_NUM_ARR[${COUNT}]}" == "5k" ]]; then - # Turn off set -e for 5k requests so the test does not fail on UNAVAILABLE nodes - set +e - mvn -q exec:java -Dexec.mainClass=${test} -Dexec.args="${HOSTS[${HOST_INDEX}]} ${NODE_ACCOUNT} ${COMMAND_LINE_ARGS}" -Dexec.cleanupDaemonThreads=false >$clientLog 2>&1 & - local clientPid=$! - waitFor "grep \"UNAVAILABLE\" $clientLog" 1 - - kill $clientPid - - # Turn set -e back on - set -e - grep -n "UNAVAILABLE" $clientLog | tail -10 -# cat $clientLog - elif [[ $test == *FreezeServiceTest* ]]; then - mvn -e -q exec:java -Dexec.mainClass=${test} -Dexec.args="${HOSTS[${HOST_INDEX}]} ${NODE_ACCOUNT} ${COMMAND_LINE_ARGS}" -Dexec.cleanupDaemonThreads=false >$clientLog 2>&1 - local failedIndicator=$(grep "\[INFO\] BUILD FAILURE" $clientLog) - if [[ -n "$failedIndicator" ]]; then - cat $clientLog - return 1 - fi - oneMinuteBeforeFreezeStartTime=$(date '+%H:%M') - local freezeTransactionBody=$(grep -A4 "freeze: FreezeTransactionBody =" $clientLog) - local freezeArr=(${freezeTransactionBody//:/ }) - freezeStartHour=$(printf "%02d" ${freezeArr[4]}) - freezeStartMin=$(printf "%02d" ${freezeArr[6]}) - freezeEndHour=$(printf "%02d" ${freezeArr[8]}) - freezeEndMin=$(printf "%02d" ${freezeArr[10]}) - else - mvn -e -q exec:java -Dexec.mainClass=${test} -Dexec.args="${HOSTS[${HOST_INDEX}]} ${NODE_ACCOUNT} ${COMMAND_LINE_ARGS}" -Dexec.cleanupDaemonThreads=false - fi - fi - look_for_iss_in_hosts_output - COUNT=$((COUNT + 1)) - # printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - - done - - echo "Tests Finished" - download_hosts_output - $HAPIAPP_DIR/validateStats.sh "$HAPIAPP_DIR/*0.csv" "$CLIENT_DIR/output/#*.log" -} - -function wait_for_it { - for host in $(cat ${TF_DIR}/nets/${TF_WORKSPACE}/hosts_list); do - wait-for-it ${host}:${1} -t 60 -- echo "${host} is up" - done -} - - -function cleanup { - SIG=$? - set +e - download_hosts_output - print_downloaded_hosts_output - check_postgresql_status - cd ${TF_DIR} - tf_destroy - exit ${SIG} -} - -#################### -### BEGIN SCRIPT -#################### - -# trap and do terraform deploy only when 'SKIP_TF_DEPLOY' var is unset -if [[ -z "${SKIP_TF_DEPLOY}" ]]; then - echo "Trapping for cleanup..." - trap cleanup EXIT - cd ${TF_DIR} - echo "Initializing TF Workspace: ${TF_WORKSPACE}" - terraform init - terraform workspace new ${TF_WORKSPACE} - tf_apply 3 - wait_for_it 22 - sleep 3 -else - echo "Not trapping for cleanup..." -fi - -# run deploy only when 'SKIP_ANSIBLE_DEPLOY' var is unset -if [[ -z "${SKIP_ANSIBLE_DEPLOY}" ]]; then - cd ${ANSIBLE_DIR} - echo "Deploying with Ansible" - ansible_deploy -fi - -# run clean only if 'DO_ANSIBLE_CLEAN' var is set -if [[ ! -z "${DO_ANSIBLE_CLEAN}" ]]; then - echo "cleaning state and restarting" - ansible_clean -fi - -wait_for_it 50211 -sleep 30 - -echo "Testing Circle Build ${CIRCLE_BUILD_NUM} and running tests: ${TESTS}" - -replace_application_properties - -prepare - -run_tests -#empty_run_only_to_check_postgresql_status diff --git a/.circleci/java-builder/wait-for-it b/.circleci/java-builder/wait-for-it deleted file mode 100755 index bbe404324bce..000000000000 --- a/.circleci/java-builder/wait-for-it +++ /dev/null @@ -1,177 +0,0 @@ -#!/usr/bin/env bash -# Use this script to test if a given TCP host/port are available - -cmdname=$(basename $0) - -echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } - -usage() -{ - cat << USAGE >&2 -Usage: - $cmdname host:port [-s] [-t timeout] [-- command args] - -h HOST | --host=HOST Host or IP under test - -p PORT | --port=PORT TCP port under test - Alternatively, you specify the host and port as host:port - -s | --strict Only execute subcommand if the test succeeds - -q | --quiet Don't output any status messages - -t TIMEOUT | --timeout=TIMEOUT - Timeout in seconds, zero for no timeout - -- COMMAND ARGS Execute command with args after the test finishes -USAGE - exit 1 -} - -wait_for() -{ - if [[ $TIMEOUT -gt 0 ]]; then - echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" - else - echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" - fi - start_ts=$(date +%s) - while : - do - if [[ $ISBUSY -eq 1 ]]; then - nc -z $HOST $PORT - result=$? - else - (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 - result=$? - fi - if [[ $result -eq 0 ]]; then - end_ts=$(date +%s) - echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" - break - fi - sleep 1 - done - return $result -} - -wait_for_wrapper() -{ - # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 - if [[ $QUIET -eq 1 ]]; then - timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & - else - timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & - fi - PID=$! - trap "kill -INT -$PID" INT - wait $PID - RESULT=$? - if [[ $RESULT -ne 0 ]]; then - echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" - fi - return $RESULT -} - -# process arguments -while [[ $# -gt 0 ]] -do - case "$1" in - *:* ) - hostport=(${1//:/ }) - HOST=${hostport[0]} - PORT=${hostport[1]} - shift 1 - ;; - --child) - CHILD=1 - shift 1 - ;; - -q | --quiet) - QUIET=1 - shift 1 - ;; - -s | --strict) - STRICT=1 - shift 1 - ;; - -h) - HOST="$2" - if [[ $HOST == "" ]]; then break; fi - shift 2 - ;; - --host=*) - HOST="${1#*=}" - shift 1 - ;; - -p) - PORT="$2" - if [[ $PORT == "" ]]; then break; fi - shift 2 - ;; - --port=*) - PORT="${1#*=}" - shift 1 - ;; - -t) - TIMEOUT="$2" - if [[ $TIMEOUT == "" ]]; then break; fi - shift 2 - ;; - --timeout=*) - TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - CLI=("$@") - break - ;; - --help) - usage - ;; - *) - echoerr "Unknown argument: $1" - usage - ;; - esac -done - -if [[ "$HOST" == "" || "$PORT" == "" ]]; then - echoerr "Error: you need to provide a host and port to test." - usage -fi - -TIMEOUT=${TIMEOUT:-15} -STRICT=${STRICT:-0} -CHILD=${CHILD:-0} -QUIET=${QUIET:-0} - -# check to see if timeout is from busybox? -# check to see if timeout is from busybox? -TIMEOUT_PATH=$(realpath $(which timeout)) -if [[ $TIMEOUT_PATH =~ "busybox" ]]; then - ISBUSY=1 - BUSYTIMEFLAG="-t" -else - ISBUSY=0 - BUSYTIMEFLAG="" -fi - -if [[ $CHILD -gt 0 ]]; then - wait_for - RESULT=$? - exit $RESULT -else - if [[ $TIMEOUT -gt 0 ]]; then - wait_for_wrapper - RESULT=$? - else - wait_for - RESULT=$? - fi -fi - -if [[ $CLI != "" ]]; then - if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then - echoerr "$cmdname: strict mode, refusing to execute subprocess" - exit $RESULT - fi - exec "${CLI[@]}" -else - exit $RESULT -fi diff --git a/.circleci/scripts/clean_up_instances.sh b/.circleci/scripts/clean_up_instances.sh deleted file mode 100755 index 039ab19d49f0..000000000000 --- a/.circleci/scripts/clean_up_instances.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -projectName="$1" -timestamp=`TZ=UTC date -d '6 hours ago' "+%Y-%m-%dT%H:%M:%S"` -echo "Project Name : $projectName" -echo "Instances before $timestamp will be deleted" -list=`gcloud compute instance-groups managed list --filter="(name~.*gcp-daily.* OR name~.*gcp-ondemand.* OR name~.*gcp-weekly.* OR name~.*gcp-commit-.*) AND creationTimestamp<$timestamp" --format="value(name,zone.scope())" --project=$projectName` -echo "Instances to be deleted : $list" -while IFS= read -r line -do - values=( $line ) - name="${values[0]}" - zone="${values[1]}" - gcloud compute instance-groups managed delete $name --project=$projectName --zone=$zone --quiet --format text -done <<< "$list" - -echo "Finished deleting all Instances" \ No newline at end of file diff --git a/.circleci/scripts/sonar_check_quality_gate.pl b/.circleci/scripts/sonar_check_quality_gate.pl deleted file mode 100644 index df0a28e9f0aa..000000000000 --- a/.circleci/scripts/sonar_check_quality_gate.pl +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use v5.18; - -use JSON::Parse "parse_json"; -use Mozilla::CA; -use LWP::UserAgent; -use HTTP::Request::Common; -use URI; -use Env; - -no warnings 'experimental::smartmatch'; - -# Define CircleCI Environment Variable Constants -use constant { - CIRCLE_BRANCH => "CIRCLE_BRANCH", - CIRCLE_TAG => "CIRCLE_TAG", - SONAR_SERVER => "SONAR_SERVER", - SONAR_TOKEN => "SONAR_TOKEN", - SONAR_PROJECT_KEY => "SONAR_PROJECT_KEY", - SONAR_TASK_ID => "SONAR_TASK_ID" -}; - -# Define Default Values for Sonar Constants -use constant { - DEFAULT_SONAR_SERVER => "https://sonarcloud.io", - SONAR_DASHBOARD_LINK_FILE => "/tmp/sonar_dashboard_link.txt" -}; - -# Define Sonar Template Constants -use constant { - SONAR_QG_PRJ_STATUS_URI => "api/qualitygates/project_status", - SONAR_CE_TASK_STATUS_URI => "api/ce/task", - SONAR_DASHBOARD_URI => "dashboard", - SONAR_ARG_DASHBOARD_ID => "id", - SONAR_ARG_BRANCH => "branch", - SONAR_ARG_PROJ_KEY => "projectKey", - SONAR_ARG_ANALYSIS_ID => "analysisId", - SONAR_ARG_TASK_ID => "id" -}; - -# Define Task Status Constants -use constant { - SONAR_TSK_SUCCESS => "SUCCESS", - SONAR_TSK_FAILED => "FAILED", - SONAR_TSK_CANCELED => "CANCELED" -}; - -# Define QG Status Constants -use constant { - SONAR_QG_OK => "OK", - SONAR_QG_WARN => "WARN", - SONAR_QG_ERROR => "ERROR", - SONAR_QG_NONE => "NONE" -}; - -# Define Exit Code Constants -use constant { - EX_OK => 0, - EX_BRANCH_MISSING => 8, - EX_PROJECT_KEY_MISSING => 9, - EX_TOKEN_MISSING => 10, - EX_HTTP_ERROR => 11, - EX_TASK_ID_MISSING => 12, - EX_TASK_LOOKUP_FAILED => 13, - EX_ANALYSIS_ID_MISSING => 14, - EX_QUALITY_GATE_FAILED => 63 -}; - - -# Define Subroutines - -sub sonar_create_url { - my ($path, $query) = @_; - my $server = $ENV{&SONAR_SERVER} || DEFAULT_SONAR_SERVER; - - my $uri = URI->new($server); - $uri->path($path); - $uri->query_form($query); - - return $uri; -} - -sub sonar_create_qg_project_url { - my $branch = $ENV{&CIRCLE_BRANCH}; - my $projectKey = $ENV{&SONAR_PROJECT_KEY}; - - if (!defined($branch) || chomp($branch) eq '') { - sonar_display_message(EX_BRANCH_MISSING); - exit(EX_BRANCH_MISSING); - } - - if (!defined($projectKey) || chomp($projectKey) eq '') { - sonar_display_message(EX_PROJECT_KEY_MISSING); - exit(EX_PROJECT_KEY_MISSING); - } - - return sonar_create_url(SONAR_QG_PRJ_STATUS_URI, { - &SONAR_ARG_BRANCH => $branch, - &SONAR_ARG_PROJ_KEY => $projectKey - }); -} - -sub sonar_create_qg_analysis_url { - my ($analysisId) = @_; - - if (!defined($analysisId) || chomp($analysisId) eq '') { - sonar_display_message(EX_ANALYSIS_ID_MISSING); - exit(EX_ANALYSIS_ID_MISSING); - } - - return sonar_create_url(SONAR_QG_PRJ_STATUS_URI, { - &SONAR_ARG_ANALYSIS_ID => $analysisId - }); -} - -sub sonar_create_task_lookup_url { - my $taskId = $ENV{&SONAR_TASK_ID}; - - if (!defined($taskId) || chomp($taskId) eq '') { - sonar_display_message(EX_TASK_ID_MISSING); - exit(EX_TASK_ID_MISSING); - } - - return sonar_create_url(SONAR_CE_TASK_STATUS_URI, { - &SONAR_ARG_TASK_ID => $taskId - }); -} - -sub sonar_create_dashboard_url { - my $projectKey = $ENV{&SONAR_PROJECT_KEY}; - my $branch = $ENV{&CIRCLE_BRANCH}; - - my %params = ( - &SONAR_ARG_BRANCH => $branch, - &SONAR_ARG_DASHBOARD_ID => $projectKey - ); - - if (!defined($projectKey) || chomp($projectKey) eq '') { - delete($params{&SONAR_ARG_DASHBOARD_ID}); - } - - if (!defined($branch) || chomp($branch) eq '') { - delete($params{&SONAR_ARG_BRANCH}); - } - - my $uri = sonar_create_url(SONAR_DASHBOARD_URI, { %params }); - - open(my $fh, '>', &SONAR_DASHBOARD_LINK_FILE); - print $fh $uri->as_string; - close($fh); - - return $uri; -} - -sub sonar_authenticated_request { - my ($uri) = @_; - my $token = $ENV{&SONAR_TOKEN}; - - if (!defined($token) || chomp($token) eq '') { - exit(EX_TOKEN_MISSING); - } - - my $req = GET $uri; - $req->authorization_basic($token, ""); - - return $req; -} - -sub sonar_web_poll { - my ($ua, $req, $limit, $testFn) = @_; - my $limitCtr = 0; - - if (!defined($testFn)) { - $testFn = sub { - my ($req, $resp) = @_; - return $resp->code == 404; - }; - } - - if (!defined($limit)) { - $limit = 5; - } - - my $resp = $ua->request($req); - - while ($testFn->($req, $resp) && ++$limitCtr <= $limit) { - sleep 1; - $resp = $ua->request($req); - } - - if (HTTP::Status::is_error($resp->code)) { - print "\n\nRequest:\n"; - print '-' x 120; - print "\n"; - print $req->as_string(); - - print "\n\nResponse:\n"; - print '-' x 120; - print "\n"; - print $resp->as_string(); - - sonar_display_message(EX_HTTP_ERROR); - exit(EX_HTTP_ERROR); - } - - return $resp; -} - -sub sonar_resolve_analysis_id { - my ($ua) = @_; - - my $uri = sonar_create_task_lookup_url; - my $req = sonar_authenticated_request($uri); - my $resp = sonar_web_poll($ua, $req, 20, sub { - my ($req, $resp) = @_; - - if ($resp->code == 404) { - return 1; - } - elsif (HTTP::Status::is_error($resp->code)) { - return 0; - } - - my $payload = parse_json($resp->content); - return !($payload->{task}->{status} ~~ [ &SONAR_TSK_SUCCESS, &SONAR_TSK_CANCELED, &SONAR_TSK_FAILED ]); - }); - - my $result = parse_json($resp->content); - - if ($result->{task}->{status} eq SONAR_TSK_SUCCESS) { - return $result->{task}->{analysisId}; - } - - sonar_display_message(EX_TASK_LOOKUP_FAILED); - exit(EX_TASK_LOOKUP_FAILED); -} - -sub sonar_check_quality_gate { - my ($ua, $analysisId) = @_; - - my $uri = sonar_create_qg_analysis_url($analysisId); - my $req = sonar_authenticated_request($uri); - my $resp = sonar_web_poll($ua, $req, 5); - - my $result = parse_json($resp->content); - - return $result->{projectStatus}->{status} ~~ [ &SONAR_QG_OK, &SONAR_QG_NONE ]; -} - -sub sonar_display_message { - my ($errorCode) = @_; - my $uri = sonar_create_dashboard_url; - - print "\n\n"; - print '-' x 150; - print "\n"; - if ($errorCode == EX_OK) { - print "\tSONAR QUALITY GATE:\tPASSED\n\tDashboard URL:\t\t" . $uri->as_string . "\n"; - } - elsif ($errorCode == EX_QUALITY_GATE_FAILED) { - print "\tSONAR QUALITY GATE:\tFAILED\n\tDashboard URL:\t\t" . $uri->as_string . "\n"; - } - else { - print "\tSONAR QUALITY GATE:\tUNHANDLED LOOKUP ERROR\n\tDIAGNOSTIC ERROR CODE:\t$errorCode\n"; - } - print '-' x 150; - print "\n"; -} - -sub main { - # Create Global HTTPS Enabled UA - my $ua = LWP::UserAgent->new( - protocols_allowed => [ 'https' ], - timeout => 10, - ssl_verify_hostname => 1 - ); - - # Provide basic initialization of the UserAgent - $ua->env_proxy; - $ua->agent("Swirlds/1.0"); - - my $analysisId = sonar_resolve_analysis_id($ua); - - if (!sonar_check_quality_gate($ua, $analysisId)) { - sonar_display_message(EX_QUALITY_GATE_FAILED); - exit(EX_QUALITY_GATE_FAILED); - } - - sonar_display_message(EX_OK); - exit(EX_OK); -} - -# Execute the main subroutine -main; diff --git a/.github/scripts/healthcheck.sh b/.github/scripts/healthcheck.sh deleted file mode 100644 index 03644dd1e9a2..000000000000 --- a/.github/scripts/healthcheck.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -nodeReady=1 -echo "Waiting for node to finish initializing..." -until [ $nodeReady -eq 0 ] -do - grep 'Now current platform status = ACTIVE' ../../compose-network/node0/output/hgcaa.log - nodeReady=$? - sleep 3 -done diff --git a/.gitignore.hedera b/.gitignore.hedera deleted file mode 100644 index 1b6d3d5c1aa1..000000000000 --- a/.gitignore.hedera +++ /dev/null @@ -1,167 +0,0 @@ -# Key files -*.pem -!hedera-node/test-clients/src/main/resource/genesis.pem -!hedera-node/test-clients/yahcli/localhost/keys/*.pem -!hapi-utils/src/test/resources/vectors/genesis.pem -!hedera-node/data/onboard/devGenesisKeypair.pem -*.pass -!hedera-node/test-clients/yahcli/localhost/keys/*.pass -*.words -*.hexed - -# Yahcli -hedera-node/test-clients/yahcli/assets/yahcli.jar -hedera-node/test-clients/yahcli/localhost/sysfiles -hedera-node/test-clients/yahcli/localhost/accounts -hedera-node/test-clients/yahcli/localhost/tokens -hedera-node/test-clients/yahcli/localhost/topics -hedera-node/test-clients/yahcli/cost-snapshots -hedera-node/test-clients/yahcli/run/test/local/ - -# VIM -*.swp -*.swo - -# Docker Compose runtime -compose-network/ - -# IDEA files -.idea -*.iml -.settings - -# Logs/crash data -logs/ -hedera-node/test-clients/devops-utils/validation-scenarios/logs/ -hedera-node/test-clients/cost-snapshots -*.log -hs_err_pid* - -# Maven wrapper -.mvn/wrapper/maven-wrapper.jar - -# Local runtime assets -hedera-node/swirlds-tmp - -hedera-node/test-clients/yahcli/*.jar -hedera-node/test-clients/yahcli/assets/*.jar - -*.sh -!run/generate-changelog.sh -!run/new-release-branch.sh -!run/reset-data-saved-from.sh -!hapi-proto/add-copyright.sh -!hapi-proto/gen-proto-docs.sh -!hedera-node/data/backup/*.sh -!hedera-node/configuration/compose/init.db/*.sh -!hedera-node/test-clients/yahcli/run/*.sh -!hedera-node/test-clients/yahcli/assets/*.sh -!hedera-node/test-clients/yahcli/run/test/local/*.sh - -*.json -!hapi-fees/src/main/resources/*.json -!hapi-fees/src/test/resources/*.json -!hapi-utils/src/test/resources/bootstrap/*.json -!hapi-utils/src/test/resources/sysfiles/*.json -!hedera-node/hedera-mono-service/src/test/resources/fees/feeSchedules.json -!hedera-node/hedera-mono-service/src/test/resources/legacyAccounts.json -!hedera-node/hedera-mono-service/src/test/resources/bootstrap/*.json -!hedera-node/hedera-mono-service/src/main/resources/*.json -!hedera-node/hedera-mono-service/src/test/resources/testfiles/expiry-throttle.json -!hedera-node/hedera-mono-service/src/test/resources/R4FeeSchedule.json -!hedera-node/configuration/**/*.json -!hedera-node/test-clients/src/main/resource/FeeSchedule.json -!hedera-node/test-clients/src/main/resource/testSystemFiles/*.json -!hedera-node/test-clients/yahcli/run/test/local/assets/*.json -.circleci/*.yml-excerpt -hedera-node/data/jasperdb/ -hedera-node/temp/ -hedera-node/hedera-mono-service/temp/ -hedera-node/swirlds-tmp/ -hedera-node/hedera-mono-service/swirlds-tmp/ -hedera-node/*.csv -hedera-node/swirlds.jar -hedera-node/build.sh -hedera-node/settingsUsed.txt -hedera-node/data/accountBalances/ -hedera-node/database/ -hedera-node/databases/ -hedera-node/data/apps/ -hedera-node/data/diskFs/ -hedera-node/data/config/*.bin -hedera-node/data/config/*.properties -hedera-node/data/lib/ -hedera-node/data/onboard/afterMigration.csv -hedera-node/data/onboard/afterMigration_created.csv -hedera-node/data/onboard/afterMigration_updated.csv -hedera-node/data/onboard/beforeMigration.csv -hedera-node/data/onboard/exchangeRate.txt -hedera-node/data/onboard/exportedAccount.txt -hedera-node/data/onboard/feeSchedule.txt -hedera-node/data/onboard/GenesisPub32Key.txt -hedera-node/data/recordstreams/ -hedera-node/data/saved/ -hedera-node/data/jdb/ -hedera-node/output/ -hedera-node/target/ -hedera-node/src/test/resources/diskFs -hedera-node/hedera-mono-service/src/test/resources/diskFs -hedera-node/src/test/resources/upgrade -hedera-node/hedera-mono-service/src/test/resources/upgrade -hedera-node/forensics -hedera-node/pgdata -hedera-node/temp -!hedera-node/forensics/start-investigation.py -hapi-proto/target/ -hapi-proto/src/main/proto/doc/index.html -hedera-node/test-clients/logs/ -hedera-node/test-clients/puv/*.jar -hedera-node/test-clients/persistent-entities/ -hedera-node/test-clients/keys/ -hedera-node/test-clients/*.bin -hedera-node/test-clients/output/ -hedera-node/test-clients/devops-utils/validation-scenarios/fees/ -hedera-node/test-clients/devops-utils/validation-scenarios/output/ -hedera-node/test-clients/devops-utils/validation-scenarios/cost-snapshots/ -hedera-node/test-clients/devops-utils/validation-scenarios/ValidationScenarios.jar -hedera-node/test-clients/src/main/resource/TestnetStartupAccount.txt -hedera-node/test-clients/src/main/resource/MainnetStartupAccount.txt -hedera-node/test-clients/src/main/resource/StableTestnetStartupAccount.txt -hedera-node/test-clients/src/main/resource/StableTestnetAccount50StartupAccount.txt -hedera-node/test-clients/saved/ -hedera-node/test-clients/target/ -hedera-node/test-clients/record-snapshots/ -hedera-node/test-clients/remote-system-files/*.bin -hedera-node/test-clients/remote-system-files/*.txt -hedera-node/test-clients/src/main/resource/Mainnet*StartUp.txt -hedera-node/test-clients/src/main/resource/MainNet*StartUp.txt -hedera-node/test-clients/src/main/resource/TestNet*StartUp.txt -hedera-node/test-clients/src/main/resource/testfiles/*.abi -hedera-node/test-clients/devops-utils/validation-scenarios/files/*-api-permission.properties -hedera-node/test-clients/devops-utils/validation-scenarios/files/*-application.properties -hedera-node/test-clients/devops-utils/validation-scenarios/files/*-exchangeRates.json - -# Maven -target - -# Gradle -.gradle -build - -# Mac system -.DS_Store - -# SonarQube -.scannerwork - -.project - -/hedera-node/hapi/hedera-protobufs/ -/hedera-node/hapi/checkouts.bin - -# Docker setup -!docker/*.sh -/docker/.env -.env -Dockerfile -.dockerignore diff --git a/.gitignore.platform b/.gitignore.platform deleted file mode 100644 index 197ab5eddb49..000000000000 --- a/.gitignore.platform +++ /dev/null @@ -1,462 +0,0 @@ -######################################################################################################################## -# Automatically Generated Ignore Defintions -######################################################################################################################## - -### Java template -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -### Maven template -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar - -### Gradle template -.gradle -**/build/ -!src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Cache of project -.gradletasknamecache - -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties - - -### VisualStudioCode template -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -### Go template -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -### Windows template -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -### NetBeans template -**/nbproject/private/ -**/nbproject/Makefile-*.mk -**/nbproject/Package-*.bash -build/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ - -### JetBrains template -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/swagger-settings.xml -.idea/**/shelf -.idea/**/codeStyles - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -.idea/artifacts -.idea/compiler.xml -.idea/jarRepositories.xml -.idea/modules.xml -.idea/*.iml -.idea/modules -*.iml -*.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -# Custom JetBrains IntelliJ IDEA Project File Excludes -**/.idea/runConfigurations.xml -**/.idea/misc.xml -**/.idea/sonarlint*.xml -**/.idea/sonarlint/ -**/.idea/encodings.xml -**/.idea/jpa-buddy.xml - -#Saveaction plugin -**/.idea/saveactions_settings.xml - -### Linux template -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### Vim template -# Swap -[._]*.s[a-v][a-z] -!*.svg # comment out if you don't need vector files -[._]*.sw[a-p] -[._]s[a-rt-v][a-z] -[._]ss[a-gi-z] -[._]sw[a-p] - -# Session -Session.vim -Sessionx.vim - -# Temporary -.netrwhist -# Auto-generated tag files -tags -# Persistent undo -[._]*.un~ - -### Gradle template -.gradle -**/build/ -!src/**/build/ - -# Ignore Gradle GUI config -gradle-app.setting - -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar - -# Cache of project -.gradletasknamecache - -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties - -### macOS template -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### GPG template -secring.* - - -### Python template -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - - -# Distribution / packaging -.Python -develop-eggs/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - - -######################################################################################################################## -# Swirlds, Hedera, & JRS Specific Ignores -######################################################################################################################## - -# Swirlds Files -*.swh -*.evts -sdk/data/saved -sdk/data/lib -sdk/data/repos -sdk/data/apps -sdk/data/stats -sdk/*.csv -sdk/settingsUsed.txt -sdk/configsUsed.txt -sdk/metricsDoc.tsv -*/platform-components.mermaid - -# Hedera Files -*.rcd - -# Keys -*.pub -*.pem -*.pfx - -# Temporary Test Folders (Poorly Behaved Tests) -**/swirlds-tmp -swirlds-platform-core/data/** - -# Test Files (Poorly Behaved Tests) -*.json.gz -saved.txt diff --git a/platform-sdk/LICENSE b/platform-sdk/LICENSE deleted file mode 100644 index f49a4e16e68b..000000000000 --- a/platform-sdk/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file From ae9b45320a5ec0c69e1b3cd7630edbeec2f4ecc3 Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Wed, 27 Dec 2023 17:11:57 -0600 Subject: [PATCH 52/80] chore(ci): gate the expensive pull request checks behind the spotless & module info checks (#10657) Signed-off-by: Nathan Klick --- .../node-flow-pull-request-checks.yaml | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/node-flow-pull-request-checks.yaml b/.github/workflows/node-flow-pull-request-checks.yaml index ac736e483990..d08534a57b3a 100644 --- a/.github/workflows/node-flow-pull-request-checks.yaml +++ b/.github/workflows/node-flow-pull-request-checks.yaml @@ -76,7 +76,8 @@ jobs: name: Unit Tests uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: true @@ -93,7 +94,8 @@ jobs: name: E2E Tests uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -111,7 +113,8 @@ jobs: name: Integration Tests uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -129,7 +132,8 @@ jobs: name: HAPI Tests (Misc) uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -148,7 +152,8 @@ jobs: name: HAPI Tests (Crypto) uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -167,7 +172,8 @@ jobs: name: HAPI Tests (Token) uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -186,7 +192,8 @@ jobs: name: HAPI Tests (Smart Contract) uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -205,7 +212,8 @@ jobs: name: HAPI Tests (Time Consuming) uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless with: custom-job-label: Standard enable-unit-tests: false @@ -224,7 +232,8 @@ jobs: name: JRS Panel uses: ./.github/workflows/zxc-jrs-regression.yaml needs: - - build + - dependency-check + - spotless if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository }} with: custom-job-name: "Platform SDK" @@ -248,7 +257,8 @@ jobs: name: Snyk Scan uses: ./.github/workflows/node-zxc-compile-application-code.yaml needs: - - build + - dependency-check + - spotless if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository }} with: custom-job-label: Standard @@ -267,7 +277,8 @@ jobs: name: Artifact Determinism uses: ./.github/workflows/zxc-verify-gradle-build-determinism.yaml needs: - - build + - dependency-check + - spotless if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository }} with: ref: ${{ github.event.inputs.ref || '' }} From fcae54a6d3070fe729dda59b0dc88f7b291c99f9 Mon Sep 17 00:00:00 2001 From: Stoyan Panayotov Date: Thu, 28 Dec 2023 04:53:24 +0200 Subject: [PATCH 53/80] chore: Add test for create2 (#7559) Signed-off-by: Stoyan Panayotov Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- ...DeleteCapableTransactionRecordBuilder.java | 5 +- .../SingleTransactionRecordBuilderImpl.java | 4 +- .../contract/impl/exec/QueryModule.java | 4 +- .../contract/impl/exec/TransactionModule.java | 5 +- .../CustomSelfDestructOperation.java | 3 +- .../scope/HandleHederaNativeOperations.java | 11 +- .../exec/scope/HederaNativeOperations.java | 6 +- .../scope/QueryHederaNativeOperations.java | 5 +- .../impl/exec/utils/FrameBuilder.java | 24 ++-- .../contract/impl/exec/utils/FrameUtils.java | 18 +++ .../handlers/EthereumTransactionHandler.java | 3 +- .../contract/impl/hevm/HederaEvmContext.java | 9 +- .../impl/hevm/HederaWorldUpdater.java | 6 +- .../impl/state/DispatchingEvmFrameState.java | 11 +- .../contract/impl/state/EvmFrameState.java | 5 +- .../impl/state/ProxyWorldUpdater.java | 6 +- .../contract/impl/test/TestHelpers.java | 15 ++- .../impl/test/exec/TransactionModuleTest.java | 19 +++ .../test/exec/gas/CustomGasChargingTest.java | 2 +- .../CustomSelfDestructOperationTest.java | 5 +- .../HandleHederaNativeOperationsTest.java | 23 +++- .../QueryHederaNativeOperationsTest.java | 6 +- .../test/exec/utils/FrameBuilderTest.java | 9 +- .../impl/test/exec/utils/FrameUtilsTest.java | 13 ++ .../EthereumTransactionHandlerTest.java | 3 - .../state/DispatchingEvmFrameStateTest.java | 24 ++-- .../test/state/ProxyWorldUpdaterTest.java | 4 +- .../opcodes/Create2OperationSuite.java | 2 +- .../crypto/AutoAccountCreationSuite.java | 101 ++++++++++++++- .../suites/leaky/LeakyContractTestsSuite.java | 116 +++++++++++++++++- ...ate2FactoryWithSelfDestructingContract.bin | 1 + ...te2FactoryWithSelfDestructingContract.json | 87 +++++++++++++ ...ate2FactoryWithSelfDestructingContract.sol | 70 +++++++++++ 33 files changed, 561 insertions(+), 64 deletions(-) create mode 100644 hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin create mode 100644 hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json create mode 100644 hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/record/DeleteCapableTransactionRecordBuilder.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/record/DeleteCapableTransactionRecordBuilder.java index 9afbc5fc2863..6b4394f353e4 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/record/DeleteCapableTransactionRecordBuilder.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/record/DeleteCapableTransactionRecordBuilder.java @@ -51,11 +51,10 @@ public interface DeleteCapableTransactionRecordBuilder extends SingleTransaction /** * Adds a beneficiary for a deleted account. + * * @param deletedAccountID the deleted account ID * @param beneficiaryForDeletedAccount the beneficiary account ID - * @return the builder */ - @NonNull - SingleTransactionRecordBuilder addBeneficiaryForDeletedAccount( + void addBeneficiaryForDeletedAccount( @NonNull AccountID deletedAccountID, @NonNull AccountID beneficiaryForDeletedAccount); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index df10d82fae95..b5df857487a1 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -1079,16 +1079,14 @@ public SingleTransactionRecordBuilderImpl addContractBytecode( * * @param deletedAccountID the deleted account ID * @param beneficiaryForDeletedAccount the beneficiary account ID - * @return the builder */ @Override @NonNull - public SingleTransactionRecordBuilderImpl addBeneficiaryForDeletedAccount( + public void addBeneficiaryForDeletedAccount( @NonNull final AccountID deletedAccountID, @NonNull final AccountID beneficiaryForDeletedAccount) { requireNonNull(deletedAccountID, "deletedAccountID must not be null"); requireNonNull(beneficiaryForDeletedAccount, "beneficiaryForDeletedAccount must not be null"); deletedAccountBeneficiaries.put(deletedAccountID, beneficiaryForDeletedAccount); - return this; } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java index 81e0a0af1c6b..da0000d56b0c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java @@ -113,12 +113,14 @@ static HederaEvmContext provideHederaEvmContext( @NonNull final HederaEvmBlocks hederaEvmBlocks, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator) { + // Use null for the DeleteCapableTransactionRecordBuilder, as selfdestruct is illegal in a static context return new HederaEvmContext( hederaOperations.gasPriceInTinybars(), true, hederaEvmBlocks, tinybarValues, - systemContractGasCalculator); + systemContractGasCalculator, + null); } @Binds diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java index 01581b7fb894..b365350ac8db 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java @@ -51,6 +51,7 @@ import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.FunctionalityResourcePrices; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.data.HederaConfig; import dagger.Binds; import dagger.Module; @@ -132,6 +133,7 @@ static ActionSidecarContentTracer provideActionSidecarContentTracer() { @Provides @TransactionScope static HederaEvmContext provideHederaEvmContext( + @NonNull final HandleContext context, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator, @NonNull final HederaOperations hederaOperations, @@ -141,7 +143,8 @@ static HederaEvmContext provideHederaEvmContext( false, hederaEvmBlocks, tinybarValues, - systemContractGasCalculator); + systemContractGasCalculator, + context.recordBuilder(DeleteCapableTransactionRecordBuilder.class)); } @Provides diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomSelfDestructOperation.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomSelfDestructOperation.java index d4b94310f856..674b5acd92df 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomSelfDestructOperation.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomSelfDestructOperation.java @@ -72,7 +72,8 @@ public OperationResult execute(@NonNull final MessageFrame frame, @NonNull final final var tbdAddress = frame.getRecipientAddress(); final var proxyWorldUpdater = (ProxyWorldUpdater) frame.getWorldUpdater(); // Enforce Hedera-specific restrictions on account deletion - final var maybeHaltReason = proxyWorldUpdater.tryTrackingDeletion(tbdAddress, beneficiaryAddress); + final var maybeHaltReason = + proxyWorldUpdater.tryTrackingSelfDestructBeneficiary(tbdAddress, beneficiaryAddress, frame); if (maybeHaltReason.isPresent()) { return haltFor(null, 0, maybeHaltReason.get()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java index 5b90688026e9..4398f9ff5033 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaNativeOperations.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.selfDestructBeneficiariesFor; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.LAZY_CREATION_MEMO; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthHollowAccountCreation; import static java.util.Objects.requireNonNull; @@ -41,6 +42,7 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Inject; +import org.hyperledger.besu.evm.frame.MessageFrame; /** * A fully-mutable {@link HederaNativeOperations} implemented with a {@link HandleContext}. @@ -166,8 +168,13 @@ public void finalizeHollowAccountAsContract(@NonNull final Bytes evmAddress) { * {@inheritDoc} */ @Override - public void trackDeletion(final long deletedNumber, final long beneficiaryNumber) { - // TODO - implement after merging upstream + public void trackSelfDestructBeneficiary( + final long deletedNumber, final long beneficiaryNumber, @NonNull final MessageFrame frame) { + requireNonNull(frame); + selfDestructBeneficiariesFor(frame) + .addBeneficiaryForDeletedAccount( + AccountID.newBuilder().accountNum(deletedNumber).build(), + AccountID.newBuilder().accountNum(beneficiaryNumber).build()); } @Override diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java index effcc2059929..290cea7737b9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaNativeOperations.java @@ -34,6 +34,7 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import org.hyperledger.besu.evm.frame.MessageFrame; /** * Provides Hedera operations using PBJ types to allow a {@link DispatchingEvmFrameState} to access and change @@ -199,13 +200,14 @@ ResponseCodeEnum transferWithReceiverSigCheck( long amount, long fromEntityNumber, long toEntityNumber, @NonNull VerificationStrategy strategy); /** - * Tracks the deletion of a contract and the beneficiary that should receive any staking awards otherwise + * Tracks the self-destruction of a contract and the beneficiary that should receive any staking awards otherwise * earned by the deleted contract. * * @param deletedNumber the number of the deleted contract * @param beneficiaryNumber the number of the beneficiary + * @param frame the frame in which to track the self-destruct */ - void trackDeletion(long deletedNumber, final long beneficiaryNumber); + void trackSelfDestructBeneficiary(long deletedNumber, long beneficiaryNumber, @NonNull MessageFrame frame); /** * Checks if the given transfer operation uses custom fees. diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java index 18bcd310f704..ba4918b8d910 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaNativeOperations.java @@ -28,6 +28,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; import javax.inject.Inject; +import org.hyperledger.besu.evm.frame.MessageFrame; /** * A read-only {@link HederaNativeOperations} based on a {@link QueryContext}. @@ -135,9 +136,11 @@ public ResponseCodeEnum transferWithReceiverSigCheck( * * @param deletedNumber the number of the deleted contract * @param beneficiaryNumber the number of the beneficiary + * @param frame the frame in which to track the beneficiary */ @Override - public void trackDeletion(final long deletedNumber, final long beneficiaryNumber) { + public void trackSelfDestructBeneficiary( + final long deletedNumber, final long beneficiaryNumber, @NonNull final MessageFrame frame) { throw new UnsupportedOperationException("Cannot track deletion in query context"); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java index b5110e91ad45..6b67dff5c387 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java @@ -20,6 +20,7 @@ import static com.hedera.hapi.streams.SidecarType.CONTRACT_STATE_CHANGE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SELF_DESTRUCT_BENEFICIARIES; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TINYBAR_VALUES_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TRACKER_CONTEXT_VARIABLE; @@ -34,6 +35,7 @@ import com.hedera.node.config.data.LedgerConfig; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; @@ -113,18 +115,18 @@ public MessageFrame buildInitialFrameWith( @SuppressWarnings("unchecked") private Map contextVariablesFrom( @NonNull final Configuration config, @NonNull final HederaEvmContext context) { - final var contractsConfig = config.getConfigData(ContractsConfig.class); - final var needsStorageTracker = contractsConfig.sidecars().contains(CONTRACT_STATE_CHANGE); - final var contextEntries = new Map.Entry[needsStorageTracker ? 5 : 4]; - contextEntries[0] = Map.entry(CONFIG_CONTEXT_VARIABLE, config); - contextEntries[1] = Map.entry(TINYBAR_VALUES_CONTEXT_VARIABLE, context.tinybarValues()); - contextEntries[2] = - Map.entry(SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE, context.systemContractGasCalculator()); - contextEntries[3] = Map.entry(PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE, new PropagatedCallFailureReference()); - if (needsStorageTracker) { - contextEntries[4] = Map.entry(TRACKER_CONTEXT_VARIABLE, new StorageAccessTracker()); + final Map contextEntries = new HashMap<>(); + contextEntries.put(CONFIG_CONTEXT_VARIABLE, config); + contextEntries.put(TINYBAR_VALUES_CONTEXT_VARIABLE, context.tinybarValues()); + contextEntries.put(SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE, context.systemContractGasCalculator()); + contextEntries.put(PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE, new PropagatedCallFailureReference()); + if (config.getConfigData(ContractsConfig.class).sidecars().contains(CONTRACT_STATE_CHANGE)) { + contextEntries.put(TRACKER_CONTEXT_VARIABLE, new StorageAccessTracker()); } - return Map.ofEntries((Map.Entry[]) contextEntries); + if (context.isDeleteCapable()) { + contextEntries.put(SELF_DESTRUCT_BENEFICIARIES, context.deleteCapableTransactionRecordBuilder()); + } + return contextEntries; } private MessageFrame finishedAsCreate( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java index 4a75b9adab31..d455cc87f547 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java @@ -27,6 +27,7 @@ import com.hedera.node.app.service.contract.impl.infra.StorageAccessTracker; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; @@ -39,6 +40,7 @@ public class FrameUtils { public static final String CONFIG_CONTEXT_VARIABLE = "contractsConfig"; public static final String TRACKER_CONTEXT_VARIABLE = "storageAccessTracker"; public static final String TINYBAR_VALUES_CONTEXT_VARIABLE = "tinybarValues"; + public static final String SELF_DESTRUCT_BENEFICIARIES = "selfDestructBeneficiaries"; public static final String PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE = "propagatedCallFailure"; public static final String SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE = "systemContractGasCalculator"; @@ -109,6 +111,22 @@ private static PropagatedCallFailureReference propagatedCallFailureReference(@No return initialFrameOf(frame).getContextVariable(TINYBAR_VALUES_CONTEXT_VARIABLE); } + /** + * Returns a record builder able to track the beneficiaries of {@code SELFDESTRUCT} operations executed + * so far in the frame's EVM transaction. + * + *

    Note it does not matter if we track a {@code SELFDESTRUCT} that is later reverted; we just need to + * be sure that for the committed self-destructs, we know what beneficiary they used so the staking logic + * can redirect rewards as appropriate. + * + * @param frame the frame whose EVM transaction we are tracking beneficiaries in + * @return the record builder able to track beneficiary ids + */ + public static @NonNull DeleteCapableTransactionRecordBuilder selfDestructBeneficiariesFor( + @NonNull final MessageFrame frame) { + return requireNonNull(initialFrameOf(frame).getContextVariable(SELF_DESTRUCT_BENEFICIARIES)); + } + public static @NonNull SystemContractGasCalculator systemContractGasCalculatorOf( @NonNull final MessageFrame frame) { return initialFrameOf(frame).getContextVariable(SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index f57b5fdca614..f2e9181f6a89 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -97,8 +97,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var outcome = component.contextTransactionProcessor().call(); final var recordBuilder = context.recordBuilder(EthereumTransactionRecordBuilder.class) - .ethereumHash(Bytes.wrap(ethTxData.getEthereumHash())) - .status(outcome.status()); + .ethereumHash(Bytes.wrap(ethTxData.getEthereumHash())); if (ethTxData.hasToAddress()) { // The Ethereum transaction was a top-level MESSAGE_CALL recordBuilder.contractID(outcome.recipientId()).contractCallResult(outcome.result()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java index 39c04282333c..f12208090f12 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java @@ -18,6 +18,8 @@ import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; +import edu.umd.cs.findbugs.annotations.Nullable; import org.hyperledger.besu.evm.frame.BlockValues; public record HederaEvmContext( @@ -25,7 +27,8 @@ public record HederaEvmContext( boolean staticCall, HederaEvmBlocks blocks, TinybarValues tinybarValues, - SystemContractGasCalculator systemContractGasCalculator) { + SystemContractGasCalculator systemContractGasCalculator, + @Nullable DeleteCapableTransactionRecordBuilder deleteCapableTransactionRecordBuilder) { public BlockValues blockValuesOf(final long gasLimit) { return blocks.blockValuesOf(gasLimit); } @@ -33,4 +36,8 @@ public BlockValues blockValuesOf(final long gasLimit) { public boolean isNoopGasContext() { return staticCall || gasPrice == 0; } + + public boolean isDeleteCapable() { + return deleteCapableTransactionRecordBuilder != null; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java index 8ded1f4521a5..e6aa8f69e278 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java @@ -177,11 +177,13 @@ Optional tryTransfer( * Attempts to track the given deletion of an account with the designated beneficiary, returning an optional * {@link ExceptionalHaltReason} to indicate whether the deletion could be successfully tracked. * - * @param deleted the address of the account being deleted + * @param deleted the address of the account being deleted * @param beneficiary the address of the beneficiary of the deletion + * @param frame * @return an optional {@link ExceptionalHaltReason} with the reason deletion could not be tracked */ - Optional tryTrackingDeletion(@NonNull Address deleted, @NonNull Address beneficiary); + Optional tryTrackingSelfDestructBeneficiary( + @NonNull Address deleted, @NonNull Address beneficiary, MessageFrame frame); /** * Given the HAPI operation initiating a top-level {@code CONTRACT_CREATION} message, sets up the diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java index a8ad3ab75748..539dcbf23405 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java @@ -67,6 +67,7 @@ import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; /** * An implementation of {@link EvmFrameState} that uses {@link WritableKVState}s to manage @@ -406,8 +407,11 @@ public Optional tryLazyCreation(@NonNull final Address ad * {@inheritDoc} */ @Override - public Optional tryTrackingDeletion( - @NonNull final Address deleted, @NonNull final Address beneficiary) { + public Optional tryTrackingSelfDestructBeneficiary( + @NonNull final Address deleted, @NonNull final Address beneficiary, @NonNull final MessageFrame frame) { + requireNonNull(deleted); + requireNonNull(beneficiary); + requireNonNull(frame); if (deleted.equals(beneficiary)) { return Optional.of(SELF_DESTRUCT_TO_SELF); } @@ -423,7 +427,8 @@ public Optional tryTrackingDeletion( if (deletedAccount.numPositiveTokenBalances() > 0) { return Optional.of(CONTRACT_STILL_OWNS_NFTS); } - nativeOperations.trackDeletion(deletedAccount.number, ((ProxyEvmAccount) beneficiaryAccount).number); + nativeOperations.trackSelfDestructBeneficiary( + deletedAccount.number, ((ProxyEvmAccount) beneficiaryAccount).number, frame); return Optional.empty(); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java index 1de3a6f14712..734b8c5062ff 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/EvmFrameState.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; /** * Exposes the full Hedera state that may be read and changed directly from an EVM frame, @@ -109,9 +110,11 @@ Optional tryTransfer( * * @param deleted the address of the account being deleted * @param beneficiary the address of the beneficiary of the deletion + * @param frame the frame in which to track the deletion * @return an optional {@link ExceptionalHaltReason} with the reason deletion could not be tracked */ - Optional tryTrackingDeletion(@NonNull Address deleted, @NonNull Address beneficiary); + Optional tryTrackingSelfDestructBeneficiary( + @NonNull Address deleted, @NonNull Address beneficiary, @NonNull MessageFrame frame); /** * Returns the read-only account with the given address, or {@code null} if the account is missing, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index 6bdf1f079434..cad6d34a7e9b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -313,9 +313,9 @@ public void finalizeHollowAccount(@NonNull final Address alias) { * {@inheritDoc} */ @Override - public Optional tryTrackingDeletion( - @NonNull final Address deleted, @NonNull final Address beneficiary) { - return evmFrameState.tryTrackingDeletion(deleted, beneficiary); + public Optional tryTrackingSelfDestructBeneficiary( + @NonNull final Address deleted, @NonNull final Address beneficiary, @NonNull final MessageFrame frame) { + return evmFrameState.tryTrackingSelfDestructBeneficiary(deleted, beneficiary, frame); } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java index b49c31886ca5..bbe07c0d5b60 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java @@ -82,6 +82,7 @@ import com.hedera.node.app.spi.key.KeyUtils; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.data.ContractsConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; @@ -698,7 +699,7 @@ public static HederaEvmContext wellKnownContextWith( @NonNull final HederaEvmBlocks blocks, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator) { - return new HederaEvmContext(NETWORK_GAS_PRICE, false, blocks, tinybarValues, systemContractGasCalculator); + return new HederaEvmContext(NETWORK_GAS_PRICE, false, blocks, tinybarValues, systemContractGasCalculator, null); } public static HederaEvmContext wellKnownContextWith( @@ -706,7 +707,17 @@ public static HederaEvmContext wellKnownContextWith( final boolean staticCall, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator) { - return new HederaEvmContext(NETWORK_GAS_PRICE, staticCall, blocks, tinybarValues, systemContractGasCalculator); + return new HederaEvmContext( + NETWORK_GAS_PRICE, staticCall, blocks, tinybarValues, systemContractGasCalculator, null); + } + + public static HederaEvmContext wellKnownContextWith( + @NonNull final HederaEvmBlocks blocks, + @NonNull final TinybarValues tinybarValues, + @NonNull final SystemContractGasCalculator systemContractGasCalculator, + @NonNull DeleteCapableTransactionRecordBuilder recordBuilder) { + return new HederaEvmContext( + NETWORK_GAS_PRICE, false, blocks, tinybarValues, systemContractGasCalculator, recordBuilder); } public static void assertFailsWith(@NonNull final ResponseCodeEnum status, @NonNull final Runnable something) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java index 70d29777128b..945f1daa47d0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.contract.impl.test.exec; import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideActionSidecarContentTracer; +import static com.hedera.node.app.service.contract.impl.exec.TransactionModule.provideHederaEvmContext; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_HEDERA_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_CALL_DATA; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -25,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import com.hedera.hapi.node.base.AccountID; @@ -35,10 +37,12 @@ import com.hedera.node.app.service.contract.impl.exec.TransactionModule; import com.hedera.node.app.service.contract.impl.exec.gas.CanonicalDispatchPrices; import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; +import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; +import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; @@ -51,6 +55,7 @@ import com.hedera.node.app.spi.validation.AttributeValidator; import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import java.time.Instant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -110,6 +115,20 @@ void feesOnlyUpdaterIsProxyUpdater() { verify(hederaOperations).begin(); } + @Test + void providesExpectedEvmContext() { + final var recordBuilder = mock(DeleteCapableTransactionRecordBuilder.class); + final var gasCalculator = mock(SystemContractGasCalculator.class); + final var blocks = mock(HederaEvmBlocks.class); + given(hederaOperations.gasPriceInTinybars()).willReturn(123L); + given(context.recordBuilder(DeleteCapableTransactionRecordBuilder.class)) + .willReturn(recordBuilder); + final var result = provideHederaEvmContext(context, tinybarValues, gasCalculator, hederaOperations, blocks); + assertSame(blocks, result.blocks()); + assertSame(123L, result.gasPrice()); + assertSame(recordBuilder, result.deleteCapableTransactionRecordBuilder()); + } + @Test void providesEthTxDataWhenApplicable() { final var ethTxn = EthereumTransactionBody.newBuilder() diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java index 88e17347a3bb..e60265cdeddb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java @@ -98,7 +98,7 @@ void staticCallsDoNotChargeGas() { @Test void zeroPriceGasDoesNoChargingWorkButDoesReturnIntrinsicGas() { - final var context = new HederaEvmContext(0L, false, blocks, tinybarValues, systemContractGasCalculator); + final var context = new HederaEvmContext(0L, false, blocks, tinybarValues, systemContractGasCalculator, null); givenWellKnownIntrinsicGasCost(); final var chargingResult = subject.chargeForGas(sender, relayer, context, worldUpdater, wellKnownHapiCall()); assertEquals(0, chargingResult.relayerAllowanceUsed()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomSelfDestructOperationTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomSelfDestructOperationTest.java index c4bf65b27803..ff42c3b5daba 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomSelfDestructOperationTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomSelfDestructOperationTest.java @@ -97,7 +97,7 @@ void respectsHederaCustomHaltReason() { given(addressChecks.isPresent(BENEFICIARY, frame)).willReturn(true); given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); given(gasCalculator.selfDestructOperationGasCost(null, Wei.ZERO)).willReturn(123L); - given(proxyWorldUpdater.tryTrackingDeletion(TBD, BENEFICIARY)) + given(proxyWorldUpdater.tryTrackingSelfDestructBeneficiary(TBD, BENEFICIARY, frame)) .willReturn(Optional.of(CustomExceptionalHaltReason.SELF_DESTRUCT_TO_SELF)); final var expected = new Operation.OperationResult(123L, CustomExceptionalHaltReason.SELF_DESTRUCT_TO_SELF); assertSameResult(expected, subject.execute(frame, evm)); @@ -158,7 +158,8 @@ private void givenRunnableSelfDestruct() { given(frame.getRecipientAddress()).willReturn(TBD); given(addressChecks.isPresent(BENEFICIARY, frame)).willReturn(true); given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); - given(proxyWorldUpdater.tryTrackingDeletion(TBD, BENEFICIARY)).willReturn(Optional.empty()); + given(proxyWorldUpdater.tryTrackingSelfDestructBeneficiary(TBD, BENEFICIARY, frame)) + .willReturn(Optional.empty()); given(proxyWorldUpdater.get(TBD)).willReturn(account); given(account.getBalance()).willReturn(INHERITANCE); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index e37a2d0ee123..d80f89028276 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -20,6 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations.MISSING_ENTITY_NUMBER; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SELF_DESTRUCT_BENEFICIARIES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_FUNGIBLE_RELATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CANONICAL_ALIAS; @@ -44,6 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -64,8 +66,12 @@ import com.hedera.node.app.service.token.records.CryptoCreateRecordBuilder; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.function.Predicate; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -77,6 +83,9 @@ class HandleHederaNativeOperationsTest { @Mock private HandleContext context; + @Mock + private MessageFrame frame; + @Mock private ReadableTokenStore tokenStore; @@ -101,6 +110,8 @@ class HandleHederaNativeOperationsTest { @Mock private ReadableNftStore nftStore; + private final Deque stack = new ArrayDeque<>(); + private HandleHederaNativeOperations subject; @BeforeEach @@ -281,8 +292,16 @@ void transferWithReceiverSigCheckSkipsCheckWithoutRequirement() { } @Test - void trackDeletionIsTodo() { - assertDoesNotThrow(() -> subject.trackDeletion(1L, 2L)); + void trackDeletionUpdatesMap() { + final DeleteCapableTransactionRecordBuilder beneficiaries = mock(DeleteCapableTransactionRecordBuilder.class); + given(frame.getMessageFrameStack()).willReturn(stack); + stack.push(frame); + given(frame.getContextVariable(SELF_DESTRUCT_BENEFICIARIES)).willReturn(beneficiaries); + subject.trackSelfDestructBeneficiary(1L, 2L, frame); + verify(beneficiaries) + .addBeneficiaryForDeletedAccount( + AccountID.newBuilder().accountNum(1L).build(), + AccountID.newBuilder().accountNum(2L).build()); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java index 399e147ba3c5..e95e34a4705b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java @@ -42,6 +42,7 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.pbj.runtime.io.buffer.Bytes; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -53,6 +54,9 @@ class QueryHederaNativeOperationsTest { @Mock private QueryContext context; + @Mock + private MessageFrame frame; + @Mock private ReadableAccountStore accountStore; @@ -84,7 +88,7 @@ void doesNotSupportAnyMutations() { assertThrows( UnsupportedOperationException.class, () -> subject.transferWithReceiverSigCheck(1L, 2L, 3L, MOCK_VERIFICATION_STRATEGY)); - assertThrows(UnsupportedOperationException.class, () -> subject.trackDeletion(1L, 2L)); + assertThrows(UnsupportedOperationException.class, () -> subject.trackSelfDestructBeneficiary(1L, 2L, frame)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java index c0b3970254e5..a0a47c9ef625 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java @@ -18,11 +18,13 @@ import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.selfDestructBeneficiariesFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.tinybarValuesFor; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.*; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; @@ -30,6 +32,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Hash; @@ -69,8 +72,9 @@ class FrameBuilderTest { private final FrameBuilder subject = new FrameBuilder(); @Test - void constructsExpectedFrameForCallToExtantContractIncludingAccessTrackerWithSidecarEnabled() { + void constructsExpectedFrameForCallToExtantContractIncludingOptionalContextVaraiables() { final var transaction = wellKnownHapiCall(); + final var recordBuilder = mock(DeleteCapableTransactionRecordBuilder.class); given(worldUpdater.getHederaAccount(NON_SYSTEM_LONG_ZERO_ADDRESS)).willReturn(account); given(account.getEvmCode()).willReturn(CONTRACT_CODE); given(worldUpdater.updater()).willReturn(stackedUpdater); @@ -83,7 +87,7 @@ void constructsExpectedFrameForCallToExtantContractIncludingAccessTrackerWithSid final var frame = subject.buildInitialFrameWith( transaction, worldUpdater, - wellKnownContextWith(blocks, tinybarValues, systemContractGasCalculator), + wellKnownContextWith(blocks, tinybarValues, systemContractGasCalculator, recordBuilder), config, EIP_1014_ADDRESS, NON_SYSTEM_LONG_ZERO_ADDRESS, @@ -110,6 +114,7 @@ void constructsExpectedFrameForCallToExtantContractIncludingAccessTrackerWithSid assertSame(CONTRACT_CODE, frame.getCode()); assertNotNull(accessTrackerFor(frame)); assertSame(tinybarValues, tinybarValuesFor(frame)); + assertSame(recordBuilder, selfDestructBeneficiariesFor(frame)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java index b5af60a93ce9..4ab17cd14982 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java @@ -17,9 +17,11 @@ package com.hedera.node.app.service.contract.impl.test.exec.utils; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SELF_DESTRUCT_BENEFICIARIES; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TRACKER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.selfDestructBeneficiariesFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.stackIncludesActiveAddress; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; @@ -33,6 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; import com.hedera.node.app.service.contract.impl.exec.operations.utils.OpUtils; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; @@ -42,6 +45,7 @@ import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.service.contract.impl.utils.OpcodeUtils; import com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import java.lang.reflect.InvocationTargetException; import java.util.ArrayDeque; import java.util.Arrays; @@ -242,6 +246,15 @@ void checksForAccessorAsExpected() { assertSame(tracker, accessTrackerFor(frame)); } + @Test + void checksForBeneficiaryMapAsExpected() { + givenNonInitialFrame(); + given(frame.getMessageFrameStack()).willReturn(stack); + final DeleteCapableTransactionRecordBuilder beneficiaries = mock(DeleteCapableTransactionRecordBuilder.class); + given(initialFrame.getContextVariable(SELF_DESTRUCT_BENEFICIARIES)).willReturn(beneficiaries); + assertSame(beneficiaries, selfDestructBeneficiariesFor(frame)); + } + @Test void okIfFrameHasNoTracker() { given(frame.getMessageFrameStack()).willReturn(stack); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index bdb18b7d4d81..f5837be31d9d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -18,7 +18,6 @@ import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; -import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.VERSION_038; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; @@ -158,7 +157,6 @@ void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome( expectedResult, SUCCESS_RESULT.finalStatus(), CALLED_CONTRACT_ID, SUCCESS_RESULT.gasPrice()); - given(recordBuilder.status(SUCCESS)).willReturn(recordBuilder); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITH_TO_ADDRESS.getEthereumHash()))) @@ -180,7 +178,6 @@ void delegatesToCreatedComponentAndExposesEthTxDataCreateWithoutToAddress() { final var expectedOutcome = new CallOutcome(expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice()); - given(recordBuilder.status(SUCCESS)).willReturn(recordBuilder); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITHOUT_TO_ADDRESS.getEthereumHash()))) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java index 84bce582628e..61623680f90b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/DispatchingEvmFrameStateTest.java @@ -73,6 +73,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -119,6 +120,9 @@ class DispatchingEvmFrameStateTest { @Mock private ContractStateStore contractStateStore; + @Mock + private MessageFrame frame; + private DispatchingEvmFrameState subject; @BeforeEach @@ -427,7 +431,8 @@ void returnsNullForExpired() { void missingAccountsCannotBeBeneficiaries() { givenWellKnownAccount(accountWith(ACCOUNT_NUM).expiredAndPendingRemoval(true)); - final var reasonToHaltDeletion = subject.tryTrackingDeletion(EVM_ADDRESS, LONG_ZERO_ADDRESS); + final var reasonToHaltDeletion = + subject.tryTrackingSelfDestructBeneficiary(EVM_ADDRESS, LONG_ZERO_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isPresent()); assertEquals(INVALID_SOLIDITY_ADDRESS, reasonToHaltDeletion.get()); @@ -545,7 +550,8 @@ void deletedAccountCannotBeTokenTreasury() { givenWellKnownAccount(accountWith(ACCOUNT_NUM).numberTreasuryTitles(1)); givenWellKnownAccount(BENEFICIARY_NUM, accountWith(BENEFICIARY_NUM)); - final var reasonToHaltDeletion = subject.tryTrackingDeletion(LONG_ZERO_ADDRESS, BENEFICIARY_ADDRESS); + final var reasonToHaltDeletion = + subject.tryTrackingSelfDestructBeneficiary(LONG_ZERO_ADDRESS, BENEFICIARY_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isPresent()); assertEquals(CONTRACT_IS_TREASURY, reasonToHaltDeletion.get()); @@ -556,7 +562,8 @@ void deletedAccountCannotHaveTokenBalances() { givenWellKnownAccount(accountWith(ACCOUNT_NUM).numberPositiveBalances(1)); givenWellKnownAccount(BENEFICIARY_NUM, accountWith(BENEFICIARY_NUM)); - final var reasonToHaltDeletion = subject.tryTrackingDeletion(LONG_ZERO_ADDRESS, BENEFICIARY_ADDRESS); + final var reasonToHaltDeletion = + subject.tryTrackingSelfDestructBeneficiary(LONG_ZERO_ADDRESS, BENEFICIARY_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isPresent()); assertEquals(CONTRACT_STILL_OWNS_NFTS, reasonToHaltDeletion.get()); @@ -567,15 +574,16 @@ void deletionsAreTracked() { givenWellKnownAccount(accountWith(ACCOUNT_NUM)); givenWellKnownAccount(BENEFICIARY_NUM, accountWith(BENEFICIARY_NUM)); - final var reasonToHaltDeletion = subject.tryTrackingDeletion(LONG_ZERO_ADDRESS, BENEFICIARY_ADDRESS); + final var reasonToHaltDeletion = + subject.tryTrackingSelfDestructBeneficiary(LONG_ZERO_ADDRESS, BENEFICIARY_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isEmpty()); - verify(nativeOperations).trackDeletion(ACCOUNT_NUM, BENEFICIARY_NUM); + verify(nativeOperations).trackSelfDestructBeneficiary(ACCOUNT_NUM, BENEFICIARY_NUM, frame); } @Test void beneficiaryCannotBeSelf() { - final var reasonToHaltDeletion = subject.tryTrackingDeletion(EVM_ADDRESS, EVM_ADDRESS); + final var reasonToHaltDeletion = subject.tryTrackingSelfDestructBeneficiary(EVM_ADDRESS, EVM_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isPresent()); assertEquals(CustomExceptionalHaltReason.SELF_DESTRUCT_TO_SELF, reasonToHaltDeletion.get()); @@ -585,7 +593,7 @@ void beneficiaryCannotBeSelf() { void tokenAccountsCannotBeBeneficiaries() { givenWellKnownToken(); - final var reasonToHaltDeletion = subject.tryTrackingDeletion(EVM_ADDRESS, TOKEN_ADDRESS); + final var reasonToHaltDeletion = subject.tryTrackingSelfDestructBeneficiary(EVM_ADDRESS, TOKEN_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isPresent()); assertEquals(INVALID_SOLIDITY_ADDRESS, reasonToHaltDeletion.get()); @@ -595,7 +603,7 @@ void tokenAccountsCannotBeBeneficiaries() { void senderAccountMustBeC() { givenWellKnownToken(); - final var reasonToHaltDeletion = subject.tryTrackingDeletion(EVM_ADDRESS, TOKEN_ADDRESS); + final var reasonToHaltDeletion = subject.tryTrackingSelfDestructBeneficiary(EVM_ADDRESS, TOKEN_ADDRESS, frame); assertTrue(reasonToHaltDeletion.isPresent()); assertEquals(INVALID_SOLIDITY_ADDRESS, reasonToHaltDeletion.get()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java index 35f5235c2226..5897c764e69e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java @@ -489,9 +489,9 @@ void doesntSupportDeletedAccountAddresses() { @Test void delegatesDeletionTrackingAttempt() { final var haltReason = Optional.of(CustomExceptionalHaltReason.SELF_DESTRUCT_TO_SELF); - given(evmFrameState.tryTrackingDeletion(SOME_EVM_ADDRESS, OTHER_EVM_ADDRESS)) + given(evmFrameState.tryTrackingSelfDestructBeneficiary(SOME_EVM_ADDRESS, OTHER_EVM_ADDRESS, frame)) .willReturn(haltReason); - assertSame(haltReason, subject.tryTrackingDeletion(SOME_EVM_ADDRESS, OTHER_EVM_ADDRESS)); + assertSame(haltReason, subject.tryTrackingSelfDestructBeneficiary(SOME_EVM_ADDRESS, OTHER_EVM_ADDRESS, frame)); } @Test diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java index 8fa3b73e5982..56b4887a7113 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java @@ -1330,7 +1330,7 @@ final HapiSpec canInternallyCallAliasedAddressesOnlyViaCreate2Address() { })); } - private HapiContractCallLocal setExpectedCreate2Address( + public static HapiContractCallLocal setExpectedCreate2Address( String contract, BigInteger salt, AtomicReference expectedCreate2Address, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java index 0cf5a7608c01..fb62f6a26a72 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java @@ -40,10 +40,12 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createDefaultContract; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDeleteAliased; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdateAliased; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.ethereumCallWithFunctionAbi; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.sortedCryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; @@ -69,6 +71,7 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.accountId; +import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hedera.services.bdd.suites.contract.Utils.ocWith; import static com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite.ADMIN_KEY; import static com.hedera.services.bdd.suites.crypto.AutoCreateUtils.updateSpecFor; @@ -89,15 +92,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.protobuf.ByteString; +import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.suites.HapiSuite; +import com.hedera.services.bdd.suites.contract.Utils; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ContractID; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.KeyList; +import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.ThresholdKey; import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.TokenID; @@ -164,6 +170,7 @@ public class AutoAccountCreationSuite extends HapiSuite { private static final String HBAR_XFER = "hbarXfer"; private static final String NFT_XFER = "nftXfer"; private static final String FT_XFER = "ftXfer"; + private static final String ERC20_ABI = "ERC20ABI"; public static void main(String... args) { new AutoAccountCreationSuite().runSuiteAsync(); @@ -212,7 +219,8 @@ public List getSpecsInSuite() { transferFungibleToEVMAddressAlias(), transferNonFungibleToEVMAddressAlias(), transferHbarsToECDSAKey(), - cannotAutoCreateWithTxnToLongZero()); + cannotAutoCreateWithTxnToLongZero(), + accountDeleteResetsTheAliasNonce()); } @HapiTest @@ -1403,6 +1411,97 @@ final HapiSpec transferHbarsToEVMAddressAlias() { .has(accountWith().expectedBalanceWithChargedUsd(3 * ONE_HBAR, 0, 0))); } + private HapiSpec accountDeleteResetsTheAliasNonce() { + + final AtomicReference partyId = new AtomicReference<>(); + final AtomicReference partyAlias = new AtomicReference<>(); + final AtomicReference counterAlias = new AtomicReference<>(); + final AtomicReference aliasedAccountId = new AtomicReference<>(); + final AtomicReference tokenNum = new AtomicReference<>(); + final var totalSupply = 50; + final var ercUser = "ercUser"; + + return defaultHapiSpec("accountDeleteResetsTheAliasNonce") + .given( + cryptoCreate(PARTY).maxAutomaticTokenAssociations(2), + cryptoCreate(TOKEN_TREASURY), + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + withOpContext((spec, opLog) -> { + final var registry = spec.registry(); + final var ecdsaKey = registry.getKey(SECP_256K1_SOURCE_KEY); + final var tmp = ecdsaKey.getECDSASecp256K1().toByteArray(); + final var addressBytes = recoverAddressFromPubKey(tmp); + final var evmAddressBytes = ByteString.copyFrom(addressBytes); + partyId.set(registry.getAccountID(PARTY)); + partyAlias.set(ByteString.copyFrom(asSolidityAddress(partyId.get()))); + counterAlias.set(evmAddressBytes); + }), + tokenCreate("token") + .tokenType(TokenType.FUNGIBLE_COMMON) + .initialSupply(totalSupply) + .treasury(TOKEN_TREASURY) + .adminKey(SECP_256K1_SOURCE_KEY) + .supplyKey(SECP_256K1_SOURCE_KEY) + .exposingCreatedIdTo(tokenNum::set)) + .when( + withOpContext((spec, opLog) -> { + var op1 = cryptoTransfer((s, b) -> b.setTransfers(TransferList.newBuilder() + .addAccountAmounts(aaWith(partyAlias.get(), -2 * ONE_HBAR)) + .addAccountAmounts(aaWith(counterAlias.get(), +2 * ONE_HBAR)))) + .signedBy(DEFAULT_PAYER, PARTY) + .via(HBAR_XFER); + + var op2 = getAliasedAccountInfo(counterAlias.get()) + .logged() + .exposingIdTo(aliasedAccountId::set) + .has(accountWith() + .hasEmptyKey() + .noAlias() + .nonce(0) + .autoRenew(THREE_MONTHS_IN_SECONDS) + .receiverSigReq(false) + .memo(LAZY_MEMO)); + + // send eth transaction signed by the ecdsa key + var op3 = ethereumCallWithFunctionAbi( + true, + "token", + getABIFor(Utils.FunctionType.FUNCTION, "totalSupply", ERC20_ABI)) + .type(EthTxData.EthTransactionType.EIP1559) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(GENESIS) + .nonce(0) + .gasPrice(50L) + .maxGasAllowance(FIVE_HBARS) + .maxPriorityGas(2L) + .gasLimit(1_000_000L) + .hasKnownStatus(ResponseCodeEnum.SUCCESS); + + // assert account nonce is increased to 1 + var op4 = getAliasedAccountInfo(counterAlias.get()) + .logged() + .has(accountWith().nonce(1)); + + allRunFor(spec, op1, op2, op3, op4); + + spec.registry().saveAccountId(ercUser, aliasedAccountId.get()); + spec.registry().saveKey(ercUser, spec.registry().getKey(SECP_256K1_SOURCE_KEY)); + }), + // delete the account currently holding the alias + cryptoDelete(ercUser)) + .then( + // try to create a new account with the same alias + withOpContext((spec, opLog) -> { + var op1 = cryptoTransfer((s, b) -> b.setTransfers(TransferList.newBuilder() + .addAccountAmounts(aaWith(partyAlias.get(), -2 * ONE_HBAR)) + .addAccountAmounts(aaWith(counterAlias.get(), +2 * ONE_HBAR)))) + .signedBy(DEFAULT_PAYER, PARTY) + .hasKnownStatus(ACCOUNT_DELETED); + + allRunFor(spec, op1); + })); + } + @HapiTest final HapiSpec transferHbarsToECDSAKey() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index 3ff6d0f12a6d..b912df373475 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -41,6 +41,7 @@ import static com.hedera.services.bdd.spec.keys.SigControl.ED25519_ON; import static com.hedera.services.bdd.spec.keys.SigControl.ON; import static com.hedera.services.bdd.spec.keys.SigControl.SECP256K1_ON; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; @@ -101,9 +102,11 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; +import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hedera.services.bdd.suites.contract.Utils.asHexedAddress; import static com.hedera.services.bdd.suites.contract.Utils.asToken; +import static com.hedera.services.bdd.suites.contract.Utils.captureOneChildCreate2MetaFor; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith; @@ -124,6 +127,7 @@ import static com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite.TRANSFERRING_CONTRACT; import static com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite.TRANSFER_TO_CALLER; import static com.hedera.services.bdd.suites.contract.hapi.ContractCreateSuite.EMPTY_CONSTRUCTOR_CONTRACT; +import static com.hedera.services.bdd.suites.contract.opcodes.Create2OperationSuite.setExpectedCreate2Address; import static com.hedera.services.bdd.suites.contract.precompile.ApproveAllowanceSuite.ATTACK_CALL; import static com.hedera.services.bdd.suites.contract.precompile.ApproveAllowanceSuite.CALL_TO; import static com.hedera.services.bdd.suites.contract.precompile.ApproveAllowanceSuite.CONTRACTS_PERMITTED_DELEGATE_CALLERS; @@ -159,6 +163,7 @@ import static com.hedera.services.bdd.suites.contract.precompile.ERCPrecompileSuite.TRANSFER_SIG_NAME; import static com.hedera.services.bdd.suites.contract.precompile.LazyCreateThroughPrecompileSuite.FIRST_META; import static com.hedera.services.bdd.suites.contract.precompile.V1SecurityModelOverrides.CONTRACTS_MAX_NUM_WITH_HAPI_SIGS_ACCESS; +import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.TRUE; import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.ADMIN_KEY; import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.FUNGIBLE_TOKEN; import static com.hedera.services.bdd.suites.crypto.CryptoApproveAllowanceSuite.NON_FUNGIBLE_TOKEN; @@ -193,6 +198,7 @@ import static org.hyperledger.besu.datatypes.Address.contractAddress; import static org.hyperledger.besu.datatypes.Address.fromHexString; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.esaulpaugh.headlong.abi.Address; @@ -202,7 +208,6 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.fee.FeeBuilder; import com.hedera.services.bdd.junit.HapiTest; -import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; @@ -228,6 +233,7 @@ import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TransactionRecord; +import com.hederahashgraph.api.proto.java.TransferList; import com.swirlds.common.utility.CommonUtils; import java.math.BigInteger; import java.time.Instant; @@ -243,7 +249,7 @@ import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; -@HapiTestSuite +// @HapiTestSuite @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class LeakyContractTestsSuite extends HapiSuite { public static final String CONTRACTS_MAX_REFUND_PERCENT_OF_GAS_LIMIT1 = "contracts.maxRefundPercentOfGasLimit"; @@ -264,6 +270,14 @@ public class LeakyContractTestsSuite extends HapiSuite { private static final String TOKEN_TRANSFER_CONTRACT = "TokenTransferContract"; private static final String TRANSFER_TOKEN_PUBLIC = "transferTokenPublic"; private static final String HEDERA_ALLOWANCES_IS_ENABLED = "hedera.allowances.isEnabled"; + public static final String LAZY_CREATION_ENABLED = "lazyCreation.enabled"; + private static final String CREATION = "creation"; + private static final String ENTITY_MEMO = "JUST DO IT"; + private static final String CREATE_2_TXN = "create2Txn"; + public static final String GET_BYTECODE = "getBytecode"; + public static final String CONTRACT_REPORTED_LOG_MESSAGE = "Contract reported TestContract initcode is {} bytes"; + public static final String DEPLOY = "deploy"; + private static final String CREATE_2_TXN_2 = "create2Txn2"; public static void main(String... args) { new LeakyContractTestsSuite().runSuiteSync(); @@ -309,9 +323,107 @@ public List getSpecsInSuite() { contractCreateNoncesExternalizationHappyPath(), contractCreateFollowedByContractCallNoncesExternalization(), shouldReturnNullWhenContractsNoncesExternalizationFlagIsDisabled(), + canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor(), getErc20TokenNameExceedingLimits()); } + @SuppressWarnings("java:S5960") + final HapiSpec canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor() { + final var tcValue = 1_234L; + final var contract = "Create2FactoryWithSelfDestructingContract"; + final var creation = CREATION; + final var salt = BigInteger.valueOf(42); + final var adminKey = ADMIN_KEY; + final AtomicReference factoryEvmAddress = new AtomicReference<>(); + final AtomicReference expectedCreate2Address = new AtomicReference<>(); + final AtomicReference hollowCreationAddress = new AtomicReference<>(); + final AtomicReference mergedAliasAddr = new AtomicReference<>(); + final AtomicReference mergedMirrorAddr = new AtomicReference<>(); + final AtomicReference mergedAliasAddr2 = new AtomicReference<>(); + final AtomicReference mergedMirrorAddr2 = new AtomicReference<>(); + final AtomicReference testContractInitcode = new AtomicReference<>(); + + return propertyPreservingHapiSpec("canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor") + .preserving(LAZY_CREATION_ENABLED) + .given( + overriding(LAZY_CREATION_ENABLED, TRUE), + newKeyNamed(adminKey), + newKeyNamed(MULTI_KEY), + uploadInitCode(contract), + contractCreate(contract) + .payingWith(GENESIS) + .adminKey(adminKey) + .entityMemo(ENTITY_MEMO) + .via(CREATE_2_TXN) + .exposingNumTo(num -> factoryEvmAddress.set(asHexedSolidityAddress(0, 0, num)))) + .when( + sourcing(() -> contractCallLocal( + contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) + .exposingTypedResultsTo(results -> { + final var tcInitcode = (byte[]) results[0]; + testContractInitcode.set(tcInitcode); + log.info(CONTRACT_REPORTED_LOG_MESSAGE, tcInitcode.length); + }) + .payingWith(GENESIS) + .nodePayment(ONE_HBAR)), + sourcing(() -> setExpectedCreate2Address( + contract, salt, expectedCreate2Address, testContractInitcode)), + // Now create a hollow account at the desired address + cryptoTransfer((spec, b) -> { + final var defaultPayerId = spec.registry().getAccountID(DEFAULT_PAYER); + b.setTransfers(TransferList.newBuilder() + .addAccountAmounts(aaWith( + ByteString.copyFrom( + CommonUtils.unhex(expectedCreate2Address.get())), + +ONE_HBAR)) + .addAccountAmounts(aaWith(defaultPayerId, -ONE_HBAR))); + }) + .signedBy(DEFAULT_PAYER) + .fee(ONE_HBAR) + .via(creation), + getTxnRecord(creation) + .andAllChildRecords() + .exposingCreationsTo(l -> hollowCreationAddress.set(l.get(0)))) + .then( + getContractInfo(contract) + .has(ContractInfoAsserts.contractWith().balance(0L)), + sourcing(() -> contractCall(contract, DEPLOY, testContractInitcode.get(), salt) + .payingWith(GENESIS) + .gas(4_000_000L) + .sending(tcValue) + .via(CREATE_2_TXN)), + captureOneChildCreate2MetaFor( + "Merged deployed contract with hollow account and self-destructed the contract", + CREATE_2_TXN, + mergedMirrorAddr, + mergedAliasAddr), + sourcing(() -> getContractInfo(mergedMirrorAddr.get()) + .has(ContractInfoAsserts.contractWith().isDeleted())), + getContractInfo(contract) + .has(ContractInfoAsserts.contractWith().balance(ONE_HBAR + tcValue)), + /* Can repeat CREATE2 with same args because the previous contract was destroyed in the constructor*/ + sourcing(() -> contractCall(contract, DEPLOY, testContractInitcode.get(), salt) + .payingWith(GENESIS) + .gas(4_000_000L) + .sending(tcValue) + .via(CREATE_2_TXN_2)), + captureOneChildCreate2MetaFor( + "Merged deployed contract with hollow account and self-destructed the contract", + CREATE_2_TXN_2, + mergedMirrorAddr2, + mergedAliasAddr2), + sourcing(() -> getContractInfo(mergedMirrorAddr2.get()) + .has(ContractInfoAsserts.contractWith().isDeleted())), + sourcing(() -> assertionsHold((spec, asertLog) -> { + assertEquals( + mergedAliasAddr.get(), mergedAliasAddr2.get(), "Alias addresses must be equal!"); + assertNotEquals( + mergedMirrorAddr.get(), + mergedMirrorAddr2.get(), + "Mirror addresses must not be equal!"); + }))); + } + @HapiTest final HapiSpec transferErc20TokenFromErc721TokenFails() { return propertyPreservingHapiSpec("transferErc20TokenFromErc721TokenFails") diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin new file mode 100644 index 000000000000..c4d260362dbe --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506108b8806100206000396000f3fe6080604052600436106100345760003560e01c806381871cbc1461003957806394ca2cb5146100765780639c4ae2d0146100b3575b600080fd5b34801561004557600080fd5b50610060600480360381019061005b919061028f565b6100cf565b60405161006d919061035f565b60405180910390f35b34801561008257600080fd5b5061009d600480360381019061009891906104b6565b610145565b6040516100aa9190610521565b60405180910390f35b6100cd60048036038101906100c891906104b6565b61018f565b005b60606000604051806020016100e3906101da565b6020820181038252601f19601f82011660405250905080848460405160200161010d92919061054b565b60405160208183030381529060405260405160200161012d9291906105b0565b60405160208183030381529060405291505092915050565b60008060ff60f81b3084868051906020012060405160200161016a94939291906106b5565b6040516020818303038152906040528051906020012090508060001c91505092915050565b60008183516020850134f590507fb03c53b28e78a88e31607a27e1fa48234dce28d5d9d9ec7b295aeb02e674a1e181836040516101cd92919061054b565b60405180910390a1505050565b61017f8061070483390190565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610226826101fb565b9050919050565b6102368161021b565b811461024157600080fd5b50565b6000813590506102538161022d565b92915050565b6000819050919050565b61026c81610259565b811461027757600080fd5b50565b60008135905061028981610263565b92915050565b600080604083850312156102a6576102a56101f1565b5b60006102b485828601610244565b92505060206102c58582860161027a565b9150509250929050565b600081519050919050565b600082825260208201905092915050565b60005b838110156103095780820151818401526020810190506102ee565b60008484015250505050565b6000601f19601f8301169050919050565b6000610331826102cf565b61033b81856102da565b935061034b8185602086016102eb565b61035481610315565b840191505092915050565b600060208201905081810360008301526103798184610326565b905092915050565b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103c382610315565b810181811067ffffffffffffffff821117156103e2576103e161038b565b5b80604052505050565b60006103f56101e7565b905061040182826103ba565b919050565b600067ffffffffffffffff8211156104215761042061038b565b5b61042a82610315565b9050602081019050919050565b82818337600083830152505050565b600061045961045484610406565b6103eb565b90508281526020810184848401111561047557610474610386565b5b610480848285610437565b509392505050565b600082601f83011261049d5761049c610381565b5b81356104ad848260208601610446565b91505092915050565b600080604083850312156104cd576104cc6101f1565b5b600083013567ffffffffffffffff8111156104eb576104ea6101f6565b5b6104f785828601610488565b92505060206105088582860161027a565b9150509250929050565b61051b8161021b565b82525050565b60006020820190506105366000830184610512565b92915050565b61054581610259565b82525050565b60006040820190506105606000830185610512565b61056d602083018461053c565b9392505050565b600081905092915050565b600061058a826102cf565b6105948185610574565b93506105a48185602086016102eb565b80840191505092915050565b60006105bc828561057f565b91506105c8828461057f565b91508190509392505050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b61061b610616826105d4565b610600565b82525050565b60008160601b9050919050565b600061063982610621565b9050919050565b600061064b8261062e565b9050919050565b61066361065e8261021b565b610640565b82525050565b6000819050919050565b61068461067f82610259565b610669565b82525050565b6000819050919050565b6000819050919050565b6106af6106aa8261068a565b610694565b82525050565b60006106c1828761060a565b6001820191506106d18286610652565b6014820191506106e18285610673565b6020820191506106f1828461069e565b6020820191508190509594505050505056fe608060405260405161017f38038061017f8339818101604052810190610025919061013e565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100d5826100aa565b9050919050565b6100e5816100ca565b81146100f057600080fd5b50565b600081519050610102816100dc565b92915050565b6000819050919050565b61011b81610108565b811461012657600080fd5b50565b60008151905061013881610112565b92915050565b60008060408385031215610155576101546100a5565b5b6000610163858286016100f3565b925050602061017485828601610129565b915050925092905056fea2646970667358221220e153333d92557f0299a7a37f3835c04b5cc06faec36534cf64848b9f0f54e6f964736f6c63430008120033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json new file mode 100644 index 000000000000..a48ca4e92f5c --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json @@ -0,0 +1,87 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "salt", + "type": "uint256" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "bytecode", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_salt", + "type": "uint256" + } + ], + "name": "deploy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "bytecode", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_salt", + "type": "uint256" + } + ], + "name": "getAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_foo", + "type": "uint256" + } + ], + "name": "getBytecode", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol new file mode 100644 index 000000000000..b40b84d1d6d8 --- /dev/null +++ b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +contract Create2FactoryWithSelfDestructingContract { + event Deployed(address addr, uint salt); + + // 1. Get bytecode of contract to be deployed + // NOTE: _owner and _foo are arguments of the SelfDestructingTestContract's constructor + function getBytecode(address _owner, uint _foo) public pure returns (bytes memory) { + bytes memory bytecode = type(SelfDestructingTestContract).creationCode; + + return abi.encodePacked(bytecode, abi.encode(_owner, _foo)); + } + + // 2. Compute the address of the contract to be deployed + // NOTE: _salt is a random number used to create an address + function getAddress(bytes memory bytecode, uint _salt) + public + view + returns (address) + { + bytes32 hash = keccak256( + abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode)) + ); + + // NOTE: cast last 20 bytes of hash to address + return address(uint160(uint(hash))); + } + + // 3. Deploy the contract + // NOTE: + // Check the event log Deployed which contains the address of the deployed SelfDestructingTestContract. + // The address in the log should equal the address computed from above. + function deploy(bytes memory bytecode, uint _salt) public payable { + address addr; + + /* + NOTE: How to call create2 + + create2(v, p, n, s) + create new contract with code at memory p to p + n + and send v wei + and return the new address + where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n))) + s = big-endian 256-bit value + */ + assembly { + addr := create2( + callvalue(), // wei sent with current call + // Actual code starts after skipping the first 32 bytes + add(bytecode, 0x20), + mload(bytecode), // Load the size of code contained in the first 32 bytes + _salt // Salt from function arguments + ) + } + + emit Deployed(addr, _salt); + } +} + +contract SelfDestructingTestContract { + address public owner; + uint public foo; + + constructor(address _owner, uint _foo) payable { + owner = _owner; + foo = _foo; + selfdestruct(payable(owner)); + } +} From 78b1fe38d87351b9b5db5734522e94d0e81d77b0 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Thu, 28 Dec 2023 09:26:34 -0600 Subject: [PATCH 54/80] chore: merge the dual state into the platform state (#10429) Signed-off-by: Cody Littley --- .../main/java/com/hedera/node/app/Hedera.java | 18 +- .../node/app/HederaInjectionComponent.java | 4 +- .../state/HandleConsensusRoundListener.java | 4 +- .../app/state/merkle/MerkleHederaState.java | 10 +- .../app/state/merkle/OnStateInitialized.java | 4 +- .../app/workflows/handle/HandleWorkflow.java | 24 +- ....java => PlatformStateUpdateFacility.java} | 20 +- .../state/merkle/MerkleHederaStateTest.java | 14 +- .../workflows/handle/HandleWorkflowTest.java | 100 ++-- ...a => PlatformStateUpdateFacilityTest.java} | 27 +- .../node/app/service/mono/ServicesApp.java | 4 +- .../node/app/service/mono/ServicesState.java | 38 +- .../annotations/StaticAccountMemo.java | 4 +- .../marshalling/ImpliedTransfersMeta.java | 3 +- .../mono/sigs/HederaToPlatformSigOps.java | 4 +- ...cessor.java => PlatformStateAccessor.java} | 16 +- .../mono/state/logic/BlockManager.java | 16 +- .../state/merkle/MerkleNetworkContext.java | 18 +- .../mono/txns/network/NetworkLogicModule.java | 8 +- .../mono/txns/network/UpgradeActions.java | 26 +- .../mono/txns/span/ExpandHandleSpan.java | 4 +- .../app/service/mono/ServicesAppTest.java | 4 +- .../app/service/mono/ServicesStateTest.java | 79 ++- ...st.java => PlatformStateAccessorTest.java} | 14 +- .../mono/state/logic/BlockManagerTest.java | 33 +- .../merkle/MerkleNetworkContextTest.java | 14 +- .../mono/txns/network/UpgradeActionsTest.java | 42 +- .../demo/crypto/CryptocurrencyDemoState.java | 6 +- .../demo/hashgraph/HashgraphDemoState.java | 4 +- .../demo/hello/HelloSwirldDemoState.java | 4 +- .../swirlds/demo/stats/StatsDemoState.java | 4 +- .../AddressBookTestingToolState.java | 16 +- .../ConsistencyTestingToolState.java | 16 +- .../swirlds/demo/iss/ISSTestingToolState.java | 6 +- .../migration/MigrationTestingToolState.java | 6 +- .../platform/PlatformTestingToolState.java | 22 +- .../freeze/FreezeTransactionHandler.java | 6 +- .../demo/merkle/map/MapValueFCQTests.java | 4 +- .../signing/StatsSigningTestingToolState.java | 4 +- .../demo/stress/StressTestingToolState.java | 6 +- .../com/swirlds/platform/ConsensusImpl.java | 40 +- .../com/swirlds/platform/SwirldsPlatform.java | 68 +-- .../cli/GenesisPlatformStateCommand.java | 14 +- .../appcomm/AppCommunicationComponent.java | 2 +- .../consensus/RoundCalculationUtils.java | 9 +- .../eventhandling/ConsensusRoundHandler.java | 39 +- .../DefaultSignedStateValidator.java | 3 +- .../platform/reconnect/ReconnectHelper.java | 6 - .../platform/reconnect/ReconnectLearner.java | 3 +- .../EmergencySignedStateValidator.java | 7 +- .../recovery/EventRecoveryWorkflow.java | 67 ++- .../swirlds/platform/state/DualStateImpl.java | 109 +--- .../platform/state/GenesisStateBuilder.java | 35 +- .../platform/state/LegacyPlatformState.java | 137 +++++ .../swirlds/platform/state/PlatformData.java | 18 +- .../platform/state/PlatformDualState.java | 40 -- .../swirlds/platform/state/PlatformState.java | 474 ++++++++++++++++-- .../com/swirlds/platform/state/State.java | 130 +++-- .../platform/state/SwirldStateManager.java | 15 +- .../state/SwirldStateManagerUtils.java | 28 +- .../platform/state/TransactionHandler.java | 2 +- .../state/signed/SavedStateMetadata.java | 14 +- .../platform/state/signed/SignedState.java | 24 +- .../state/signed/SignedStateFileWriter.java | 2 +- .../state/signed/SignedStateManager.java | 6 +- .../signed/SignedStateValidationData.java | 6 +- .../state/signed/StartupStateUtils.java | 9 +- .../swirlds/platform/system/DualState.java | 51 -- .../platform/system/SwirldDualState.java | 32 -- .../swirlds/platform/system/SwirldState.java | 41 +- .../NewSignedStateNotification.java | 16 +- .../swirlds/platform/util/BootstrapUtils.java | 6 +- .../platform/AddressBookInitializerTest.java | 5 +- .../platform/SavedStateMetadataTests.java | 7 +- ...ldStateManagerFreezePeriodCheckerTest.java | 57 +-- .../consensus/RoundCalculationUtilsTest.java | 7 +- .../ConsensusRoundHandlerTests.java | 11 - .../DefaultSignedStateValidatorTests.java | 4 +- .../platform/reconnect/ReconnectTest.java | 1 - .../EmergencySignedStateValidatorTests.java | 11 +- .../recovery/EventRecoveryWorkflowTests.java | 8 +- .../platform/state/DualStateImplTest.java | 93 ---- .../state/RandomSignedStateGenerator.java | 37 +- .../platform/state/SignedStateTests.java | 6 +- .../state/SwirldStateManagerTests.java | 3 - .../state/SwirldStateManagerUtilsTests.java | 6 - .../state/TransactionHandlerTest.java | 5 +- .../state/manager/AddIncompleteStateTest.java | 6 +- .../state/manager/EarlySignaturesTest.java | 6 +- .../RegisterStatesWithoutSignaturesTest.java | 6 +- .../manager/SequentialSignaturesTest.java | 6 +- .../state/signed/StartupStateUtilsTests.java | 13 +- .../swirlds/platform/util/HashLoggerTest.java | 11 +- .../test/fixtures/event/EventUtils.java | 78 --- .../test/fixtures/state/DummySwirldState.java | 4 +- .../src/testFixtures/java/module-info.java | 1 - .../platform/test/PlatformStateUtils.java | 23 +- .../test/consensus/ConsensusUtils.java | 13 - .../platform/test/consensus/TestIntake.java | 2 - .../framework/ConsensusTestNode.java | 9 - .../framework/ConsensusTestOrchestrator.java | 5 - .../com/swirlds/platform/test/StateTests.java | 2 - .../consensus/ConsensusTestDefinitions.java | 51 +- .../test/consensus/ConsensusTests.java | 14 - .../linking/OrphanBufferingLinkerTest.java | 9 +- 105 files changed, 1234 insertions(+), 1395 deletions(-) rename hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/{DualStateUpdateFacility.java => PlatformStateUpdateFacility.java} (84%) rename hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/{DualStateUpdateFacilityTest.java => PlatformStateUpdateFacilityTest.java} (81%) rename hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/{DualStateAccessor.java => PlatformStateAccessor.java} (69%) rename hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/{DualStateAccessorTest.java => PlatformStateAccessorTest.java} (75%) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/LegacyPlatformState.java delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformDualState.java delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/DualState.java delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldDualState.java delete mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/DualStateImplTest.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java index dc43bd8e544d..ba09ead52f0f 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java @@ -84,11 +84,11 @@ import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.listeners.PlatformStatusChangeListener; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.Event; @@ -370,7 +370,7 @@ public SwirldState newState() { private void onStateInitialized( @NonNull final MerkleHederaState state, @NonNull final Platform platform, - @NonNull final SwirldDualState dualState, + @NonNull final PlatformState platformState, @NonNull final InitTrigger trigger, @Nullable final SoftwareVersion previousVersion) { // Initialize the configuration from disk. We must do this BEFORE we run migration, because the various @@ -407,7 +407,7 @@ private void onStateInitialized( } //noinspection ConstantValue - assert dualState != null : "Platform should never pass a null dual state"; + assert platformState != null : "Platform should never pass a null platform state"; logger.info( "Initializing Hedera state with trigger {} and previous version {}", () -> trigger, @@ -449,11 +449,11 @@ private void onStateInitialized( // assertion will hold true. assert configProvider != null : "Config Provider *must* have been set by now!"; - // Some logging on what we found about freeze in the dual state + // Some logging on what we found about freeze in the platform state logger.info( - "Dual state includes freeze time={} and last frozen={}", - dualState.getFreezeTime(), - dualState.getLastFrozenTime()); + "Platform state includes freeze time={} and last frozen={}", + platformState.getFreezeTime(), + platformState.getLastFrozenTime()); } /** * Called by this class when we detect it is time to do migration. The {@code deserializedVersion} must not be newer @@ -720,9 +720,9 @@ private void onPreHandle(@NonNull final Event event, @NonNull final HederaState * called. */ private void onHandleConsensusRound( - @NonNull final Round round, @NonNull final SwirldDualState dualState, @NonNull final HederaState state) { + @NonNull final Round round, @NonNull final PlatformState platformState, @NonNull final HederaState state) { daggerApp.workingStateAccessor().setHederaState(state); - daggerApp.handleWorkflow().handleRound(state, dualState, round); + daggerApp.handleWorkflow().handleRound(state, platformState, round); } /*================================================================================================================== diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java index 4bf5eb255ebc..5cbec646e957 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java @@ -46,8 +46,8 @@ import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.throttle.ThrottleManager; import com.hedera.node.app.workflows.WorkflowsInjectionModule; -import com.hedera.node.app.workflows.handle.DualStateUpdateFacility; import com.hedera.node.app.workflows.handle.HandleWorkflow; +import com.hedera.node.app.workflows.handle.PlatformStateUpdateFacility; import com.hedera.node.app.workflows.handle.SystemFileUpdateFacility; import com.hedera.node.app.workflows.handle.record.GenesisRecordsConsensusHook; import com.hedera.node.app.workflows.prehandle.PreHandleWorkflow; @@ -118,7 +118,7 @@ public interface HederaInjectionComponent { ThrottleManager throttleManager(); - DualStateUpdateFacility dualStateUpdateFacility(); + PlatformStateUpdateFacility platformStateUpdateFacility(); GenesisRecordsConsensusHook genesisRecordsConsensusHook(); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java index 213acf251478..07f55381c966 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/HandleConsensusRoundListener.java @@ -16,12 +16,12 @@ package com.hedera.node.app.state; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import edu.umd.cs.findbugs.annotations.NonNull; /** Listener invoked for each consensus round that occurs. */ @FunctionalInterface public interface HandleConsensusRoundListener { - void onConsensusRound(@NonNull Round round, @NonNull SwirldDualState dualState, @NonNull HederaState state); + void onConsensusRound(@NonNull Round round, @NonNull PlatformState platformState, @NonNull HederaState state); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java index cfbd69271962..6f3dd5052cf3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java @@ -52,11 +52,11 @@ import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; import com.swirlds.common.utility.Labeled; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.Event; @@ -190,14 +190,14 @@ public MerkleHederaState() { @Override public void init( final Platform platform, - final SwirldDualState dualState, + final PlatformState platformState, final InitTrigger trigger, final SoftwareVersion deserializedVersion) { // At some point this method will no longer be defined on SwirldState2, because we want to move // to a model where SwirldState/SwirldState2 are simply data objects, without this lifecycle. // Instead, this method will be a callback the app registers with the platform. So for now, // we simply call the callback handler, which is implemented by the app. - this.onInit.onStateInitialized(this, platform, dualState, trigger, deserializedVersion); + this.onInit.onStateInitialized(this, platform, platformState, trigger, deserializedVersion); } /** @@ -307,10 +307,10 @@ public MerkleHederaState copy() { * {@inheritDoc} */ @Override - public void handleConsensusRound(@NonNull final Round round, @NonNull final SwirldDualState swirldDualState) { + public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformState platformState) { throwIfImmutable(); if (onHandleConsensusRound != null) { - onHandleConsensusRound.onConsensusRound(round, swirldDualState, this); + onHandleConsensusRound.onConsensusRound(round, platformState, this); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnStateInitialized.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnStateInitialized.java index b19d65ec423a..0ca3e0fcdfe0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnStateInitialized.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnStateInitialized.java @@ -16,10 +16,10 @@ package com.hedera.node.app.state.merkle; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -30,7 +30,7 @@ public interface OnStateInitialized { void onStateInitialized( @NonNull MerkleHederaState state, @NonNull Platform platform, - @NonNull SwirldDualState dualState, + @NonNull PlatformState platformState, @NonNull InitTrigger trigger, @Nullable SoftwareVersion deserializedVersion); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 6f081d770a0d..bbf2e48afe8e 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -94,8 +94,8 @@ import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.transaction.ConsensusTransaction; import edu.umd.cs.findbugs.annotations.NonNull; @@ -134,7 +134,7 @@ public class HandleWorkflow { private final ChildRecordFinalizer childRecordFinalizer; private final ParentRecordFinalizer transactionFinalizer; private final SystemFileUpdateFacility systemFileUpdateFacility; - private final DualStateUpdateFacility dualStateUpdateFacility; + private final PlatformStateUpdateFacility platformStateUpdateFacility; private final SolvencyPreCheck solvencyPreCheck; private final Authorizer authorizer; private final NetworkUtilizationManager networkUtilizationManager; @@ -156,7 +156,7 @@ public HandleWorkflow( @NonNull final ChildRecordFinalizer childRecordFinalizer, @NonNull final ParentRecordFinalizer transactionFinalizer, @NonNull final SystemFileUpdateFacility systemFileUpdateFacility, - @NonNull final DualStateUpdateFacility dualStateUpdateFacility, + @NonNull final PlatformStateUpdateFacility platformStateUpdateFacility, @NonNull final SolvencyPreCheck solvencyPreCheck, @NonNull final Authorizer authorizer, @NonNull final NetworkUtilizationManager networkUtilizationManager) { @@ -176,8 +176,8 @@ public HandleWorkflow( this.transactionFinalizer = requireNonNull(transactionFinalizer, "transactionFinalizer must not be null"); this.systemFileUpdateFacility = requireNonNull(systemFileUpdateFacility, "systemFileUpdateFacility must not be null"); - this.dualStateUpdateFacility = - requireNonNull(dualStateUpdateFacility, "dualStateUpdateFacility must not be null"); + this.platformStateUpdateFacility = + requireNonNull(platformStateUpdateFacility, "platformStateUpdateFacility must not be null"); this.solvencyPreCheck = requireNonNull(solvencyPreCheck, "solvencyPreCheck must not be null"); this.authorizer = requireNonNull(authorizer, "authorizer must not be null"); this.networkUtilizationManager = @@ -191,7 +191,7 @@ public HandleWorkflow( * @param round the next {@link Round} that needs to be processed */ public void handleRound( - @NonNull final HederaState state, @NonNull final SwirldDualState dualState, @NonNull final Round round) { + @NonNull final HederaState state, @NonNull final PlatformState platformState, @NonNull final Round round) { // Keep track of whether any user transactions were handled. If so, then we will need to close the round // with the block record manager. final var userTransactionsHandled = new AtomicBoolean(false); @@ -222,7 +222,7 @@ public void handleRound( // skip system transactions if (!platformTxn.isSystem()) { userTransactionsHandled.set(true); - handlePlatformTransaction(state, dualState, event, creator, platformTxn); + handlePlatformTransaction(state, platformState, event, creator, platformTxn); } } catch (final Exception e) { logger.fatal( @@ -244,7 +244,7 @@ public void handleRound( private void handlePlatformTransaction( @NonNull final HederaState state, - @NonNull final SwirldDualState dualState, + @NonNull final PlatformState platformState, @NonNull final ConsensusEvent platformEvent, @NonNull final NodeInfo creator, @NonNull final ConsensusTransaction platformTxn) { @@ -253,13 +253,13 @@ private void handlePlatformTransaction( final Instant consensusNow = platformTxn.getConsensusTimestamp().minusNanos(1000 - 3L); // handle user transaction - handleUserTransaction(consensusNow, state, dualState, platformEvent, creator, platformTxn); + handleUserTransaction(consensusNow, state, platformState, platformEvent, creator, platformTxn); } private void handleUserTransaction( @NonNull final Instant consensusNow, @NonNull final HederaState state, - @NonNull final SwirldDualState dualState, + @NonNull final PlatformState platformState, @NonNull final ConsensusEvent platformEvent, @NonNull final NodeInfo creator, @NonNull final ConsensusTransaction platformTxn) { @@ -479,8 +479,8 @@ private void handleUserTransaction( final var fileUpdateResult = systemFileUpdateFacility.handleTxBody(stack, txBody); recordBuilder.status(fileUpdateResult); - // Notify if dual state was updated - dualStateUpdateFacility.handleTxBody(stack, dualState, txBody); + // Notify if platform state was updated + platformStateUpdateFacility.handleTxBody(stack, platformState, txBody); } catch (final HandleException e) { // In case of a ContractCall when it reverts, the gas charged should not be rolled back diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/DualStateUpdateFacility.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java similarity index 84% rename from hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/DualStateUpdateFacility.java rename to hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java index 4429f2d71121..e698606b3024 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/DualStateUpdateFacility.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacility.java @@ -29,7 +29,7 @@ import com.hedera.node.app.spi.state.ReadableSingletonState; import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.state.HederaState; -import com.swirlds.platform.system.DualState; +import com.swirlds.platform.state.PlatformState; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; import javax.inject.Inject; @@ -41,14 +41,14 @@ * Simple facility that notifies interested parties when the freeze state is updated. */ @Singleton -public class DualStateUpdateFacility { - private static final Logger logger = LogManager.getLogger(DualStateUpdateFacility.class); +public class PlatformStateUpdateFacility { + private static final Logger logger = LogManager.getLogger(PlatformStateUpdateFacility.class); /** * Creates a new instance of this class. */ @Inject - public DualStateUpdateFacility() { + public PlatformStateUpdateFacility() { // For dagger } @@ -61,7 +61,7 @@ public DualStateUpdateFacility() { */ public void handleTxBody( @NonNull final HederaState state, - @NonNull final DualState dualState, + @NonNull final PlatformState platformState, @NonNull final TransactionBody txBody) { requireNonNull(state, "state must not be null"); requireNonNull(txBody, "txBody must not be null"); @@ -70,7 +70,7 @@ public void handleTxBody( final FreezeType freezeType = txBody.freezeOrThrow().freezeType(); if (freezeType == FREEZE_UPGRADE || freezeType == FREEZE_ONLY) { logger.info("Transaction freeze of type {} detected", freezeType); - // copy freeze state to dual state + // copy freeze state to platform state final ReadableStates states = state.createReadableStates(FreezeService.NAME); final ReadableSingletonState freezeTime = states.getSingleton(FreezeServiceImpl.FREEZE_TIME_KEY); @@ -78,12 +78,12 @@ public void handleTxBody( final Instant freezeTimeInstant = Instant.ofEpochSecond( freezeTime.get().seconds(), freezeTime.get().nanos()); logger.info("Freeze time will be {}", freezeTimeInstant); - dualState.setFreezeTime(freezeTimeInstant); + platformState.setFreezeTime(freezeTimeInstant); } else if (freezeType == FREEZE_ABORT) { logger.info("Aborting freeze"); - // copy freeze state (which is null) to dual state - // we just set dual state to null - dualState.setFreezeTime(null); + // copy freeze state (which is null) to platform state + // we just set platform state to null + platformState.setFreezeTime(null); } // else for other freeze types, do nothing } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java index 876d8ebb71b5..47af6d8bc664 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java @@ -31,8 +31,8 @@ import com.swirlds.base.state.MutabilityException; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.merkle.map.MerkleMap; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.events.Event; import java.util.ArrayList; import java.util.Collections; @@ -65,7 +65,7 @@ void setUp() { hederaMerkle = new MerkleHederaState( (tree, state) -> onPreHandleCalled.set(true), (evt, meta, state) -> onHandleCalled.set(true), - (state, platform, dual, trigger, version) -> onMigrateCalled.set(true)); + (state, platform, platformState, trigger, version) -> onMigrateCalled.set(true)); } /** Looks for a merkle node with the given label */ @@ -688,7 +688,7 @@ final class ConsensusRoundTest { @DisplayName("Notifications are sent to onHandleConsensusRound when handleConsensusRound is called") void handleConsensusRoundCallback() { final var round = Mockito.mock(Round.class); - final var dualState = Mockito.mock(SwirldDualState.class); + final var platformState = Mockito.mock(PlatformState.class); final var state = new MerkleHederaState( (tree, st) -> onPreHandleCalled.set(true), (evt, meta, provider) -> { @@ -697,7 +697,7 @@ void handleConsensusRoundCallback() { }, (s, p, d, t, v) -> {}); - state.handleConsensusRound(round, dualState); + state.handleConsensusRound(round, platformState); assertThat(onHandleCalled).isTrue(); } } @@ -712,11 +712,11 @@ void originalLosesConsensusRoundCallbackAfterCopy() { // The original no longer has the listener final var round = Mockito.mock(Round.class); - final var dualState = Mockito.mock(SwirldDualState.class); - assertThrows(MutabilityException.class, () -> hederaMerkle.handleConsensusRound(round, dualState)); + final var platformState = Mockito.mock(PlatformState.class); + assertThrows(MutabilityException.class, () -> hederaMerkle.handleConsensusRound(round, platformState)); // But the copy does - copy.handleConsensusRound(round, dualState); + copy.handleConsensusRound(round, platformState); assertThat(onHandleCalled).isTrue(); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java index d5e4c71f84cc..5b56965e28f3 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java @@ -75,8 +75,8 @@ import com.hedera.node.config.VersionedConfigImpl; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.transaction.ConsensusTransaction; import com.swirlds.platform.system.transaction.SwirldTransaction; @@ -196,7 +196,7 @@ private static PreHandleResult createPreHandleResult(@NonNull Status status, @No private NetworkUtilizationManager networkUtilizationManager; @Mock - private DualStateUpdateFacility dualStateUpdateFacility; + private PlatformStateUpdateFacility platformStateUpdateFacility; @Mock(strictness = LENIENT) private SolvencyPreCheck solvencyPreCheck; @@ -205,7 +205,7 @@ private static PreHandleResult createPreHandleResult(@NonNull Status status, @No private Authorizer authorizer; @Mock - private SwirldDualState dualState; + private PlatformState platformState; private HandleWorkflow workflow; @@ -276,7 +276,7 @@ void setup() throws PreCheckException { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager); @@ -301,7 +301,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -322,7 +322,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -343,7 +343,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -364,7 +364,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -385,7 +385,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -406,7 +406,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -427,7 +427,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -448,7 +448,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -469,7 +469,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -490,7 +490,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -511,7 +511,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -532,7 +532,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -553,7 +553,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, null, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -574,7 +574,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, null, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, networkUtilizationManager)) @@ -616,7 +616,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, null, authorizer, networkUtilizationManager)) @@ -637,7 +637,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, null, networkUtilizationManager)) @@ -658,7 +658,7 @@ void testContructorWithInvalidArguments() { childRecordFinalizer, finalizer, systemFileUpdateFacility, - dualStateUpdateFacility, + platformStateUpdateFacility, solvencyPreCheck, authorizer, null)) @@ -672,7 +672,7 @@ void testPlatformTxnIsSkipped() { when(platformTxn.isSystem()).thenReturn(true); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then assertThat(accountsState.isModified()).isFalse(); @@ -689,7 +689,7 @@ void testHappyPath() { .willAnswer(invocation -> invocation.getArgument(4)); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -697,7 +697,7 @@ void testHappyPath() { assertThat(alice).isEqualTo(ALICE.account().accountId()); // TODO: Check that record was created verify(systemFileUpdateFacility).handleTxBody(any(), any()); - verify(dualStateUpdateFacility).handleTxBody(any(), any(), any()); + verify(platformStateUpdateFacility).handleTxBody(any(), any(), any()); } @Nested @@ -717,7 +717,7 @@ void testPreHandleNotExecuted() { when(platformTxn.getMetadata()).thenReturn(null); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -731,7 +731,7 @@ void testPreHandleFailure() { when(platformTxn.getMetadata()).thenReturn(PRE_HANDLE_FAILURE_RESULT); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -747,7 +747,7 @@ void testUnknownFailure() { when(platformTxn.getMetadata()).thenReturn(unknownFailure); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -774,7 +774,7 @@ void testConfigurationChanged() { when(platformTxn.getMetadata()).thenReturn(preHandleResult); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(preHandleWorkflow).preHandleTransaction(any(), any(), any(), eq(platformTxn), eq(preHandleResult)); @@ -787,7 +787,7 @@ void testPreHandleSuccess() { when(platformTxn.getMetadata()).thenReturn(null); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -808,7 +808,7 @@ void testPreHandleCausesDueDilligenceError() { .thenReturn(DUE_DILIGENCE_RESULT); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -825,7 +825,7 @@ void testPreHandleCausesPreHandleFailure() { .thenReturn(DUE_DILIGENCE_RESULT); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -841,7 +841,7 @@ void testPreHandleCausesUnknownFailure() { .thenReturn(PreHandleResult.unknownFailure()); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -857,7 +857,7 @@ void testPreHandleWithDueDiligenceFailure() { // given when(platformTxn.getMetadata()).thenReturn(DUE_DILIGENCE_RESULT); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -899,7 +899,7 @@ void testRequiredExistingKeyWithFailingSignature() throws PreCheckException { .dispatchPreHandle(any()); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(dispatcher, never()).dispatchHandle(any()); @@ -919,7 +919,7 @@ void testRequiredNewKeyWithFailingSignature() throws PreCheckException { .dispatchPreHandle(any()); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(dispatcher, never()).dispatchHandle(any()); @@ -960,7 +960,7 @@ void testOptionalExistingKeyWithPassingSignature() throws PreCheckException { .dispatchPreHandle(any()); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then final var argCapture = ArgumentCaptor.forClass(HandleContext.class); @@ -1004,7 +1004,7 @@ void testComplexCase() { .willAnswer(invocation -> invocation.getArgument(4)); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then final var argCapture = ArgumentCaptor.forClass(HandleContext.class); @@ -1042,7 +1042,7 @@ void testDuplicateFromOtherNode() { .thenReturn(DuplicateCheckResult.OTHER_NODE); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1062,7 +1062,7 @@ void testDuplicateFromSameNode() { .thenReturn(DuplicateCheckResult.SAME_NODE); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1083,7 +1083,7 @@ void testExpiredTransactionFails(final ResponseCodeEnum responseCode) throws Pre .checkTimeBox(OK_RESULT.txInfo().txBody(), TX_CONSENSUS_NOW); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1113,7 +1113,7 @@ void testInvalidPayerAccountFails(final ResponseCodeEnum responseCode) throws Pr .getPayerAccount(any(), eq(ALICE.accountID())); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1142,7 +1142,7 @@ void testInsolventPayerAccountFails(final ResponseCodeEnum responseCode) throws .checkSolvency(eq(OK_RESULT.txInfo()), any(), eq(DEFAULT_FEES), eq(FALSE)); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1163,7 +1163,7 @@ void testNonAuthorizedAccountFails() { .thenReturn(false); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1188,7 +1188,7 @@ void testNonAuthorizedAccountFailsForPrivilegedTxn() { .thenReturn(SystemPrivilege.UNAUTHORIZED); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1213,7 +1213,7 @@ void testImpermissibleTransactionFails() { .thenReturn(SystemPrivilege.IMPERMISSIBLE); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1239,7 +1239,7 @@ void testAuthorizedAccountFailsForPrivilegedTxn(final SystemPrivilege privilege) .thenReturn(privilege); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1258,7 +1258,7 @@ void testHandleException() { doThrow(new HandleException(ResponseCodeEnum.INVALID_SIGNATURE)) .when(dispatcher) .dispatchHandle(any()); - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1271,7 +1271,7 @@ void testHandleException() { void testUnknownFailure() { // when doThrow(new ArrayIndexOutOfBoundsException()).when(dispatcher).dispatchHandle(any()); - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1293,7 +1293,7 @@ void testSimpleRun() { given(preHandleWorkflow.preHandleTransaction(any(), any(), any(), any(), any())) .willAnswer(invocation -> invocation.getArgument(4)); // when - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); // then verify(blockRecordManager).advanceConsensusClock(notNull(), notNull()); @@ -1305,7 +1305,7 @@ void testSimpleRun() { @Test void testConsensusTimeHooksCalled() { - workflow.handleRound(state, dualState, round); + workflow.handleRound(state, platformState, round); verify(genesisRecordsTimeHook).process(notNull()); verify(stakingPeriodTimeHook).process(notNull(), notNull()); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/DualStateUpdateFacilityTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java similarity index 81% rename from hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/DualStateUpdateFacilityTest.java rename to hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java index a33743d7cf86..b1db1444f285 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/DualStateUpdateFacilityTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/PlatformStateUpdateFacilityTest.java @@ -34,7 +34,7 @@ import com.hedera.node.app.spi.fixtures.TransactionFactory; import com.hedera.node.app.spi.state.WritableSingletonStateBase; import com.hedera.node.app.spi.state.WritableStates; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import java.time.Instant; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -47,14 +47,14 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class DualStateUpdateFacilityTest implements TransactionFactory { +class PlatformStateUpdateFacilityTest implements TransactionFactory { private FakeHederaState state; - private DualStateUpdateFacility subject; + private PlatformStateUpdateFacility subject; private AtomicReference freezeTimeBackingStore; @Mock(strictness = Strictness.LENIENT) - private SwirldDualState dualState; + private PlatformState platformState; @Mock(strictness = LENIENT) protected WritableStates writableStates; @@ -68,11 +68,11 @@ void setUp() { state = new FakeHederaState().addService(FreezeService.NAME, Map.of(FREEZE_TIME_KEY, freezeTimeBackingStore)); - doAnswer(answer -> when(dualState.getFreezeTime()).thenReturn(answer.getArgument(0))) - .when(dualState) + doAnswer(answer -> when(platformState.getFreezeTime()).thenReturn(answer.getArgument(0))) + .when(platformState) .setFreezeTime(any(Instant.class)); - subject = new DualStateUpdateFacility(); + subject = new PlatformStateUpdateFacility(); } @SuppressWarnings("ConstantConditions") @@ -82,10 +82,11 @@ void testMethodsWithInvalidArguments() { final var txBody = simpleCryptoTransfer().body(); // then - assertThatThrownBy(() -> subject.handleTxBody(null, dualState, txBody)) + assertThatThrownBy(() -> subject.handleTxBody(null, platformState, txBody)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> subject.handleTxBody(state, null, txBody)).isInstanceOf(NullPointerException.class); - assertThatThrownBy(() -> subject.handleTxBody(state, dualState, null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> subject.handleTxBody(state, platformState, null)) + .isInstanceOf(NullPointerException.class); } @Test @@ -96,7 +97,7 @@ void testCryptoTransferShouldBeNoOp() { .build(); // then - Assertions.assertThatCode(() -> subject.handleTxBody(state, dualState, txBody)) + Assertions.assertThatCode(() -> subject.handleTxBody(state, platformState, txBody)) .doesNotThrowAnyException(); } @@ -109,10 +110,10 @@ void testFreezeUpgrade() { .freeze(FreezeTransactionBody.newBuilder().freezeType(FREEZE_UPGRADE)); // when - subject.handleTxBody(state, dualState, txBody.build()); + subject.handleTxBody(state, platformState, txBody.build()); // then - assertEquals(freezeTime.seconds(), dualState.getFreezeTime().getEpochSecond()); - assertEquals(freezeTime.nanos(), dualState.getFreezeTime().getNano()); + assertEquals(freezeTime.seconds(), platformState.getFreezeTime().getEpochSecond()); + assertEquals(freezeTime.nanos(), platformState.getFreezeTime().getNano()); } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesApp.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesApp.java index 932e524ed479..d5e04ab6676a 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesApp.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesApp.java @@ -45,7 +45,7 @@ import com.hedera.node.app.service.mono.records.RecordsModule; import com.hedera.node.app.service.mono.sigs.EventExpansion; import com.hedera.node.app.service.mono.sigs.SigsModule; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.StateModule; import com.hedera.node.app.service.mono.state.expiry.ExpiryModule; import com.hedera.node.app.service.mono.state.exports.AccountsExporter; @@ -135,7 +135,7 @@ public interface ServicesApp { ServicesInitFlow initializationFlow(); - DualStateAccessor dualStateAccessor(); + PlatformStateAccessor platformStateAccessor(); VirtualMapFactory virtualMapFactory(); diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesState.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesState.java index 84fc694be791..eb7f0484bf1c 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesState.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/ServicesState.java @@ -85,12 +85,11 @@ import com.swirlds.merkle.map.MerkleMap; import com.swirlds.merkledb.MerkleDb; import com.swirlds.platform.gui.SwirldsGui; -import com.swirlds.platform.state.DualStateImpl; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; @@ -239,14 +238,14 @@ public MerkleNode migrate(final int version) { @Override public void init( final Platform platform, - final SwirldDualState dualState, + final PlatformState platformState, final InitTrigger trigger, final SoftwareVersion deserializedVersion) { // first store a reference to the platform this.platform = platform; if (trigger == GENESIS) { - genesisInit(platform, dualState); + genesisInit(platform, platformState); } else { if (deserializedVersion == null) { throw new IllegalStateException( @@ -266,7 +265,7 @@ public void init( // Note this returns the app in case we need to do something with it after making // final changes to state (e.g. after migrating something from memory to disk) - deserializedInit(platform, dualState, trigger, deserializedVersion); + deserializedInit(platform, platformState, trigger, deserializedVersion); final var isUpgrade = SEMANTIC_VERSIONS.deployedSoftwareVersion().isNonConfigUpgrade(deserializedVersion); if (isUpgrade) { migrateFrom(deserializedVersion); @@ -292,13 +291,13 @@ public void init( } @Override - public void handleConsensusRound(final Round round, final SwirldDualState dualState) { + public void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); final var app = metadata.app(); app.mapWarmer().warmCache(round); - app.dualStateAccessor().setDualState(dualState); + app.platformStateAccessor().setPlatformState(platformState); app.logic().incorporateConsensus(round); } @@ -327,7 +326,7 @@ public AddressBook updateWeight(@NonNull AddressBook configAddressBook, @NonNull private ServicesApp deserializedInit( final Platform platform, - final SwirldDualState dualState, + final PlatformState platformState, final InitTrigger trigger, @NonNull final SoftwareVersion deserializedVersion) { log.info("Init called on Services node {} WITH Merkle saved state", platform.getSelfId()); @@ -336,10 +335,10 @@ private ServicesApp deserializedInit( enableVirtualAccounts = bootstrapProps.getBooleanProperty(PropertyNames.ACCOUNTS_STORE_ON_DISK); enableVirtualTokenRels = bootstrapProps.getBooleanProperty(PropertyNames.TOKENS_STORE_RELS_ON_DISK); enabledVirtualNft = bootstrapProps.getBooleanProperty(PropertyNames.TOKENS_NFTS_USE_VIRTUAL_MERKLE); - return internalInit(platform, bootstrapProps, dualState, trigger, deserializedVersion); + return internalInit(platform, bootstrapProps, platformState, trigger, deserializedVersion); } - private void genesisInit(final Platform platform, final SwirldDualState dualState) { + private void genesisInit(final Platform platform, final PlatformState platformState) { log.info("Init called on Services node {} WITHOUT Merkle saved state", platform.getSelfId()); // Create the top-level children in the Merkle tree @@ -350,14 +349,14 @@ private void genesisInit(final Platform platform, final SwirldDualState dualStat enabledVirtualNft = bootstrapProps.getBooleanProperty(PropertyNames.TOKENS_NFTS_USE_VIRTUAL_MERKLE); consolidateRecordStorage = bootstrapProps.getBooleanProperty(PropertyNames.RECORDS_USE_CONSOLIDATED_FCQ); createGenesisChildren(platform.getAddressBook(), seqStart, bootstrapProps); - internalInit(platform, bootstrapProps, dualState, GENESIS, null); + internalInit(platform, bootstrapProps, platformState, GENESIS, null); networkCtx().markPostUpgradeScanStatus(); } private ServicesApp internalInit( final Platform platform, final BootstrapProperties bootstrapProps, - SwirldDualState dualState, + PlatformState platformState, final InitTrigger trigger, @Nullable final SoftwareVersion deserializedVersion) { this.platform = platform; @@ -385,14 +384,15 @@ private ServicesApp internalInit( app.maybeNewRecoveredStateListener().ifPresent(listener -> platform.getNotificationEngine() .register(NewRecoveredStateListener.class, listener)); - if (dualState == null) { - dualState = new DualStateImpl(); + if (platformState == null) { + log.error("Platform state is null, this is highly unusual."); + platformState = new PlatformState(); } - app.dualStateAccessor().setDualState(dualState); + app.platformStateAccessor().setPlatformState(platformState); log.info( - "Dual state includes freeze time={} and last frozen={}", - dualState.getFreezeTime(), - dualState.getLastFrozenTime()); + "Platform state includes freeze time={} and last frozen={}", + platformState.getFreezeTime(), + platformState.getLastFrozenTime()); final var deployedVersion = SEMANTIC_VERSIONS.deployedSoftwareVersion(); if (deployedVersion.isBefore(deserializedVersion)) { @@ -489,7 +489,7 @@ public void logSummary() { if (metadata != null) { final var app = metadata.app(); app.hashLogger().logHashesFor(this); - ctxSummary = networkCtx().summarizedWith(app.dualStateAccessor()); + ctxSummary = networkCtx().summarizedWith(app.platformStateAccessor()); } else { ctxSummary = networkCtx().summarized(); } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/annotations/StaticAccountMemo.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/annotations/StaticAccountMemo.java index d0a52d3d80ff..890705466a11 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/annotations/StaticAccountMemo.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/context/annotations/StaticAccountMemo.java @@ -19,10 +19,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.hedera.node.app.service.mono.ServicesState; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -30,7 +30,7 @@ /** * Distinguishes a bound {@code String} instance that represents the address book memo of the node - * during {@link ServicesState#init(Platform, SwirldDualState, InitTrigger, SoftwareVersion)}. The + * during {@link ServicesState#init(Platform, PlatformState, InitTrigger, SoftwareVersion)}. The * "static" qualifier is meant to emphasize the current system does not allow for the possibility of * the node's account changing dynamically (i.e., without a network restart). */ diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/grpc/marshalling/ImpliedTransfersMeta.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/grpc/marshalling/ImpliedTransfersMeta.java index 84d0c1a0e120..fe59b7ee28f7 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/grpc/marshalling/ImpliedTransfersMeta.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/grpc/marshalling/ImpliedTransfersMeta.java @@ -25,7 +25,6 @@ import com.hedera.node.app.service.mono.utils.EntityNum; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.events.Event; import java.util.List; import java.util.Map; @@ -39,7 +38,7 @@ * *

    Note that we need to remember these two parameters in order to safely reuse this validation * across "span" between the {@link ServicesState#preHandle(Event)} and {@link - * ServicesState#handleConsensusRound(Round, SwirldDualState)} callbacks. + * ServicesState#handleConsensusRound(Round, PlatformState)} callbacks. * *

    This is because either parameter could change due to an update of file 0.0.121 between * the two callbacks. So we have to double-check that neither did change before reusing the diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/sigs/HederaToPlatformSigOps.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/sigs/HederaToPlatformSigOps.java index aa2b3a7c0646..e7892792cb98 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/sigs/HederaToPlatformSigOps.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/sigs/HederaToPlatformSigOps.java @@ -26,8 +26,8 @@ import com.hederahashgraph.api.proto.java.ResponseCodeEnum; import com.hederahashgraph.api.proto.java.Transaction; import com.swirlds.common.crypto.Signature; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; /** * Provides an "expand" operation that acts in-place on the {@link @@ -43,7 +43,7 @@ * have active signatures for the wrapped gRPC txn to be valid; and creates the cryptographic * signatures at the bases of the signing hierarchies for these keys. This implicitly requests the * Platform to verify these cryptographic signatures, by setting them in the sigs list of the - * platform txn, before {@link ServicesState#handleConsensusRound(Round, SwirldDualState)} is + * platform txn, before {@link ServicesState#handleConsensusRound(Round, PlatformState)} is * called with {@code isConsensus=true}. */ public final class HederaToPlatformSigOps { diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/DualStateAccessor.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/PlatformStateAccessor.java similarity index 69% rename from hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/DualStateAccessor.java rename to hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/PlatformStateAccessor.java index fdac2d682b94..91050b9efc78 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/DualStateAccessor.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/PlatformStateAccessor.java @@ -16,24 +16,24 @@ package com.hedera.node.app.service.mono.state; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import javax.inject.Inject; import javax.inject.Singleton; @Singleton -public class DualStateAccessor { - private SwirldDualState dualState = null; +public class PlatformStateAccessor { + private PlatformState platformState = null; @Inject - public DualStateAccessor() { + public PlatformStateAccessor() { // Default constructor } - public SwirldDualState getDualState() { - return dualState; + public PlatformState getPlatformState() { + return platformState; } - public void setDualState(SwirldDualState dualState) { - this.dualState = dualState; + public void setPlatformState(PlatformState platformState) { + this.platformState = platformState; } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/logic/BlockManager.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/logic/BlockManager.java index 85c81ae25531..92c41834c651 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/logic/BlockManager.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/logic/BlockManager.java @@ -24,7 +24,7 @@ import com.hedera.node.app.service.evm.contracts.execution.HederaBlockValues; import com.hedera.node.app.service.mono.context.properties.BootstrapProperties; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; import com.hedera.node.app.service.mono.stream.RecordsRunningHashLeaf; import com.swirlds.common.crypto.RunningHash; @@ -51,7 +51,7 @@ public class BlockManager { private final long blockPeriodMs; private final boolean logEveryTransaction; - private final DualStateAccessor dualStateAccessor; + private final PlatformStateAccessor platformStateAccessor; private final Supplier networkCtx; private final Supplier runningHashLeaf; @@ -68,13 +68,13 @@ public BlockManager( final BootstrapProperties bootstrapProperties, final Supplier networkCtx, final Supplier runningHashLeaf, - final DualStateAccessor dualStateAccessor) { + final PlatformStateAccessor platformStateAccessor) { this.networkCtx = networkCtx; this.runningHashLeaf = runningHashLeaf; this.blockPeriodMs = bootstrapProperties.getLongProperty(HEDERA_RECORD_STREAM_LOG_PERIOD) * SECONDS_TO_MILLISECONDS; this.logEveryTransaction = bootstrapProperties.getBooleanProperty(HEDERA_RECORD_STREAM_LOG_EVERY_TRANSACTION); - this.dualStateAccessor = dualStateAccessor; + this.platformStateAccessor = platformStateAccessor; } /** Clears all provisional block metadata for the current transaction. */ @@ -193,11 +193,11 @@ private void computeProvisionalBlockMeta(final Instant now) { private boolean willCreateNewBlock(@NonNull final Instant timestamp) { final var curNetworkCtx = networkCtx.get(); final var firstBlockTime = curNetworkCtx.firstConsTimeOfCurrentBlock(); - final var dualState = dualStateAccessor.getDualState(); - final var isFirstTransactionAfterFreezeRestart = - dualState.getFreezeTime() != null && dualState.getFreezeTime().equals(dualState.getLastFrozenTime()); + final var platformState = platformStateAccessor.getPlatformState(); + final var isFirstTransactionAfterFreezeRestart = platformState.getFreezeTime() != null + && platformState.getFreezeTime().equals(platformState.getLastFrozenTime()); if (isFirstTransactionAfterFreezeRestart) { - dualState.setFreezeTime(null); + platformState.setFreezeTime(null); } return firstBlockTime == null || isFirstTransactionAfterFreezeRestart diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContext.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContext.java index e901fc53d917..81190223200e 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContext.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContext.java @@ -31,7 +31,7 @@ import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.hapi.utils.throttles.GasLimitDeterministicThrottle; import com.hedera.node.app.service.mono.fees.congestion.MultiplierSources; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.merkle.internals.BytesElement; import com.hedera.node.app.service.mono.state.submerkle.ExchangeRates; import com.hedera.node.app.service.mono.state.submerkle.RichInstant; @@ -522,12 +522,14 @@ public String summarized() { return summarizedWith(null); } - public String summarizedWith(final DualStateAccessor dualStateAccessor) { - final var isDualStateAvailable = dualStateAccessor != null && dualStateAccessor.getDualState() != null; - final var freezeTime = - isDualStateAvailable ? dualStateAccessor.getDualState().getFreezeTime() : null; + public String summarizedWith(final PlatformStateAccessor platformStateAccessor) { + final var isPlatformStateAvailable = + platformStateAccessor != null && platformStateAccessor.getPlatformState() != null; + final var freezeTime = isPlatformStateAvailable + ? platformStateAccessor.getPlatformState().getFreezeTime() + : null; final var pendingUpdateDesc = currentPendingUpdateDesc(); - final var pendingMaintenanceDesc = freezeTimeDesc(freezeTime, isDualStateAvailable) + pendingUpdateDesc; + final var pendingMaintenanceDesc = freezeTimeDesc(freezeTime, isPlatformStateAvailable) + pendingUpdateDesc; return "The network context (state version " + (stateVersion == UNRECORDED_STATE_VERSION ? NOT_AVAILABLE : stateVersion) @@ -665,10 +667,10 @@ private String currentPendingUpdateDesc() { + CommonUtils.hex(preparedUpdateFileHash).substring(0, 8); } - private String freezeTimeDesc(@Nullable final Instant freezeTime, final boolean isDualStateAvailable) { + private String freezeTimeDesc(@Nullable final Instant freezeTime, final boolean isPlatformStateAvailable) { final var nmtDescSkip = LINE_WRAP; if (freezeTime == null) { - return (isDualStateAvailable ? NOT_EXTANT : NOT_AVAILABLE) + nmtDescSkip; + return (isPlatformStateAvailable ? NOT_EXTANT : NOT_AVAILABLE) + nmtDescSkip; } return freezeTime + nmtDescSkip; } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/NetworkLogicModule.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/NetworkLogicModule.java index f5f5a4e0b98a..7fc73c093a16 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/NetworkLogicModule.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/NetworkLogicModule.java @@ -20,10 +20,10 @@ import static com.hederahashgraph.api.proto.java.HederaFunctionality.UncheckedSubmit; import com.hedera.node.app.service.mono.fees.annotations.FunctionKey; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.txns.TransitionLogic; import com.hedera.node.app.service.mono.utils.UnzipUtility; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; @@ -41,8 +41,8 @@ public static UpgradeActions.UnzipAction provideUnzipAction() { @Provides @Singleton - public static Supplier provideDualState(DualStateAccessor dualStateAccessor) { - return dualStateAccessor::getDualState; + public static Supplier providePlatformState(PlatformStateAccessor platformStateAccessor) { + return platformStateAccessor::getPlatformState; } @Provides diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/UpgradeActions.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/UpgradeActions.java index 60d1e3ca5de6..02b44bf62246 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/UpgradeActions.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/network/UpgradeActions.java @@ -23,7 +23,7 @@ import com.hedera.node.app.service.mono.context.properties.GlobalDynamicProperties; import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; import com.hedera.node.app.service.mono.state.merkle.MerkleSpecialFiles; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -67,7 +67,7 @@ public interface UnzipAction { private final UnzipAction unzipAction; private final GlobalDynamicProperties dynamicProperties; - private final Supplier dualState; + private final Supplier platformState; private final Supplier specialFiles; private final Supplier networkCtx; @@ -75,10 +75,10 @@ public interface UnzipAction { public UpgradeActions( final UnzipAction unzipAction, final GlobalDynamicProperties dynamicProperties, - final Supplier dualState, + final Supplier platformState, final Supplier specialFiles, final Supplier networkCtx) { - this.dualState = dualState; + this.platformState = platformState; this.networkCtx = networkCtx; this.unzipAction = unzipAction; this.specialFiles = specialFiles; @@ -100,18 +100,18 @@ public CompletableFuture extractSoftwareUpgrade(final byte[] archiveData) } public void scheduleFreezeOnlyAt(final Instant freezeTime) { - withNonNullDualState("schedule freeze", ds -> ds.setFreezeTime(freezeTime)); + withNonNullPlatformState("schedule freeze", ds -> ds.setFreezeTime(freezeTime)); } public void scheduleFreezeUpgradeAt(final Instant freezeTime) { - withNonNullDualState("schedule freeze", ds -> { + withNonNullPlatformState("schedule freeze", ds -> { ds.setFreezeTime(freezeTime); writeSecondMarker(FREEZE_SCHEDULED_MARKER, freezeTime); }); } public void abortScheduledFreeze() { - withNonNullDualState("abort freeze", ds -> { + withNonNullPlatformState("abort freeze", ds -> { ds.setFreezeTime(null); writeCheckMarker(FREEZE_ABORTED_MARKER); }); @@ -119,7 +119,7 @@ public void abortScheduledFreeze() { public boolean isFreezeScheduled() { final var ans = new AtomicBoolean(); - withNonNullDualState("check freeze schedule", ds -> { + withNonNullPlatformState("check freeze schedule", ds -> { final var freezeTime = ds.getFreezeTime(); ans.set(freezeTime != null && !freezeTime.equals(ds.getLastFrozenTime())); }); @@ -154,7 +154,7 @@ private CompletableFuture extractNow( private void catchUpOnMissedFreezeScheduling() { final var isUpgradePrepared = networkCtx.get().hasPreparedUpgrade(); if (isFreezeScheduled() && isUpgradePrepared) { - writeMarker(FREEZE_SCHEDULED_MARKER, dualState.get().getFreezeTime()); + writeMarker(FREEZE_SCHEDULED_MARKER, platformState.get().getFreezeTime()); } /* If we missed a FREEZE_ABORT, we are at risk of having a problem down the road. But writing a "defensive" freeze_aborted.mf is itself too risky, as it will keep @@ -183,10 +183,10 @@ private void catchUpOnMissedUpgradePrep() { extractSoftwareUpgrade(archiveData).join(); } - private void withNonNullDualState(final String actionDesc, final Consumer action) { - final var curDualState = dualState.get(); - Objects.requireNonNull(curDualState, "Cannot " + actionDesc + " without access to the dual state"); - action.accept(curDualState); + private void withNonNullPlatformState(final String actionDesc, final Consumer action) { + final var curPlatformState = platformState.get(); + Objects.requireNonNull(curPlatformState, "Cannot " + actionDesc + " without access to the platform state"); + action.accept(curPlatformState); } private void writeCheckMarker(final String file) { diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java index 7951c546b790..ef5d00047db6 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/txns/span/ExpandHandleSpan.java @@ -22,15 +22,15 @@ import com.hedera.node.app.service.mono.utils.accessors.AccessorFactory; import com.hedera.node.app.service.mono.utils.accessors.PlatformTxnAccessor; import com.hedera.node.app.service.mono.utils.accessors.SwirldsTxnAccessor; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.Transaction; /** * Encapsulates a "span" that tracks our contact with a given {@link Transaction} between the {@link * EventExpansion#expandAllSigs(Event, ServicesState)} and {@link - * ServicesState#handleConsensusRound(Round, SwirldDualState)} platform callbacks. + * ServicesState#handleConsensusRound(Round, PlatformState)} platform callbacks. * *

    At first this span only tracks the {@link PlatformTxnAccessor} parsed from the transaction * contents in an expiring cache. Since the parsing is a pure function of the contents, this is a diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesAppTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesAppTest.java index 544e20b21816..d4ad5bcbaa8d 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesAppTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesAppTest.java @@ -41,7 +41,7 @@ import com.hedera.node.app.service.mono.ledger.accounts.staking.StakeStartupHelper; import com.hedera.node.app.service.mono.ledger.backing.BackingAccounts; import com.hedera.node.app.service.mono.sigs.EventExpansion; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.exports.ExportingRecoveredStateListener; import com.hedera.node.app.service.mono.state.exports.ServicesSignedStateListener; import com.hedera.node.app.service.mono.state.exports.SignedStateBalancesExporter; @@ -157,7 +157,7 @@ void objectGraphRootsAreAvailable() { assertThat(subject.logic(), instanceOf(StandardProcessLogic.class)); assertThat(subject.hashLogger(), instanceOf(HashLogger.class)); assertThat(subject.workingState(), instanceOf(MutableStateChildren.class)); - assertThat(subject.dualStateAccessor(), instanceOf(DualStateAccessor.class)); + assertThat(subject.platformStateAccessor(), instanceOf(PlatformStateAccessor.class)); assertThat(subject.initializationFlow(), instanceOf(ServicesInitFlow.class)); assertThat(subject.nodeLocalProperties(), instanceOf(NodeLocalProperties.class)); assertThat(subject.recordStreamManager(), instanceOf(RecordStreamManager.class)); diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesStateTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesStateTest.java index 77e637c10d56..df5eca287ecd 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesStateTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/ServicesStateTest.java @@ -48,7 +48,7 @@ import com.hedera.node.app.service.mono.context.properties.PropertyNames; import com.hedera.node.app.service.mono.ledger.accounts.staking.StakeStartupHelper; import com.hedera.node.app.service.mono.sigs.EventExpansion; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.exports.ExportingRecoveredStateListener; import com.hedera.node.app.service.mono.state.forensics.HashLogger; import com.hedera.node.app.service.mono.state.initialization.SystemAccountsCreator; @@ -93,14 +93,13 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.fchashmap.FCHashMap; import com.swirlds.merkle.map.MerkleMap; -import com.swirlds.platform.state.DualStateImpl; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedStateFileReader; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; @@ -172,7 +171,7 @@ class ServicesStateTest extends ResponsibleVMapUser { private EventExpansion eventExpansion; @Mock - private SwirldDualState dualState; + private PlatformState platformState; @Mock private StateMetadata metadata; @@ -187,7 +186,7 @@ class ServicesStateTest extends ResponsibleVMapUser { private MutableStateChildren workingState; @Mock - private DualStateAccessor dualStateAccessor; + private PlatformStateAccessor platformStateAccessor; @Mock private ServicesInitFlow initFlow; @@ -300,10 +299,10 @@ void logsSummaryAsExpectedWithAppAvailable() { given(metadata.app()).willReturn(app); given(app.hashLogger()).willReturn(hashLogger); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(networkContext.getStateVersion()).willReturn(StateVersions.CURRENT_VERSION); given(networkContext.consensusTimeOfLastHandledTxn()).willReturn(consTime); - given(networkContext.summarizedWith(dualStateAccessor)).willReturn("IMAGINE"); + given(networkContext.summarizedWith(platformStateAccessor)).willReturn("IMAGINE"); // when: subject.logSummary(); @@ -372,7 +371,7 @@ void preHandleUsesEventExpansion() { void handleThrowsIfImmutable() { tracked(subject.copy()); - assertThrows(MutabilityException.class, () -> subject.handleConsensusRound(round, dualState)); + assertThrows(MutabilityException.class, () -> subject.handleConsensusRound(round, platformState)); } @Test @@ -383,11 +382,11 @@ void handlesRoundAsExpected() { final var mapWarmer = mock(EntityMapWarmer.class); given(app.mapWarmer()).willReturn(mapWarmer); given(app.logic()).willReturn(logic); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); - subject.handleConsensusRound(round, dualState); + subject.handleConsensusRound(round, platformState); verify(mapWarmer).warmCache(round); - verify(dualStateAccessor).setDualState(dualState); + verify(platformStateAccessor).setPlatformState(platformState); verify(logic).incorporateConsensus(round); } @@ -410,14 +409,14 @@ void merkleMetaAsExpected() { } @Test - void doesntThrowWhenDualStateIsNull() { + void doesntThrowWhenPlatformStateIsNull() { subject.setChild(StateChildIndices.SPECIAL_FILES, specialFiles); subject.setChild(StateChildIndices.NETWORK_CTX, networkContext); subject.setChild(StateChildIndices.ACCOUNTS, accounts); given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(app.stakeStartupHelper()).willReturn(stakeStartupHelper); @@ -445,7 +444,7 @@ void genesisInitCreatesChildren() { // and: given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(platform.getAddressBook()).willReturn(addressBook); given(app.sysAccountsCreator()).willReturn(accountsCreator); @@ -463,7 +462,7 @@ void genesisInitCreatesChildren() { // when: subject = tracked(new ServicesState()); - subject.init(platform, dualState, InitTrigger.GENESIS, null); + subject.init(platform, platformState, InitTrigger.GENESIS, null); // then: assertFalse(subject.isImmutable()); @@ -483,7 +482,7 @@ void genesisInitCreatesChildren() { assertEquals(1001L, subject.networkCtx().seqNo().current()); assertNotNull(subject.specialFiles()); // and: - verify(dualStateAccessor).setDualState(dualState); + verify(platformStateAccessor).setPlatformState(platformState); verify(initFlow).runWith(eq(subject), any()); verify(appBuilder).bootstrapProps(any()); verify(appBuilder).initialHash(EMPTY_HASH); @@ -526,7 +525,7 @@ void genesisInitRespectsSelectedOnDiskMapsAndConsolidatedRecords() { // and: given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(platform.getAddressBook()).willReturn(addressBook); given(app.sysAccountsCreator()).willReturn(accountsCreator); @@ -541,7 +540,7 @@ void genesisInitRespectsSelectedOnDiskMapsAndConsolidatedRecords() { .getNodeId(anyInt()); // when: - subject.init(platform, dualState, InitTrigger.GENESIS, null); + subject.init(platform, platformState, InitTrigger.GENESIS, null); // then: assertTrue(subject.uniqueTokens().isVirtual()); @@ -559,7 +558,7 @@ void nonGenesisInitReusesContextIfPresent() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(platform.getAddressBook()).willReturn(addressBook); given(app.maybeNewRecoveredStateListener()).willReturn(Optional.of(recoveredStateListener)); @@ -568,7 +567,7 @@ void nonGenesisInitReusesContextIfPresent() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RECONNECT, currentVersion); + subject.init(platform, platformState, RECONNECT, currentVersion); // then: assertSame(addressBook, subject.addressBook()); @@ -589,12 +588,12 @@ void nonGenesisInitExitsIfStateVersionLaterThanCurrentSoftware() { given(platform.getSelfId()).willReturn(selfId); given(app.systemExits()).willReturn(mockExit); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); // and: APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, futureVersion); + subject.init(platform, platformState, RESTART, futureVersion); verify(mockExit).fail(1); } @@ -607,7 +606,7 @@ void nonGenesisInitDoesNotClearPreparedUpgradeIfSameVersion() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(app.stakeStartupHelper()).willReturn(stakeStartupHelper); @@ -615,7 +614,7 @@ void nonGenesisInitDoesNotClearPreparedUpgradeIfSameVersion() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, currentVersion); + subject.init(platform, platformState, RESTART, currentVersion); verify(networkContext, never()).discardPreparedUpgradeMeta(); } @@ -638,7 +637,7 @@ void nonGenesisInitWithBuildDoesntRunMigrations() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(app.stakeStartupHelper()).willReturn(stakeStartupHelper); @@ -647,7 +646,7 @@ void nonGenesisInitWithBuildDoesntRunMigrations() { // when: - subject.init(platform, dualState, RESTART, configVersion); + subject.init(platform, platformState, RESTART, configVersion); verify(networkContext, never()).discardPreparedUpgradeMeta(); } @@ -664,7 +663,7 @@ void nonGenesisInitClearsPreparedUpgradeIfDeployedIsLaterVersion() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(app.stakeStartupHelper()).willReturn(stakeStartupHelper); @@ -672,7 +671,7 @@ void nonGenesisInitClearsPreparedUpgradeIfDeployedIsLaterVersion() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, justPriorVersion); + subject.init(platform, platformState, RESTART, justPriorVersion); verify(networkContext).discardPreparedUpgradeMeta(); unmockMigrators(); @@ -691,7 +690,7 @@ void nonGenesisInitWithOldVersionMarksMigrationRecordsNotStreamed() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(app.stakeStartupHelper()).willReturn(stakeStartupHelper); @@ -699,7 +698,7 @@ void nonGenesisInitWithOldVersionMarksMigrationRecordsNotStreamed() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, justPriorVersion); + subject.init(platform, platformState, RESTART, justPriorVersion); verify(networkContext).discardPreparedUpgradeMeta(); verify(networkContext).markMigrationRecordsNotYetStreamed(); @@ -711,7 +710,7 @@ void nonGenesisInitWithOldVersionMarksMigrationRecordsNotStreamed() { void nonGenesisInitThrowsWithUnsupportedStateVersionUsed() { subject.setDeserializedStateVersion(StateVersions.RELEASE_0310_VERSION - 1); - assertThrows(IllegalStateException.class, () -> subject.init(platform, dualState, RESTART, null)); + assertThrows(IllegalStateException.class, () -> subject.init(platform, platformState, RESTART, null)); } @Test @@ -722,13 +721,13 @@ void nonGenesisInitDoesntClearPreparedUpgradeIfNotUpgrade() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); // and: APPS.save(selfId, app); // when: - subject.init(platform, dualState, RECONNECT, currentVersion); + subject.init(platform, platformState, RECONNECT, currentVersion); verify(networkContext, never()).discardPreparedUpgradeMeta(); } @@ -756,7 +755,7 @@ void nonGenesisInitConsolidatesRecords() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(platform.getAddressBook()).willReturn(addressBook); @@ -765,7 +764,7 @@ void nonGenesisInitConsolidatesRecords() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, currentVersion); + subject.init(platform, platformState, RESTART, currentVersion); verify(recordConsolidator).consolidateRecordsToSingleFcq(subject); ServicesState.setRecordConsolidator(RecordConsolidation::toSingleFcq); @@ -797,7 +796,7 @@ void nonGenesisInitHandlesNftMigration() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(platform.getAddressBook()).willReturn(addressBook); @@ -806,7 +805,7 @@ void nonGenesisInitHandlesNftMigration() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, currentVersion); + subject.init(platform, platformState, RESTART, currentVersion); verify(mapToDiskMigration) .migrateToDiskAsApropos( INSERTIONS_PER_COPY, @@ -846,7 +845,7 @@ void nonGenesisInitHandlesTokenRelMigrationToDisk() { given(app.hashLogger()).willReturn(hashLogger); given(app.initializationFlow()).willReturn(initFlow); - given(app.dualStateAccessor()).willReturn(dualStateAccessor); + given(app.platformStateAccessor()).willReturn(platformStateAccessor); given(platform.getSelfId()).willReturn(selfId); given(platform.getAddressBook()).willReturn(addressBook); @@ -855,7 +854,7 @@ void nonGenesisInitHandlesTokenRelMigrationToDisk() { APPS.save(selfId, app); // when: - subject.init(platform, dualState, RESTART, currentVersion); + subject.init(platform, platformState, RESTART, currentVersion); verify(mapToDiskMigration) .migrateToDiskAsApropos( INSERTIONS_PER_COPY, @@ -939,7 +938,7 @@ void testGenesisState() { final var app = createApp(platform); APPS.save(platform.getSelfId(), app); - assertDoesNotThrow(() -> servicesState.init(platform, new DualStateImpl(), InitTrigger.GENESIS, null)); + assertDoesNotThrow(() -> servicesState.init(platform, new PlatformState(), InitTrigger.GENESIS, null)); } @Test diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/DualStateAccessorTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/PlatformStateAccessorTest.java similarity index 75% rename from hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/DualStateAccessorTest.java rename to hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/PlatformStateAccessorTest.java index 4a29cb338bdf..0a80dba5ab69 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/DualStateAccessorTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/PlatformStateAccessorTest.java @@ -18,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,22 +26,22 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class DualStateAccessorTest { +class PlatformStateAccessorTest { @Mock - private SwirldDualState dualState; + private PlatformState platformState; @Test void beanMethodsWork() { // setup: - final var subject = new DualStateAccessor(); + final var subject = new PlatformStateAccessor(); // expect: - assertNull(subject.getDualState()); + assertNull(subject.getPlatformState()); // and when: - subject.setDualState(dualState); + subject.setPlatformState(platformState); // expect: - Assertions.assertSame(dualState, subject.getDualState()); + Assertions.assertSame(platformState, subject.getPlatformState()); } } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/logic/BlockManagerTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/logic/BlockManagerTest.java index 8acc2c8be62c..3be36c6b7ec3 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/logic/BlockManagerTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/logic/BlockManagerTest.java @@ -31,13 +31,13 @@ import static org.mockito.Mockito.verify; import com.hedera.node.app.service.mono.context.properties.BootstrapProperties; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext; import com.hedera.node.app.service.mono.stream.RecordsRunningHashLeaf; import com.hedera.test.utils.TxnUtils; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.RunningHash; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import java.time.Instant; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -60,10 +60,10 @@ class BlockManagerTest { @Mock private RecordsRunningHashLeaf runningHashLeaf; - private DualStateAccessor dualStateAccessor; + private PlatformStateAccessor platformStateAccessor; @Mock - private SwirldDualState dualState; + private PlatformState platformState; private BlockManager subject; @@ -71,9 +71,10 @@ class BlockManagerTest { void setUp() { given(bootstrapProperties.getLongProperty(HEDERA_RECORD_STREAM_LOG_PERIOD)) .willReturn(blockPeriodSecs); - dualStateAccessor = new DualStateAccessor(); - dualStateAccessor.setDualState(dualState); - subject = new BlockManager(bootstrapProperties, () -> networkContext, () -> runningHashLeaf, dualStateAccessor); + platformStateAccessor = new PlatformStateAccessor(); + platformStateAccessor.setPlatformState(platformState); + subject = new BlockManager( + bootstrapProperties, () -> networkContext, () -> runningHashLeaf, platformStateAccessor); } @Test @@ -175,39 +176,39 @@ void computesNewBlockIfIsFirstTransactionAfterFreezeRestart() throws Interrupted given(networkContext.firstConsTimeOfCurrentBlock()).willReturn(aTime); given(runningHashLeaf.currentRunningHash()).willReturn(aFullBlockHash); given(networkContext.getAlignmentBlockNo()).willReturn(someBlockNo); - given(dualState.getLastFrozenTime()).willReturn(aTime); - given(dualState.getFreezeTime()).willReturn(aTime); + given(platformState.getLastFrozenTime()).willReturn(aTime); + given(platformState.getFreezeTime()).willReturn(aTime); final var values = subject.computeBlockValues(someTime, gasLimit); assertEquals(someBlockNo + 1, values.getNumber()); - verify(dualState).setFreezeTime(null); + verify(platformState).setFreezeTime(null); } @Test void computesSameBlockIfPastAndCurrentFreezeTimeAreNull() { given(networkContext.firstConsTimeOfCurrentBlock()).willReturn(aTime); given(networkContext.getAlignmentBlockNo()).willReturn(someBlockNo); - given(dualState.getFreezeTime()).willReturn(null); - Mockito.lenient().when(dualState.getLastFrozenTime()).thenReturn(null); + given(platformState.getFreezeTime()).willReturn(null); + Mockito.lenient().when(platformState.getLastFrozenTime()).thenReturn(null); final var values = subject.computeBlockValues(someTime, gasLimit); assertEquals(someBlockNo, values.getNumber()); - verify(dualState, never()).setFreezeTime(null); + verify(platformState, never()).setFreezeTime(null); } @Test void computesSameBlockIfPastAndCurrentFreezeTimeDontMatch() { given(networkContext.firstConsTimeOfCurrentBlock()).willReturn(aTime); given(networkContext.getAlignmentBlockNo()).willReturn(someBlockNo); - given(dualState.getLastFrozenTime()).willReturn(aTime); - given(dualState.getFreezeTime()).willReturn(anotherTime); + given(platformState.getLastFrozenTime()).willReturn(aTime); + given(platformState.getFreezeTime()).willReturn(anotherTime); final var values = subject.computeBlockValues(someTime, gasLimit); assertEquals(someBlockNo, values.getNumber()); - verify(dualState, never()).setFreezeTime(null); + verify(platformState, never()).setFreezeTime(null); } @Test diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContextTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContextTest.java index 16db7aef4c25..a6903ce79456 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContextTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/state/merkle/MerkleNetworkContextTest.java @@ -50,7 +50,7 @@ import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.hapi.utils.throttles.GasLimitDeterministicThrottle; import com.hedera.node.app.service.mono.fees.congestion.MultiplierSources; -import com.hedera.node.app.service.mono.state.DualStateAccessor; +import com.hedera.node.app.service.mono.state.PlatformStateAccessor; import com.hedera.node.app.service.mono.state.merkle.internals.BytesElement; import com.hedera.node.app.service.mono.state.submerkle.ExchangeRates; import com.hedera.node.app.service.mono.state.submerkle.SequenceNumber; @@ -66,7 +66,7 @@ import com.swirlds.base.state.MutabilityException; import com.swirlds.common.crypto.Hash; import com.swirlds.fcqueue.FCQueue; -import com.swirlds.platform.state.DualStateImpl; +import com.swirlds.platform.state.PlatformState; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -534,7 +534,7 @@ void summarizesHashesAsExpected() { @Test void summarizesStateVersionAsExpected() { throttling = mock(FunctionalityThrottling.class); - final var accessor = mock(DualStateAccessor.class); + final var accessor = mock(PlatformStateAccessor.class); given(throttling.allActiveThrottles()).willReturn(activeThrottles()); given(throttling.gasLimitThrottle()).willReturn(gasLimitDeterministicThrottle); @@ -656,10 +656,10 @@ void summarizesPendingUpdateAsExpected() { final var someTime = Instant.ofEpochSecond(1_234_567L, 890); throttling = mock(FunctionalityThrottling.class); - final var dualState = mock(DualStateImpl.class); - final var accessor = mock(DualStateAccessor.class); + final var platformState = mock(PlatformState.class); + final var accessor = mock(PlatformStateAccessor.class); - given(accessor.getDualState()).willReturn(dualState); + given(accessor.getPlatformState()).willReturn(platformState); // and: final var desiredWithPreparedUnscheduledMaintenance = "The network context (state version 13) is,\n" @@ -728,7 +728,7 @@ void summarizesPendingUpdateAsExpected() { // then: assertEquals(desiredWithPreparedUnscheduledMaintenance, subject.summarizedWith(accessor)); - given(dualState.getFreezeTime()).willReturn(someTime); + given(platformState.getFreezeTime()).willReturn(someTime); assertEquals(desiredWithPreparedAndScheduledMaintenance, subject.summarizedWith(accessor)); } diff --git a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/network/UpgradeActionsTest.java b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/network/UpgradeActionsTest.java index 915285226e5d..6f8027f020da 100644 --- a/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/network/UpgradeActionsTest.java +++ b/hedera-node/hedera-mono-service/src/test/java/com/hedera/node/app/service/mono/txns/network/UpgradeActionsTest.java @@ -41,7 +41,7 @@ import com.hedera.test.extensions.LoggingTarget; import com.hedera.test.utils.IdUtils; import com.hedera.test.utils.TestFileUtils; -import com.swirlds.platform.state.DualStateImpl; +import com.swirlds.platform.state.PlatformState; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -76,7 +76,7 @@ class UpgradeActionsTest { private GlobalDynamicProperties dynamicProperties; @Mock - private DualStateImpl dualState; + private PlatformState platformState; @Mock private UpgradeActions.UnzipAction unzipAction; @@ -101,7 +101,7 @@ void setUp() { noiseSubFileLoc = TestFileUtils.toPath(markerFilesLoc, "edargpu"); subject = new UpgradeActions( - unzipAction, dynamicProperties, () -> dualState, () -> specialFiles, () -> networkCtx); + unzipAction, dynamicProperties, () -> platformState, () -> specialFiles, () -> networkCtx); } @AfterEach @@ -141,8 +141,8 @@ void catchesUpOnUpgradePreparationIfInContext() throws IOException { given(networkCtx.getPreparedUpdateFileNum()).willReturn(150L); given(specialFiles.get(IdUtils.asFile("0.0.150"))).willReturn(PRETEND_ARCHIVE); given(dynamicProperties.upgradeArtifactsLoc()).willReturn(markerFilesLoc); - given(dualState.getFreezeTime()).willReturn(then); - given(dualState.getLastFrozenTime()).willReturn(then); + given(platformState.getFreezeTime()).willReturn(then); + given(platformState.getLastFrozenTime()).willReturn(then); assertTrue(new File(markerFilesLoc).mkdirs()); subject.catchUpOnMissedSideEffects(); @@ -163,14 +163,14 @@ void catchUpIsNoopWithNothingToDo() { "Should not create " + EXEC_IMMEDIATE_MARKER + " if no prepared upgrade in state"); assertFalse( Paths.get(markerFilesLoc, FREEZE_SCHEDULED_MARKER).toFile().exists(), - "Should not create " + FREEZE_SCHEDULED_MARKER + " if dual freeze time is null"); + "Should not create " + FREEZE_SCHEDULED_MARKER + " if platform freeze time is null"); } @Test - void doesntCatchUpOnFreezeScheduleIfInDualAndNoUpgradeIsPrepared() { + void doesntCatchUpOnFreezeScheduleIfInPlatformStateAndNoUpgradeIsPrepared() { rmIfPresent(FREEZE_SCHEDULED_MARKER); - given(dualState.getFreezeTime()).willReturn(then); + given(platformState.getFreezeTime()).willReturn(then); subject.catchUpOnMissedSideEffects(); @@ -180,10 +180,10 @@ void doesntCatchUpOnFreezeScheduleIfInDualAndNoUpgradeIsPrepared() { } @Test - void catchesUpOnFreezeScheduleIfInDualAndUpgradeIsPrepared() throws IOException { + void catchesUpOnFreezeScheduleIfInPlatformStateAndUpgradeIsPrepared() throws IOException { rmIfPresent(FREEZE_SCHEDULED_MARKER); - given(dualState.getFreezeTime()).willReturn(then); + given(platformState.getFreezeTime()).willReturn(then); given(networkCtx.hasPreparedUpgrade()).willReturn(true); given(dynamicProperties.upgradeArtifactsLoc()).willReturn(markerFilesLoc); @@ -197,17 +197,17 @@ void freezeCatchUpWritesNoMarkersIfJustUnfrozen() { rmIfPresent(FREEZE_ABORTED_MARKER); rmIfPresent(FREEZE_SCHEDULED_MARKER); - given(dualState.getFreezeTime()).willReturn(then); - given(dualState.getLastFrozenTime()).willReturn(then); + given(platformState.getFreezeTime()).willReturn(then); + given(platformState.getLastFrozenTime()).willReturn(then); subject.catchUpOnMissedSideEffects(); assertFalse( Paths.get(markerFilesLoc, FREEZE_ABORTED_MARKER).toFile().exists(), - "Should not create " + FREEZE_ABORTED_MARKER + " if dual last frozen time is freeze time"); + "Should not create " + FREEZE_ABORTED_MARKER + " if platform last frozen time is freeze time"); assertFalse( Paths.get(markerFilesLoc, FREEZE_SCHEDULED_MARKER).toFile().exists(), - "Should not create " + FREEZE_SCHEDULED_MARKER + " if dual last frozen time is freeze time"); + "Should not create " + FREEZE_SCHEDULED_MARKER + " if platform last frozen time is freeze time"); } @Test @@ -302,7 +302,7 @@ void setsExpectedFreezeAndWritesMarkerForFreezeUpgrade() throws IOException { subject.scheduleFreezeUpgradeAt(then); - verify(dualState).setFreezeTime(then); + verify(platformState).setFreezeTime(then); assertMarkerCreated(FREEZE_SCHEDULED_MARKER, then); } @@ -313,7 +313,7 @@ void setsExpectedFreezeOnlyForFreezeOnly() { subject.scheduleFreezeOnlyAt(then); - verify(dualState).setFreezeTime(then); + verify(platformState).setFreezeTime(then); assertFalse( Paths.get(markerFilesLoc, FREEZE_SCHEDULED_MARKER).toFile().exists(), @@ -321,14 +321,14 @@ void setsExpectedFreezeOnlyForFreezeOnly() { } @Test - void nullsOutDualOnAborting() throws IOException { + void nullsOutPlatformStateOnAborting() throws IOException { rmIfPresent(FREEZE_ABORTED_MARKER); given(dynamicProperties.upgradeArtifactsLoc()).willReturn(markerFilesLoc); subject.abortScheduledFreeze(); - verify(dualState).setFreezeTime(null); + verify(platformState).setFreezeTime(null); assertMarkerCreated(FREEZE_ABORTED_MARKER, null); } @@ -346,7 +346,7 @@ void canStillWriteMarkersEvenIfDirDoesntExist() throws IOException { subject.abortScheduledFreeze(); - verify(dualState).setFreezeTime(null); + verify(platformState).setFreezeTime(null); assertMarkerCreated(FREEZE_ABORTED_MARKER, null, otherMarkerFilesLoc); @@ -367,7 +367,7 @@ void complainsLoudlyWhenUnableToWriteMarker() throws IOException { subject.abortScheduledFreeze(); - verify(dualState).setFreezeTime(null); + verify(platformState).setFreezeTime(null); assertThat( logCaptor.errorLogs(), @@ -380,7 +380,7 @@ void complainsLoudlyWhenUnableToWriteMarker() throws IOException { void determinesIfFreezeIsScheduled() { assertFalse(subject.isFreezeScheduled()); - given(dualState.getFreezeTime()).willReturn(then); + given(platformState.getFreezeTime()).willReturn(then); assertTrue(subject.isFreezeScheduled()); } diff --git a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java index a197053446c8..22fae2700dac 100644 --- a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java +++ b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/src/main/java/com/swirlds/demo/crypto/CryptocurrencyDemoState.java @@ -32,11 +32,11 @@ import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.SwirldsPlatform; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -199,7 +199,7 @@ public synchronized CryptocurrencyDemoState copy() { } @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public void handleConsensusRound(final Round round, final PlatformState swirldDualState) { throwIfImmutable(); round.forEachEventTransaction( (event, transaction) -> handleTransaction(event.getCreatorId(), true, transaction)); @@ -377,7 +377,7 @@ public void genesisInit() { @Override public void init( final Platform platform, - final SwirldDualState swirldDualState, + final PlatformState swirldDualState, final InitTrigger trigger, final SoftwareVersion previousSoftwareVersion) { this.platform = (SwirldsPlatform) platform; diff --git a/platform-sdk/platform-apps/demos/HashgraphDemo/src/main/java/com/swirlds/demo/hashgraph/HashgraphDemoState.java b/platform-sdk/platform-apps/demos/HashgraphDemo/src/main/java/com/swirlds/demo/hashgraph/HashgraphDemoState.java index 36cb3f012c29..60858a6c7dc5 100644 --- a/platform-sdk/platform-apps/demos/HashgraphDemo/src/main/java/com/swirlds/demo/hashgraph/HashgraphDemoState.java +++ b/platform-sdk/platform-apps/demos/HashgraphDemo/src/main/java/com/swirlds/demo/hashgraph/HashgraphDemoState.java @@ -30,8 +30,8 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; /** @@ -67,7 +67,7 @@ private HashgraphDemoState(final HashgraphDemoState sourceState) { } @Override - public synchronized void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) {} + public synchronized void handleConsensusRound(final Round round, final PlatformState platformState) {} /** * {@inheritDoc} diff --git a/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java b/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java index 050620793fc5..103fce8f360c 100644 --- a/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java +++ b/platform-sdk/platform-apps/demos/HelloSwirldDemo/src/main/java/com/swirlds/demo/hello/HelloSwirldDemoState.java @@ -30,8 +30,8 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.transaction.Transaction; import java.io.IOException; @@ -99,7 +99,7 @@ private HelloSwirldDemoState(final HelloSwirldDemoState sourceState) { } @Override - public synchronized void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public synchronized void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); round.forEachTransaction(this::handleTransaction); } diff --git a/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java b/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java index b40d1ccb957a..dfccdacf9caf 100644 --- a/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java +++ b/platform-sdk/platform-apps/demos/StatsDemo/src/main/java/com/swirlds/demo/stats/StatsDemoState.java @@ -30,8 +30,8 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; /** @@ -69,7 +69,7 @@ private StatsDemoState(final StatsDemoState sourceState) { } @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) {} + public void handleConsensusRound(final Round round, final PlatformState platformState) {} /** * {@inheritDoc} diff --git a/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java b/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java index 2e1def946d93..fe5c26fbc3ad 100644 --- a/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java +++ b/platform-sdk/platform-apps/tests/AddressBookTestingTool/src/main/java/com/swirlds/demo/addressbook/AddressBookTestingToolState.java @@ -44,11 +44,11 @@ import com.swirlds.common.utility.ByteUtils; import com.swirlds.common.utility.StackTrace; import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -109,8 +109,8 @@ private static class ClassVersion { /** * The number of rounds handled by this app. Is incremented each time - * {@link #handleConsensusRound(Round, SwirldDualState)} is called. Note that this may not actually equal the round - * number, since we don't call {@link #handleConsensusRound(Round, SwirldDualState)} for rounds with no events. + * {@link #handleConsensusRound(Round, PlatformState)} is called. Note that this may not actually equal the round + * number, since we don't call {@link #handleConsensusRound(Round, PlatformState)} for rounds with no events. * *

    * Affects the hash of this node. @@ -161,11 +161,11 @@ public synchronized AddressBookTestingToolState copy() { @Override public void init( @NonNull final Platform platform, - @NonNull final SwirldDualState swirldDualState, + @NonNull final PlatformState platformState, @NonNull final InitTrigger trigger, @Nullable final SoftwareVersion previousSoftwareVersion) { Objects.requireNonNull(platform, "the platform cannot be null"); - Objects.requireNonNull(swirldDualState, "the swirld dual state cannot be null"); + Objects.requireNonNull(platformState, "the platform state cannot be null"); Objects.requireNonNull(trigger, "the init trigger cannot be null"); addressBookConfig = platform.getContext().getConfiguration().getConfigData(AddressBookConfig.class); testingToolConfig = platform.getContext().getConfiguration().getConfigData(AddressBookTestingToolConfig.class); @@ -184,9 +184,9 @@ public void init( * {@inheritDoc} */ @Override - public void handleConsensusRound(@NonNull final Round round, @NonNull final SwirldDualState swirldDualState) { + public void handleConsensusRound(@NonNull final Round round, @NonNull final PlatformState platformState) { Objects.requireNonNull(round, "the round cannot be null"); - Objects.requireNonNull(swirldDualState, "the swirld dual state cannot be null"); + Objects.requireNonNull(platformState, "the platform state cannot be null"); throwIfImmutable(); if (roundsHandled == 0 && !freezeAfterGenesis.equals(Duration.ZERO)) { @@ -195,7 +195,7 @@ public void handleConsensusRound(@NonNull final Round round, @NonNull final Swir STARTUP.getMarker(), "Setting freeze time to {} seconds after genesis.", freezeAfterGenesis.getSeconds()); - swirldDualState.setFreezeTime(round.getConsensusTimestamp().plus(freezeAfterGenesis)); + platformState.setFreezeTime(round.getConsensusTimestamp().plus(freezeAfterGenesis)); } roundsHandled++; diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java index 570f73a283e5..44b241e5682c 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/main/java/com/swirlds/demo/consistency/ConsistencyTestingToolState.java @@ -26,11 +26,11 @@ import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.utility.NonCryptographicHashing; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.ConsensusTransaction; @@ -75,8 +75,8 @@ private static class ClassVersion { /** * The number of rounds handled by this app. Is incremented each time - * {@link #handleConsensusRound(Round, SwirldDualState)} is called. Note that this may not actually equal the round - * number, since we don't call {@link #handleConsensusRound(Round, SwirldDualState)} for rounds with no events. + * {@link #handleConsensusRound(Round, PlatformState)} is called. Note that this may not actually equal the round + * number, since we don't call {@link #handleConsensusRound(Round, PlatformState)} for rounds with no events. * *

    * Affects the hash of this node. @@ -130,12 +130,12 @@ private ConsistencyTestingToolState(@NonNull final ConsistencyTestingToolState t @Override public void init( @NonNull final Platform platform, - @NonNull final SwirldDualState swirldDualState, + @NonNull final PlatformState platformState, @NonNull final InitTrigger trigger, @Nullable final SoftwareVersion previousSoftwareVersion) { Objects.requireNonNull(platform); - Objects.requireNonNull(swirldDualState); + Objects.requireNonNull(platformState); Objects.requireNonNull(trigger); final StateConfig stateConfig = platform.getContext().getConfiguration().getConfigData(StateConfig.class); @@ -242,9 +242,9 @@ public void preHandle(@NonNull final Event event) { * Writes the round and its contents to a log on disk */ @Override - public void handleConsensusRound(final @NonNull Round round, final @NonNull SwirldDualState swirldDualState) { + public void handleConsensusRound(final @NonNull Round round, final @NonNull PlatformState platformState) { Objects.requireNonNull(round); - Objects.requireNonNull(swirldDualState); + Objects.requireNonNull(platformState); if (roundsHandled == 0 && !freezeAfterGenesis.equals(Duration.ZERO)) { // This is the first round after genesis. @@ -252,7 +252,7 @@ public void handleConsensusRound(final @NonNull Round round, final @NonNull Swir STARTUP.getMarker(), "Setting freeze time to {} seconds after genesis.", freezeAfterGenesis.getSeconds()); - swirldDualState.setFreezeTime(round.getConsensusTimestamp().plus(freezeAfterGenesis)); + platformState.setFreezeTime(round.getConsensusTimestamp().plus(freezeAfterGenesis)); } roundsHandled++; diff --git a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java index a8b51334be6a..74c067ab2da7 100644 --- a/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java +++ b/platform-sdk/platform-apps/tests/ISSTestingTool/src/main/java/com/swirlds/demo/iss/ISSTestingToolState.java @@ -40,11 +40,11 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.scratchpad.Scratchpad; import com.swirlds.common.utility.ByteUtils; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -149,7 +149,7 @@ public synchronized ISSTestingToolState copy() { @Override public void init( final Platform platform, - final SwirldDualState swirldDualState, + final PlatformState platformState, final InitTrigger trigger, final SoftwareVersion previousSoftwareVersion) { @@ -173,7 +173,7 @@ public void init( * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); final Iterator eventIterator = round.iterator(); diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index 548edd45f9d4..298bc4434628 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -33,11 +33,11 @@ import com.swirlds.merkledb.MerkleDb; import com.swirlds.merkledb.MerkleDbDataSourceBuilder; import com.swirlds.merkledb.MerkleDbTableConfig; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.ConsensusEvent; @@ -224,7 +224,7 @@ private void genesisInit(final Platform platform) { @Override public void init( final Platform platform, - final SwirldDualState swirldDualState, + final PlatformState platformState, final InitTrigger trigger, final SoftwareVersion previousSoftwareVersion) { @@ -274,7 +274,7 @@ private static MigrationTestingToolTransaction parseTransaction(final Transactio * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); for (final Iterator eventIt = round.iterator(); eventIt.hasNext(); ) { final ConsensusEvent event = eventIt.next(); diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java index fbe86a209efe..44a12e872190 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/PlatformTestingToolState.java @@ -70,11 +70,11 @@ import com.swirlds.merkle.map.test.pta.MapKey; import com.swirlds.platform.ParameterProvider; import com.swirlds.platform.Utilities; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.ConsensusEvent; @@ -1037,9 +1037,9 @@ private void handleControlTransaction( /** * Handle the freeze transaction type. */ - private void handleFreezeTransaction(final TestTransaction testTransaction, final SwirldDualState swirldDualState) { + private void handleFreezeTransaction(final TestTransaction testTransaction, final PlatformState platformState) { final FreezeTransaction freezeTx = testTransaction.getFreezeTransaction(); - FreezeTransactionHandler.freeze(freezeTx, swirldDualState); + FreezeTransactionHandler.freeze(freezeTx, platformState); } /** @@ -1057,7 +1057,7 @@ protected void preHandleTransaction(final Transaction transaction) { } @Override - public synchronized void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public synchronized void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); if (!initialized.get()) { throw new IllegalStateException("handleConsensusRound() called before init()"); @@ -1065,7 +1065,7 @@ public synchronized void handleConsensusRound(final Round round, final SwirldDua delay(); updateTransactionCounters(); round.forEachEventTransaction((event, transaction) -> - handleConsensusTransaction(event, transaction, swirldDualState, round.getRoundNum())); + handleConsensusTransaction(event, transaction, platformState, round.getRoundNum())); } /** @@ -1089,12 +1089,12 @@ private void updateTransactionCounters() { private void handleConsensusTransaction( final ConsensusEvent event, final ConsensusTransaction trans, - final SwirldDualState dualState, + final PlatformState platformState, final long roundNum) { try { waitForSignatureValidation(trans); handleTransaction( - event.getCreatorId(), event.getTimeCreated(), trans.getConsensusTimestamp(), trans, dualState); + event.getCreatorId(), event.getTimeCreated(), trans.getConsensusTimestamp(), trans, platformState); } catch (final InterruptedException e) { logger.info( TESTING_EXCEPTIONS_ACCEPTABLE_RECONNECT.getMarker(), @@ -1123,7 +1123,7 @@ private void handleTransaction( @NonNull final Instant timeCreated, @NonNull final Instant timestamp, @NonNull final ConsensusTransaction trans, - @NonNull final SwirldDualState swirldDualState) { + @NonNull final PlatformState platformState) { if (getConfig().isAppendSig()) { try { final TestTransactionWrapper testTransactionWrapper = @@ -1211,7 +1211,7 @@ private void handleTransaction( handleControlTransaction(testTransaction.get(), id, timestamp); break; case FREEZETRANSACTION: - handleFreezeTransaction(testTransaction.get(), swirldDualState); + handleFreezeTransaction(testTransaction.get(), platformState); break; case SIMPLEACTION: handleSimpleAction(testTransaction.get().getSimpleAction()); @@ -1260,7 +1260,7 @@ private void genesisInit() { @Override public void init( final Platform platform, - final SwirldDualState swirldDualState, + final PlatformState platformState, final InitTrigger trigger, final SoftwareVersion previousSoftwareVersion) { @@ -1289,7 +1289,7 @@ public void init( initializeExpirationQueueAndAccountsSet(); logger.info(LOGM_DEMO_INFO, "Dual state received in init function {}", () -> new ApplicationDualStatePayload( - swirldDualState.getFreezeTime(), swirldDualState.getLastFrozenTime()) + platformState.getFreezeTime(), platformState.getLastFrozenTime()) .toString()); logger.info(LOGM_STARTUP, () -> new SoftwareVersionPayload( diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java index 219da4ae3400..406becf566e7 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/main/java/com/swirlds/demo/platform/freeze/FreezeTransactionHandler.java @@ -17,7 +17,7 @@ package com.swirlds.demo.platform.freeze; import com.swirlds.demo.platform.fs.stresstest.proto.FreezeTransaction; -import com.swirlds.platform.system.SwirldDualState; +import com.swirlds.platform.state.PlatformState; import java.time.Instant; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,10 +28,10 @@ public class FreezeTransactionHandler { private static final Logger logger = LogManager.getLogger(FreezeTransactionHandler.class); private static final Marker LOGM_FREEZE = MarkerManager.getMarker("FREEZE"); - public static boolean freeze(final FreezeTransaction transaction, final SwirldDualState swirldDualState) { + public static boolean freeze(final FreezeTransaction transaction, final PlatformState platformState) { logger.debug(LOGM_FREEZE, "Handling FreezeTransaction: " + transaction); try { - swirldDualState.setFreezeTime(Instant.ofEpochSecond(transaction.getStartTimeEpochSecond())); + platformState.setFreezeTime(Instant.ofEpochSecond(transaction.getStartTimeEpochSecond())); return true; } catch (IllegalArgumentException ex) { logger.warn(LOGM_FREEZE, "FreezeTransactionHandler::freeze fails. {}", ex.getMessage()); diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/MapValueFCQTests.java b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/MapValueFCQTests.java index f05181e7235c..7e4a56ad1a00 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/MapValueFCQTests.java +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/src/test/java/com/swirlds/demo/merkle/map/MapValueFCQTests.java @@ -39,7 +39,7 @@ import com.swirlds.merkle.map.test.lifecycle.ExpectedValue; import com.swirlds.merkle.map.test.pta.MapKey; import com.swirlds.merkle.map.test.pta.TransactionRecord; -import com.swirlds.platform.state.DualStateImpl; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; @@ -87,7 +87,7 @@ public static void setUp() throws ConstructableRegistryException { AddressBook addressBook = Mockito.spy(AddressBook.class); when(addressBook.getNumberWithWeight()).thenReturn(4); when(platform.getAddressBook()).thenReturn(addressBook); - state.init(platform, new DualStateImpl(), InitTrigger.RESTART, SoftwareVersion.NO_VERSION); + state.init(platform, new PlatformState(), InitTrigger.RESTART, SoftwareVersion.NO_VERSION); state.initChildren(); } diff --git a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java index fd4e3400a12d..c5aed3bb7173 100644 --- a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java +++ b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/src/main/java/com/swirlds/demo/stats/signing/StatsSigningTestingToolState.java @@ -37,8 +37,8 @@ import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.ConsensusTransaction; @@ -121,7 +121,7 @@ public void preHandle(final Event event) { * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); round.forEachTransaction(this::handleTransaction); } diff --git a/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java b/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java index e84371806813..6c6d0600ef02 100644 --- a/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java +++ b/platform-sdk/platform-apps/tests/StressTestingTool/src/main/java/com/swirlds/demo/stress/StressTestingToolState.java @@ -31,11 +31,11 @@ import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; import com.swirlds.common.utility.ByteUtils; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.ConsensusTransaction; @@ -78,7 +78,7 @@ public synchronized StressTestingToolState copy() { public void init( @NonNull final Platform platform, - @NonNull final SwirldDualState swirldDualState, + @NonNull final PlatformState platformState, @NonNull final InitTrigger trigger, @NonNull final SoftwareVersion previousSoftwareVersion) { this.config = platform.getContext().getConfiguration().getConfigData(StressTestingToolConfig.class); @@ -96,7 +96,7 @@ public void preHandle(final Event event) { * {@inheritDoc} */ @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public void handleConsensusRound(final Round round, final PlatformState platformState) { throwIfImmutable(); round.forEachTransaction(this::handleTransaction); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java index bfad19a14daf..cead95025e11 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java @@ -39,7 +39,6 @@ import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.ConsensusMetrics; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; @@ -209,44 +208,7 @@ public ConsensusImpl( @Override public void loadFromSignedState(@NonNull final SignedState signedState) { reset(); - final PlatformData platformData = - signedState.getState().getPlatformState().getPlatformData(); - if (platformData.getEvents() != null) { - loadLegacyState(platformData); - } else { - loadSnapshot(platformData.getSnapshot()); - } - } - - private void loadLegacyState(@NonNull final PlatformData platformData) { - migrationMode = true; - - // create all the rounds that we have events for - rounds.loadFromMinGen(platformData.getMinGenInfo()); - updateRoundGenerations(rounds.getFameDecidedBelow()); - - for (final EventImpl event : platformData.getEvents()) { - event.setRoundCreated( - // this is where round created used to be stored, only needed for migration - event.getConsensusData().getRoundCreated()); - calculateMetadata(event); - event.setConsensus(true); - // events are stored in consensus order, so the last event in consensus order should be - // incremented by 1 to get the numConsensus - numConsensus = event.getConsensusOrder() + 1; - } - - // The lastConsensusTime is equal to the last transaction that has been handled - lastConsensusTime = platformData.getConsensusTimestamp(); - - logger.debug( - STARTUP.getMarker(), - "ConsensusImpl is initialized from signed state. minRound: {}(min gen = {})," - + " maxRound: {}(max gen = {})", - this::getMinRound, - this::getMinRoundGeneration, - this::getMaxRound, - this::getMaxRoundGeneration); + loadSnapshot(signedState.getState().getPlatformState().getSnapshot()); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 79666efb749c..4f37234401b8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -82,7 +82,6 @@ import com.swirlds.platform.dispatch.triggers.flow.DiskStateLoadedTrigger; import com.swirlds.platform.dispatch.triggers.flow.ReconnectStateLoadedTrigger; import com.swirlds.platform.event.EventCounter; -import com.swirlds.platform.event.EventUtils; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.AsyncEventCreationManager; import com.swirlds.platform.event.creation.EventCreationManager; @@ -194,9 +193,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicLong; @@ -469,8 +466,7 @@ public class SwirldsPlatform implements Platform { if (emergencyRecoveryManager.getEmergencyRecoveryFile() != null) { epochHash = emergencyRecoveryManager.getEmergencyRecoveryFile().hash(); } else { - epochHash = - initialState.getState().getPlatformState().getPlatformData().getEpochHash(); + epochHash = initialState.getState().getPlatformState().getEpochHash(); } StartupStateUtils.doRecoveryCleanup( @@ -896,7 +892,7 @@ public class SwirldsPlatform implements Platform { initialMinimumGenerationNonAncient = 0; } else { initialMinimumGenerationNonAncient = - initialState.getState().getPlatformState().getPlatformData().getMinimumGenerationNonAncient(); + initialState.getState().getPlatformState().getMinimumGenerationNonAncient(); latestImmutableState.setState(initialState.reserve("set latest immutable to initial state")); stateManagementComponent.stateToLoad(initialState, SourceOfSignedState.DISK); savedStateController.registerSignedStateFromDisk(initialState); @@ -1002,8 +998,7 @@ private void initializeState(@NonNull final SignedState signedState) { previousSoftwareVersion = NO_VERSION; trigger = GENESIS; } else { - previousSoftwareVersion = - signedState.getState().getPlatformState().getPlatformData().getCreationSoftwareVersion(); + previousSoftwareVersion = signedState.getState().getPlatformState().getCreationSoftwareVersion(); trigger = RESTART; } @@ -1018,7 +1013,7 @@ private void initializeState(@NonNull final SignedState signedState) { throw new IllegalStateException("Expected initial swirld state to be unhashed"); } - initialState.getSwirldState().init(this, initialState.getSwirldDualState(), trigger, previousSoftwareVersion); + initialState.getSwirldState().init(this, initialState.getPlatformState(), trigger, previousSoftwareVersion); abortAndThrowIfInterrupted( () -> { @@ -1060,27 +1055,7 @@ private void loadStateIntoEventCreator(@NonNull final SignedState signedState) { try { eventCreator.setMinimumGenerationNonAncient( - signedState.getState().getPlatformState().getPlatformData().getMinimumGenerationNonAncient()); - - // newer states will not have events, so we need to check for null - if (signedState.getState().getPlatformState().getPlatformData().getEvents() == null) { - return; - } - - // The event creator may not be started yet. To avoid filling up queues, only register - // the latest event from each creator. These are the only ones the event creator cares about. - - final Map latestEvents = new HashMap<>(); - - for (final EventImpl event : - signedState.getState().getPlatformState().getPlatformData().getEvents()) { - latestEvents.put(event.getCreatorId(), event); - } - - for (final EventImpl event : latestEvents.values()) { - eventCreator.registerEvent(event); - } - + signedState.getState().getPlatformState().getMinimumGenerationNonAncient()); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("interrupted while loading state into event creator", e); @@ -1096,23 +1071,7 @@ private void loadStateIntoConsensus(@NonNull final SignedState signedState) { Objects.requireNonNull(signedState); consensusRef.get().loadFromSignedState(signedState); - - // old states will have events in them that need to be loaded, newer states will not - if (signedState.getEvents() != null) { - shadowGraph.initFromEvents( - EventUtils.prepareForShadowGraph( - // we need to pass in a copy of the array, otherwise prepareForShadowGraph will rearrange - // the events in the signed state which will cause issues for other components that depend - // on it - signedState.getEvents().clone()), - // we need to provide the minGen from consensus so that expiry matches after a restart/reconnect - consensusRef.get().getMinRoundGeneration()); - - // Intentionally don't bother initiating the latestEventTipsetTracker here. We don't support this - // code path way any more. - } else { - shadowGraph.startFromGeneration(consensusRef.get().getMinGenerationNonAncient()); - } + shadowGraph.startFromGeneration(consensusRef.get().getMinGenerationNonAncient()); gossip.loadFromSignedState(signedState); } @@ -1138,13 +1097,9 @@ private void loadReconnectState(final SignedState signedState) { .getSwirldState() .init( this, - signedState.getState().getSwirldDualState(), + signedState.getState().getPlatformState(), InitTrigger.RECONNECT, - signedState - .getState() - .getPlatformState() - .getPlatformData() - .getCreationSoftwareVersion()); + signedState.getState().getPlatformState().getCreationSoftwareVersion()); if (!Objects.equals(signedState.getState().getHash(), reconnectHash)) { throw new IllegalStateException( "State hash is not permitted to change during a reconnect init() call. Previous hash was " @@ -1207,11 +1162,8 @@ private void loadReconnectState(final SignedState signedState) { try { preconsensusEventWriter.registerDiscontinuity(signedState.getRound()); - preconsensusEventWriter.setMinimumGenerationNonAncient(signedState - .getState() - .getPlatformState() - .getPlatformData() - .getMinimumGenerationNonAncient()); + preconsensusEventWriter.setMinimumGenerationNonAncient( + signedState.getState().getPlatformState().getMinimumGenerationNonAncient()); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("interrupted while loading updating PCES after reconnect", e); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index 28567778d6de..3faa7da6a915 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -78,18 +78,8 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti SignedStateFileReader.readStateFile(platformContext, statePath); try (final ReservedSignedState reservedSignedState = deserializedSignedState.reservedSignedState()) { System.out.printf("Replacing platform data %n"); - reservedSignedState - .get() - .getState() - .getPlatformState() - .getPlatformData() - .setRound(PlatformData.GENESIS_ROUND); - reservedSignedState - .get() - .getState() - .getPlatformState() - .getPlatformData() - .setSnapshot(SyntheticSnapshot.getGenesisSnapshot()); + reservedSignedState.get().getState().getPlatformState().setRound(PlatformData.GENESIS_ROUND); + reservedSignedState.get().getState().getPlatformState().setSnapshot(SyntheticSnapshot.getGenesisSnapshot()); System.out.printf("Hashing state %n"); MerkleCryptoFactory.getInstance() .digestTreeAsync(reservedSignedState.get().getState()) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/appcomm/AppCommunicationComponent.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/appcomm/AppCommunicationComponent.java index 4f5b19b4960c..30ac6faacb5f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/appcomm/AppCommunicationComponent.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/appcomm/AppCommunicationComponent.java @@ -125,7 +125,7 @@ public void newLatestCompleteStateEvent(@NonNull final SignedState signedState) private void latestCompleteStateHandler(@NonNull final ReservedSignedState reservedSignedState) { final NewSignedStateNotification notification = new NewSignedStateNotification( reservedSignedState.get().getSwirldState(), - reservedSignedState.get().getState().getSwirldDualState(), + reservedSignedState.get().getState().getPlatformState(), reservedSignedState.get().getRound(), reservedSignedState.get().getConsensusTimestamp()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java index 722f8f841841..33692d78162f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/RoundCalculationUtils.java @@ -72,10 +72,9 @@ public static long getMinGenNonAncient( * @return minimum non-ancient generation */ public static long getMinGenNonAncient(final int roundsNonAncient, final SignedState signedState) { - return getMinGenNonAncient(roundsNonAncient, signedState.getRound(), round -> signedState - .getState() - .getPlatformState() - .getPlatformData() - .getMinGen(round)); + return getMinGenNonAncient( + roundsNonAncient, + signedState.getRound(), + round -> signedState.getState().getPlatformState().getMinGen(round)); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java index 59aa2c1aa8fe..9e9c211978bd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/ConsensusRoundHandler.java @@ -48,6 +48,7 @@ import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.ConsensusHandlingMetrics; import com.swirlds.platform.observers.ConsensusRoundObserver; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -367,6 +368,7 @@ private void applyConsensusRoundToState(final ConsensusRound round) throws Inter consensusTimingStat.setTimePoint(1); propagateConsensusData(round); + updatePlatformState(round); if (round.getEventCount() > 0) { consensusHandlingMetrics.recordConsensusTime(round.getConsensusTimestamp()); @@ -399,8 +401,7 @@ private void applyConsensusRoundToState(final ConsensusRound round) throws Inter // . For an accurate stat on how much time it takes to create a signed state, refer to // newSignedStateCycleTiming in Statistics consensusTimingStat.setTimePoint(5); - - updatePlatformState(round); + updateRunningEventHash(); consensusTimingStat.setTimePoint(6); @@ -439,21 +440,29 @@ private boolean timeToSignState(final long roundNum) { } /** - * Populate the {@link com.swirlds.platform.state.PlatformState PlatformState} with all of its needed data. + * Populate the {@link com.swirlds.platform.state.PlatformState PlatformState} with all of its needed data for this + * round, with the exception of the running event hash. Wait until transactions are handled before updating this. + * This makes it less likely that we will have to wait for the hash to be computed. */ - private void updatePlatformState(final ConsensusRound round) throws InterruptedException { - final Hash runningHash = eventsConsRunningHash.getFutureHash().getAndRethrow(); + private void updatePlatformState(final ConsensusRound round) { + final PlatformState platformState = + swirldStateManager.getConsensusState().getPlatformState(); + + platformState.setRound(round.getRoundNum()); + platformState.setConsensusTimestamp(round.getConsensusTimestamp()); + platformState.setCreationSoftwareVersion(softwareVersion); + platformState.setRoundsNonAncient(roundsNonAncient); + platformState.setSnapshot(round.getSnapshot()); + } - swirldStateManager - .getConsensusState() - .getPlatformState() - .getPlatformData() - .setRound(round.getRoundNum()) - .setHashEventsCons(runningHash) - .setConsensusTimestamp(round.getConsensusTimestamp()) - .setCreationSoftwareVersion(softwareVersion) - .setRoundsNonAncient(roundsNonAncient) - .setSnapshot(round.getSnapshot()); + /** + * Update the running event hash in the platform state. + */ + private void updateRunningEventHash() throws InterruptedException { + final PlatformState platformState = + swirldStateManager.getConsensusState().getPlatformState(); + final Hash runningHash = eventsConsRunningHash.getFutureHash().getAndRethrow(); + platformState.setRunningEventHash(runningHash); } private void createSignedState() throws InterruptedException { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java index 4f144d2aa2bb..1991b12b14bf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java @@ -67,11 +67,10 @@ public void validate( private void throwIfOld(final SignedState signedState, final SignedStateValidationData previousStateData) throws SignedStateInvalidException { - if (signedState.getState().getPlatformState().getPlatformData().getRound() < previousStateData.round() + if (signedState.getState().getPlatformState().getRound() < previousStateData.round() || signedState .getState() .getPlatformState() - .getPlatformData() .getConsensusTimestamp() .isBefore(previousStateData.consensusTimestamp())) { logger.error( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectHelper.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectHelper.java index e966c18d1ee9..b7bc25bf5af7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectHelper.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectHelper.java @@ -24,7 +24,6 @@ import com.swirlds.logging.legacy.payload.ReconnectFinishPayload; import com.swirlds.logging.legacy.payload.ReconnectLoadFailurePayload; import com.swirlds.logging.legacy.payload.ReconnectStartPayload; -import com.swirlds.platform.event.EventUtils; import com.swirlds.platform.network.Connection; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.ReservedSignedState; @@ -150,11 +149,6 @@ private ReservedSignedState reconnectLearner(final Connection conn, final Signed {}""", () -> reservedState.get().getState().getInfoString(stateConfig.debugHashDepth())); - logger.info( - RECONNECT.getMarker(), - "signed state events:\n{}", - () -> EventUtils.toShortStrings(reservedState.get().getEvents())); - return reservedState; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java index 4539c7d9b000..08681ba0d792 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java @@ -103,8 +103,7 @@ public ReconnectLearner( this.statistics = Objects.requireNonNull(statistics); // Save some of the current state data for validation - this.stateValidationData = - new SignedStateValidationData(currentState.getPlatformState().getPlatformData(), addressBook); + this.stateValidationData = new SignedStateValidationData(currentState.getPlatformState(), addressBook); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidator.java index 48fbd854c285..b6c028dc9362 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidator.java @@ -58,7 +58,7 @@ public EmergencySignedStateValidator( * {@inheritDoc} * * If the {@code reservedSignedState} is matches the request round and hash exactly, this method updates the next epoch hash - * via {@link com.swirlds.platform.state.PlatformData#setNextEpochHash(Hash)}. Doing so does not modify the hash, + * via {@link com.swirlds.platform.state.PlatformState#setNextEpochHash(Hash)}. Doing so does not modify the hash, * but will trigger the epoch hash to update when the next round reaches consensus. * Note: the previous state is ignored by this validator. Emergency round, emergency state hash, and epoch hash * are used instead. @@ -106,7 +106,7 @@ private void verifyStateHashMatches(final SignedState signedState) { // FUTURE WORK: move this to the calling code (saved state loading and emergency reconnect) when // reconnect is refactored such that it no longer needs to be called by sync - signedState.getState().getPlatformState().getPlatformData().setNextEpochHash(emergencyRecoveryFile.hash()); + signedState.getState().getPlatformState().setNextEpochHash(emergencyRecoveryFile.hash()); signedState.markAsRecoveryState(); logger.info( @@ -146,8 +146,7 @@ private void verifyLaterRoundIsValid(final SignedState signedState, final Addres } private void checkEpochHash(final SignedState signedState) { - final Hash epochHash = - signedState.getState().getPlatformState().getPlatformData().getEpochHash(); + final Hash epochHash = signedState.getState().getPlatformState().getEpochHash(); if (!emergencyRecoveryFile.hash().equals(epochHash)) { logger.error( EXCEPTION.getMarker(), diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index 8d8ac78d270c..288cd70e9a49 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -47,6 +47,7 @@ import com.swirlds.platform.recovery.internal.EventStreamRoundIterator; import com.swirlds.platform.recovery.internal.RecoveredState; import com.swirlds.platform.recovery.internal.RecoveryPlatform; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; @@ -54,7 +55,6 @@ import com.swirlds.platform.state.signed.SignedStateFileWriter; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.ConsensusEvent; @@ -65,7 +65,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; -import java.util.*; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -288,14 +290,9 @@ public static RecoveredState reapplyTransactions( .getSwirldState() .init( platform, - initialState.get().getState().getSwirldDualState(), + initialState.get().getState().getPlatformState(), InitTrigger.EVENT_STREAM_RECOVERY, - initialState - .get() - .getState() - .getPlatformState() - .getPlatformData() - .getCreationSoftwareVersion()); + initialState.get().getState().getPlatformState().getCreationSoftwareVersion()); appMain.init(platform, platform.getSelfId()); @@ -342,10 +339,10 @@ public static RecoveredState reapplyTransactions( /** * Apply a single round and generate a new state. The previous state is released. * - * @param platformContext the current context - * @param previousState the previous round's signed state - * @param round the next round - * @param config the consensus configuration + * @param platformContext the current context + * @param previousState the previous round's signed state + * @param round the next round + * @param config the consensus configuration * @return the resulting signed state */ private static ReservedSignedState handleNextRound( @@ -359,36 +356,34 @@ private static ReservedSignedState handleNextRound( final State newState = previousState.get().getState().copy(); final EventImpl lastEvent = (EventImpl) getLastEvent(round); CryptographyHolder.get().digestSync(lastEvent.getBaseEvent().getHashedData()); - newState.getPlatformState() - .getPlatformData() - .setRound(round.getRoundNum()) - .setHashEventsCons(getHashEventsCons(previousState.get().getHashEventsCons(), round)) - .setConsensusTimestamp(currentRoundTimestamp) - .setSnapshot(SyntheticSnapshot.generateSyntheticSnapshot( - round.getRoundNum(), - lastEvent.getConsensusOrder(), - currentRoundTimestamp, - config, - lastEvent.getBaseEvent())) - .setCreationSoftwareVersion(previousState - .get() - .getState() - .getPlatformState() - .getPlatformData() - .getCreationSoftwareVersion()); + + final PlatformState platformState = newState.getPlatformState(); + + platformState.setRound(round.getRoundNum()); + platformState.setRunningEventHash(getHashEventsCons(previousState.get().getHashEventsCons(), round)); + platformState.setConsensusTimestamp(currentRoundTimestamp); + platformState.setSnapshot(SyntheticSnapshot.generateSyntheticSnapshot( + round.getRoundNum(), + lastEvent.getConsensusOrder(), + currentRoundTimestamp, + config, + lastEvent.getBaseEvent())); + platformState.setCreationSoftwareVersion( + previousState.get().getState().getPlatformState().getCreationSoftwareVersion()); applyTransactions( previousState.get().getSwirldState().cast(), newState.getSwirldState().cast(), - newState.getSwirldDualState(), + newState.getPlatformState(), round); final boolean isFreezeState = isFreezeState( previousState.get().getConsensusTimestamp(), currentRoundTimestamp, - newState.getPlatformDualState().getFreezeTime()); + newState.getPlatformState().getFreezeTime()); if (isFreezeState) { - newState.getPlatformDualState().setLastFrozenTimeToBeCurrentFreezeTime(); + newState.getPlatformState() + .setLastFrozenTime(newState.getPlatformState().getFreezeTime()); } final ReservedSignedState signedState = new SignedState( @@ -449,13 +444,13 @@ static ConsensusEvent getLastEvent(final Round round) { * * @param immutableState the immutable swirld state for the previous round * @param mutableState the swirld state for the current round - * @param dualState the dual state for the current round + * @param platformState the platform state for the current round * @param round the current round */ static void applyTransactions( final SwirldState immutableState, final SwirldState mutableState, - final SwirldDualState dualState, + final PlatformState platformState, final Round round) { mutableState.throwIfImmutable(); @@ -464,7 +459,7 @@ static void applyTransactions( immutableState.preHandle(event); } - mutableState.handleConsensusRound(round, dualState); + mutableState.handleConsensusRound(round, platformState); // FUTURE WORK: there are currently no system transactions that are capable of modifying // the state. If/when system transactions capable of modifying state are added, this workflow diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/DualStateImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/DualStateImpl.java index d7c1435a899e..e9915d9be87d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/DualStateImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/DualStateImpl.java @@ -16,30 +16,23 @@ package com.swirlds.platform.state; -import static com.swirlds.logging.legacy.LogMarker.FREEZE; - -import com.swirlds.base.utility.ToStringBuilder; import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.merkle.MerkleLeaf; import com.swirlds.common.merkle.impl.PartialMerkleLeaf; -import com.swirlds.logging.legacy.payload.SetFreezeTimePayload; -import com.swirlds.logging.legacy.payload.SetLastFrozenTimePayload; -import com.swirlds.platform.system.SwirldDualState; -import com.swirlds.platform.system.UptimeData; -import com.swirlds.platform.uptime.MutableUptimeData; import com.swirlds.platform.uptime.UptimeDataImpl; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.time.Instant; -import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * Contains any data that is either read or written by the platform and the application + * @deprecated can be removed after we don't need it for migration */ -public class DualStateImpl extends PartialMerkleLeaf implements PlatformDualState, SwirldDualState, MerkleLeaf { +@Deprecated(forRemoval = true) +public class DualStateImpl extends PartialMerkleLeaf implements MerkleLeaf { private static final Logger logger = LogManager.getLogger(DualStateImpl.class); public static final long CLASS_ID = 0x565e2e04ce3782b8L; @@ -96,59 +89,23 @@ public void deserialize(SerializableDataInputStream in, int version) throws IOEx } /** - * {@inheritDoc} - */ - @NonNull - @Override - public UptimeData getUptimeData() { - return uptimeData; - } - - /** - * {@inheritDoc} + * Get the node uptime data. */ @NonNull - @Override - public MutableUptimeData getMutableUptimeData() { + public UptimeDataImpl getUptimeData() { return uptimeData; } /** - * {@inheritDoc} - */ - @Override - public void setFreezeTime(Instant freezeTime) { - this.freezeTime = freezeTime; - logger.info(FREEZE.getMarker(), "setFreezeTime: {}", () -> freezeTime); - logger.info(FREEZE.getMarker(), () -> new SetFreezeTimePayload(freezeTime).toString()); - } - - /** - * {@inheritDoc} + * Get the freeze time. */ - @Override public Instant getFreezeTime() { return freezeTime; } - protected void setLastFrozenTime(Instant lastFrozenTime) { - this.lastFrozenTime = lastFrozenTime; - } - /** - * {@inheritDoc} - */ - @Override - public void setLastFrozenTimeToBeCurrentFreezeTime() { - this.lastFrozenTime = freezeTime; - logger.info(FREEZE.getMarker(), "setLastFrozenTimeToBeCurrentFreezeTime: {}", () -> lastFrozenTime); - logger.info(FREEZE.getMarker(), () -> new SetLastFrozenTimePayload(freezeTime).toString()); - } - - /** - * {@inheritDoc} + * Get the last frozen time. */ - @Override public Instant getLastFrozenTime() { return lastFrozenTime; } @@ -176,56 +133,4 @@ public int getVersion() { public DualStateImpl copy() { return new DualStateImpl(this); } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - final DualStateImpl dualState = (DualStateImpl) other; - return Objects.equals(freezeTime, dualState.freezeTime) - && Objects.equals(lastFrozenTime, dualState.lastFrozenTime); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return Objects.hash(freezeTime, lastFrozenTime); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return new ToStringBuilder(this) - .append("freezeTime", freezeTime) - .append("lastFrozenTime", lastFrozenTime) - .toString(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isInFreezePeriod(Instant consensusTime) { - // if freezeTime is not set, or consensusTime is before freezeTime, we are not in a freeze period - // if lastFrozenTime is equal to or after freezeTime, which means the nodes have been frozen once at/after the - // freezeTime, we are not in a freeze period - if (freezeTime == null || consensusTime.isBefore(freezeTime)) { - return false; - } - // Now we should check whether the nodes have been frozen at the freezeTime. - // when consensusTime is equal to or after freezeTime, - // and lastFrozenTime is before freezeTime, we are in a freeze period. - return lastFrozenTime == null || lastFrozenTime.isBefore(freezeTime); - } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java index 2c2d8360577d..4df610752b6a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java @@ -42,36 +42,17 @@ private static PlatformState buildGenesisPlatformState( final AddressBook addressBook, final SoftwareVersion appVersion) { final PlatformState platformState = new PlatformState(); - final PlatformData platformData = new PlatformData(); - platformState.setPlatformData(platformData); platformState.setAddressBook(addressBook.copy()); - platformData.setCreationSoftwareVersion(appVersion); - platformData.setRound(0); - platformData.setHashEventsCons(null); - platformData.setEpochHash(null); - platformData.setConsensusTimestamp(Instant.ofEpochSecond(0L)); + platformState.setCreationSoftwareVersion(appVersion); + platformState.setRound(0); + platformState.setRunningEventHash(null); + platformState.setEpochHash(null); + platformState.setConsensusTimestamp(Instant.ofEpochSecond(0L)); return platformState; } - /** - * Construct a genesis dual state. - * - * @param configuration configuration for the platform - * @return a genesis dual state - */ - private static DualStateImpl buildGenesisDualState(final BasicConfig configuration) { - final DualStateImpl dualState = new DualStateImpl(); - - final long genesisFreezeTime = configuration.genesisFreezeTime(); - if (genesisFreezeTime > 0) { - dualState.setFreezeTime(Instant.ofEpochSecond(genesisFreezeTime)); - } - - return dualState; - } - /** * Build and initialize a genesis state. * @@ -91,7 +72,11 @@ public static ReservedSignedState buildGenesisState( final State state = new State(); state.setPlatformState(buildGenesisPlatformState(addressBook, appVersion)); state.setSwirldState(swirldState); - state.setDualState(buildGenesisDualState(basicConfig)); + + final long genesisFreezeTime = basicConfig.genesisFreezeTime(); + if (genesisFreezeTime > 0) { + state.getPlatformState().setFreezeTime(Instant.ofEpochSecond(genesisFreezeTime)); + } final SignedState signedState = new SignedState(platformContext, state, "genesis state", false); return signedState.reserve("initial reservation on genesis state"); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/LegacyPlatformState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/LegacyPlatformState.java new file mode 100644 index 000000000000..19226f4b66b0 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/LegacyPlatformState.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016-2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.state; + +import com.swirlds.common.merkle.MerkleInternal; +import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; +import com.swirlds.platform.system.address.AddressBook; + +/** + * This subtree contains state data which is managed and used exclusively by the platform. + * + * @deprecated This class is deprecated and will be removed in a future release. Use {@link PlatformState} instead. + */ +@Deprecated(forRemoval = true) +public class LegacyPlatformState extends PartialNaryMerkleInternal implements MerkleInternal { + + public static final long CLASS_ID = 0x483ae5404ad0d0bfL; + + private static final class ClassVersion { + public static final int ORIGINAL = 1; + public static final int ADDED_PREVIOUS_ADDRESS_BOOK = 2; + } + + private static final class ChildIndices { + public static final int PLATFORM_DATA = 0; + public static final int ADDRESS_BOOK = 1; + public static final int PREVIOUS_ADDRESS_BOOK = 2; + } + + public LegacyPlatformState() {} + + /** + * Copy constructor. + * + * @param that the node to copy + */ + private LegacyPlatformState(final LegacyPlatformState that) { + super(that); + if (that.getPlatformData() != null) { + this.setPlatformData(that.getPlatformData().copy()); + } + if (that.getAddressBook() != null) { + this.setAddressBook(that.getAddressBook().copy()); + } + if (that.getPreviousAddressBook() != null) { + this.setPreviousAddressBook(that.getPreviousAddressBook().copy()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public long getClassId() { + return CLASS_ID; + } + + /** + * {@inheritDoc} + */ + @Override + public int getVersion() { + return ClassVersion.ADDED_PREVIOUS_ADDRESS_BOOK; + } + + /** + * {@inheritDoc} + */ + @Override + public LegacyPlatformState copy() { + return new LegacyPlatformState(this); + } + + /** + * Get the address book. + */ + public AddressBook getAddressBook() { + return getChild(ChildIndices.ADDRESS_BOOK); + } + + /** + * Set the address book. + * + * @param addressBook an address book + */ + public void setAddressBook(final AddressBook addressBook) { + setChild(ChildIndices.ADDRESS_BOOK, addressBook); + } + + /** + * Get the object containing miscellaneous round information. + * + * @return round data + */ + public PlatformData getPlatformData() { + return getChild(ChildIndices.PLATFORM_DATA); + } + + /** + * Set the object containing miscellaneous platform information. + * + * @param round round data + */ + public void setPlatformData(final PlatformData round) { + setChild(ChildIndices.PLATFORM_DATA, round); + } + + /** + * Get the previous address book. + */ + public AddressBook getPreviousAddressBook() { + return getChild(ChildIndices.PREVIOUS_ADDRESS_BOOK); + } + + /** + * Set the previous address book. + * + * @param addressBook an address book + */ + public void setPreviousAddressBook(final AddressBook addressBook) { + setChild(ChildIndices.PREVIOUS_ADDRESS_BOOK, addressBook); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformData.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformData.java index 80cfbb05cf6a..2967f06e613e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformData.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformData.java @@ -40,7 +40,10 @@ /** * A collection of miscellaneous platform data. + * + * @deprecated this class is no longer used and is kept for migration purposes only */ +@Deprecated(forRemoval = true) public class PlatformData extends PartialMerkleLeaf implements MerkleLeaf { private static final long CLASS_ID = 0x1f89d0c43a8c08bdL; @@ -54,20 +57,19 @@ private static final class ClassVersion { public static final int EPOCH_HASH = 2; public static final int ROUNDS_NON_ANCIENT = 3; /** - * - Events are no longer serialized, the field is kept for migration purposes - * - Mingen is no longer stored directly, its part of the snapshot - * - restart/reconnect now uses a snapshot - * - lastTransactionTimestamp is no longer stored directly, its part of the snapshot - * - numEventsCons is no longer stored directly, its part of the snapshot - * */ + * - Events are no longer serialized, the field is kept for migration purposes - Mingen is no longer stored + * directly, its part of the snapshot - restart/reconnect now uses a snapshot - lastTransactionTimestamp is no + * longer stored directly, its part of the snapshot - numEventsCons is no longer stored directly, its part of + * the snapshot + */ public static final int CONSENSUS_SNAPSHOT = 4; } /** * The round of this state. This state represents the handling of all transactions that have reached consensus in * all previous rounds. All transactions from this round will eventually be applied to this state. The first state - * (genesis state) has a round of 0 because the first round is defined as round 1, and the genesis state is - * before any transactions are handled. + * (genesis state) has a round of 0 because the first round is defined as round 1, and the genesis state is before + * any transactions are handled. */ private long round = GENESIS_ROUND; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformDualState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformDualState.java deleted file mode 100644 index 052aea749a83..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformDualState.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state; - -import com.swirlds.platform.FreezePeriodChecker; -import com.swirlds.platform.system.DualState; -import com.swirlds.platform.uptime.MutableUptimeData; -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Contains any data that is either read or written by the platform and the application, and contains methods available - * to the platform - */ -public interface PlatformDualState extends DualState, FreezePeriodChecker { - - /** - * Sets the lastFrozenTime to be current freezeTime - */ - void setLastFrozenTimeToBeCurrentFreezeTime(); - - /** - * Get the node uptime data. - */ - @NonNull - MutableUptimeData getMutableUptimeData(); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java index 86292ab7bd82..d504eb6d4734 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformState.java @@ -16,46 +16,147 @@ package com.swirlds.platform.state; -import com.swirlds.common.merkle.MerkleInternal; -import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; +import com.swirlds.common.crypto.Hash; +import com.swirlds.common.io.streams.SerializableDataInputStream; +import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.common.merkle.MerkleLeaf; +import com.swirlds.common.merkle.impl.PartialMerkleLeaf; +import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.consensus.RoundCalculationUtils; +import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.uptime.UptimeDataImpl; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; /** - * This subtree contains state data which is managed and used exclusively by the platform. + * State managed and used by the platform. */ -public class PlatformState extends PartialNaryMerkleInternal implements MerkleInternal { +public class PlatformState extends PartialMerkleLeaf implements MerkleLeaf { - public static final long CLASS_ID = 0x483ae5404ad0d0bfL; + public static final long CLASS_ID = 0x52cef730a11cb6dfL; + + /** + * The round of the genesis state. + */ + public static final long GENESIS_ROUND = 0; private static final class ClassVersion { public static final int ORIGINAL = 1; - public static final int ADDED_PREVIOUS_ADDRESS_BOOK = 2; } - private static final class ChildIndices { - public static final int PLATFORM_DATA = 0; - public static final int ADDRESS_BOOK = 1; - public static final int PREVIOUS_ADDRESS_BOOK = 2; - } + /** + * The address book for this round. + */ + private AddressBook addressBook; + + /** + * The previous address book. A temporary workaround until dynamic address books are supported. + */ + private AddressBook previousAddressBook; + + /** + * The round of this state. This state represents the handling of all transactions that have reached consensus in + * all previous rounds. All transactions from this round will eventually be applied to this state. The first state + * (genesis state) has a round of 0 because the first round is defined as round 1, and the genesis state is before + * any transactions are handled. + */ + private long round = GENESIS_ROUND; + + /** + * The running hash of the hashes of all events have reached consensus up through the round that this SignedState + * represents. + */ + private Hash runningEventHash; + + /** + * the consensus timestamp for this signed state + */ + private Instant consensusTimestamp; + + /** + * The version of the application software that was responsible for creating this state. + */ + private SoftwareVersion creationSoftwareVersion; + + /** + * The epoch hash of this state. Updated every time emergency recovery is performed. + */ + private Hash epochHash; + + /** + * The next epoch hash, used to update the epoch hash at the next round boundary. This field is not part of the hash + * and is not serialized. + */ + private Hash nextEpochHash; + + /** + * The number of non-ancient rounds. + */ + private int roundsNonAncient; + + /** + * A snapshot of the consensus state at the end of the round, used for restart/reconnect + */ + private ConsensusSnapshot snapshot; + + /** + * the time when the freeze starts + */ + private Instant freezeTime; + + /** + * the last time when a freeze was performed + */ + private Instant lastFrozenTime; + + /** + * Data on node uptime. + */ + private UptimeDataImpl uptimeData = new UptimeDataImpl(); public PlatformState() {} /** * Copy constructor. * - * @param that - * the node to copy + * @param that the object to copy */ private PlatformState(final PlatformState that) { super(that); - if (that.getPlatformData() != null) { - this.setPlatformData(that.getPlatformData().copy()); - } - if (that.getAddressBook() != null) { - this.setAddressBook(that.getAddressBook().copy()); - } - if (that.getPreviousAddressBook() != null) { - this.setPreviousAddressBook(that.getPreviousAddressBook().copy()); + this.addressBook = that.addressBook.copy(); + this.previousAddressBook = that.previousAddressBook == null ? null : that.previousAddressBook.copy(); + this.round = that.round; + this.runningEventHash = that.runningEventHash; + this.consensusTimestamp = that.consensusTimestamp; + this.creationSoftwareVersion = that.creationSoftwareVersion; + this.epochHash = that.epochHash; + this.nextEpochHash = that.nextEpochHash; + this.roundsNonAncient = that.roundsNonAncient; + this.snapshot = that.snapshot; + this.freezeTime = that.freezeTime; + this.lastFrozenTime = that.lastFrozenTime; + this.uptimeData = that.uptimeData.copy(); + } + + /** + * Update the epoch hash if the next epoch hash is non-null and different from the current epoch hash. + */ + public void updateEpochHash() { + throwIfImmutable(); + if (nextEpochHash != null && !nextEpochHash.equals(epochHash)) { + // This is the first round after an emergency recovery round + // Set the epoch hash to the next value + epochHash = nextEpochHash; + + // set this to null so the value is consistent with a + // state loaded from disk or received via reconnect + nextEpochHash = null; } } @@ -67,12 +168,50 @@ public long getClassId() { return CLASS_ID; } + /** + * {@inheritDoc} + */ + @Override + public void serialize(final SerializableDataOutputStream out) throws IOException { + out.writeSerializable(addressBook, false); + out.writeSerializable(previousAddressBook, true); + out.writeLong(round); + out.writeSerializable(runningEventHash, false); + out.writeInstant(consensusTimestamp); + out.writeSerializable(creationSoftwareVersion, true); + out.writeSerializable(epochHash, false); + out.writeInt(roundsNonAncient); + out.writeSerializable(snapshot, false); + out.writeInstant(freezeTime); + out.writeInstant(lastFrozenTime); + out.writeSerializable(uptimeData, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void deserialize(final SerializableDataInputStream in, final int version) throws IOException { + addressBook = in.readSerializable(false, AddressBook::new); + previousAddressBook = in.readSerializable(true, AddressBook::new); + round = in.readLong(); + runningEventHash = in.readSerializable(false, Hash::new); + consensusTimestamp = in.readInstant(); + creationSoftwareVersion = in.readSerializable(); + epochHash = in.readSerializable(false, Hash::new); + roundsNonAncient = in.readInt(); + snapshot = in.readSerializable(false, ConsensusSnapshot::new); + freezeTime = in.readInstant(); + lastFrozenTime = in.readInstant(); + uptimeData = in.readSerializable(false, UptimeDataImpl::new); + } + /** * {@inheritDoc} */ @Override public int getVersion() { - return ClassVersion.ADDED_PREVIOUS_ADDRESS_BOOK; + return ClassVersion.ORIGINAL; } /** @@ -83,56 +222,303 @@ public PlatformState copy() { return new PlatformState(this); } + /** + * Get the software version of the application that created this state. + * + * @return the creation version + */ + @Nullable + public SoftwareVersion getCreationSoftwareVersion() { + return creationSoftwareVersion; + } + + /** + * Set the software version of the application that created this state. + * + * @param creationVersion the creation version + */ + public void setCreationSoftwareVersion(@NonNull final SoftwareVersion creationVersion) { + this.creationSoftwareVersion = Objects.requireNonNull(creationVersion); + } + /** * Get the address book. */ + @Nullable public AddressBook getAddressBook() { - return getChild(ChildIndices.ADDRESS_BOOK); + return addressBook; } /** * Set the address book. * - * @param addressBook - * an address book + * @param addressBook an address book + */ + public void setAddressBook(@NonNull final AddressBook addressBook) { + this.addressBook = Objects.requireNonNull(addressBook); + } + + /** + * Get the previous address book. + */ + @Nullable + public AddressBook getPreviousAddressBook() { + return previousAddressBook; + } + + /** + * Set the previous address book. + * + * @param addressBook an address book */ - public void setAddressBook(final AddressBook addressBook) { - setChild(ChildIndices.ADDRESS_BOOK, addressBook); + public void setPreviousAddressBook(@Nullable final AddressBook addressBook) { + this.previousAddressBook = addressBook; } /** - * Get the object containing miscellaneous round information. + * Get the round when this state was generated. * - * @return round data + * @return a round number */ - public PlatformData getPlatformData() { - return getChild(ChildIndices.PLATFORM_DATA); + public long getRound() { + return round; } /** - * Set the object containing miscellaneous platform information. + * Set the round when this state was generated. * - * @param round - * round data + * @param round a round number */ - public void setPlatformData(final PlatformData round) { - setChild(ChildIndices.PLATFORM_DATA, round); + public void setRound(final long round) { + this.round = round; } /** - * Get the previous address book. + * Get the running hash of all events that have been applied to this state since the beginning of time. + * + * @return a running hash of events */ - public AddressBook getPreviousAddressBook() { - return getChild(ChildIndices.PREVIOUS_ADDRESS_BOOK); + @Nullable + public Hash getRunningEventHash() { + return runningEventHash; } /** - * Set the previous address book. + * Set the running hash of all events that have been applied to this state since the beginning of time. + * + * @param runningEventHash a running hash of events + */ + public void setRunningEventHash(@Nullable final Hash runningEventHash) { + this.runningEventHash = runningEventHash; + } + + /** + * Get the consensus timestamp for this state, defined as the timestamp of the first transaction that was applied in + * the round that created the state. + * + * @return a consensus timestamp + */ + @Nullable + public Instant getConsensusTimestamp() { + return consensusTimestamp; + } + + /** + * Set the consensus timestamp for this state, defined as the timestamp of the first transaction that was applied in + * the round that created the state. + * + * @param consensusTimestamp a consensus timestamp + */ + public void setConsensusTimestamp(@NonNull final Instant consensusTimestamp) { + this.consensusTimestamp = Objects.requireNonNull(consensusTimestamp); + } + + /** + * Get the minimum event generation for each node within this state. + * + * @return minimum generation info list, or null if this is a genesis state + */ + @Nullable + public List getMinGenInfo() { + return snapshot == null ? null : snapshot.minGens(); + } + + /** + * The minimum generation of famous witnesses for the round specified. This method only looks at non-ancient rounds + * contained within this state. + * + * @param round the round whose minimum generation will be returned + * @return the minimum generation for the round specified + * @throws NoSuchElementException if the generation information for this round is not contained withing this state + */ + public long getMinGen(final long round) { + final List minGenInfo = getMinGenInfo(); + if (minGenInfo == null) { + throw new IllegalStateException("No MinGen info found in state for round " + round); + } + + for (final MinGenInfo info : minGenInfo) { + if (info.round() == round) { + return info.minimumGeneration(); + } + } + throw new NoSuchElementException("No minimum generation found for round: " + round); + } + + /** + * Return the round generation of the oldest round in this state + * + * @return the generation of the oldest round + */ + public long getMinRoundGeneration() { + + final List minGenInfo = getMinGenInfo(); + if (minGenInfo == null) { + throw new IllegalStateException("No MinGen info found in state for round " + round); + } + + return getMinGenInfo().stream() + .findFirst() + .orElseThrow(() -> new IllegalStateException("No MinGen info found in state")) + .minimumGeneration(); + } + + /** + * Sets the epoch hash of this state. + * + * @param epochHash the epoch hash of this state + */ + public void setEpochHash(@Nullable final Hash epochHash) { + this.epochHash = epochHash; + } + + /** + * Gets the epoch hash of this state. + * + * @return the epoch hash of this state + */ + @Nullable + public Hash getEpochHash() { + return epochHash; + } + + /** + * Sets the next epoch hash of this state. + * + * @param nextEpochHash the next epoch hash of this state + */ + public void setNextEpochHash(@Nullable final Hash nextEpochHash) { + this.nextEpochHash = nextEpochHash; + } + + /** + * Gets the next epoch hash of this state. + * + * @return the next epoch hash of this state + */ + @Nullable + public Hash getNextEpochHash() { + return nextEpochHash; + } + + /** + * Sets the number of non-ancient rounds. + * + * @param roundsNonAncient the number of non-ancient rounds + */ + public void setRoundsNonAncient(final int roundsNonAncient) { + this.roundsNonAncient = roundsNonAncient; + } + + /** + * Gets the number of non-ancient rounds. + * + * @return the number of non-ancient rounds + */ + public int getRoundsNonAncient() { + return roundsNonAncient; + } + + /** + * Gets the minimum generation of non-ancient events. + * + * @return the minimum generation of non-ancient events + */ + public long getMinimumGenerationNonAncient() { + return RoundCalculationUtils.getMinGenNonAncient(roundsNonAncient, round, this::getMinGen); + } + + /** + * @return the consensus snapshot for this round + */ + @Nullable + public ConsensusSnapshot getSnapshot() { + return snapshot; + } + + /** + * @param snapshot the consensus snapshot for this round + */ + public void setSnapshot(@NonNull final ConsensusSnapshot snapshot) { + this.snapshot = Objects.requireNonNull(snapshot); + } + + /** + * Gets the time when the next freeze is scheduled to start. If null then there is no freeze scheduled. + * + * @return the time when the freeze starts + */ + @Nullable + public Instant getFreezeTime() { + return freezeTime; + } + + /** + * Sets the instant after which the platform will enter FREEZING status. When consensus timestamp of a signed state + * is after this instant, the platform will stop creating events and accepting transactions. This is used to safely + * shut down the platform for maintenance. + * + * @param freezeTime an Instant in UTC + */ + public void setFreezeTime(@Nullable final Instant freezeTime) { + this.freezeTime = freezeTime; + } + + /** + * Gets the last freezeTime based on which the nodes were frozen. If null then there has never been a freeze. + * + * @return the last freezeTime based on which the nodes were frozen + */ + @Nullable + public Instant getLastFrozenTime() { + return lastFrozenTime; + } + + /** + * Sets the last freezeTime based on which the nodes were frozen. + * + * @param lastFrozenTime the last freezeTime based on which the nodes were frozen + */ + public void setLastFrozenTime(@Nullable final Instant lastFrozenTime) { + this.lastFrozenTime = lastFrozenTime; + } + + /** + * Gets the uptime data. + * + * @return the uptime data + */ + @Nullable + public UptimeDataImpl getUptimeData() { + return uptimeData; + } + + /** + * Sets the uptime data. * - * @param addressBook - * an address book + * @param uptimeData the uptime data */ - public void setPreviousAddressBook(final AddressBook addressBook) { - setChild(ChildIndices.PREVIOUS_ADDRESS_BOOK, addressBook); + public void setUptimeData(@NonNull final UptimeDataImpl uptimeData) { + this.uptimeData = Objects.requireNonNull(uptimeData); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java index 9dab8eae7b1f..5d0cd187b477 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java @@ -16,30 +16,34 @@ package com.swirlds.platform.state; +import static com.swirlds.logging.legacy.LogMarker.STARTUP; + import com.swirlds.base.utility.ToStringBuilder; import com.swirlds.common.crypto.Hash; import com.swirlds.common.formatting.TextTable; import com.swirlds.common.merkle.MerkleInternal; -import com.swirlds.common.merkle.exceptions.IllegalChildIndexException; +import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; import com.swirlds.common.merkle.utility.MerkleTreeVisualizer; import com.swirlds.common.utility.RuntimeObjectRecord; import com.swirlds.common.utility.RuntimeObjectRegistry; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** - * The root of the merkle tree holding the state of the Swirlds ledger. Contains three children: the state used by the - * application; the state used by the platform; and the state used by both application and platform. + * The root of the merkle tree holding the state of the Swirlds ledger. Contains two children: the state used by the + * application and the state used by the platform. */ public class State extends PartialNaryMerkleInternal implements MerkleInternal { + private static final Logger logger = LogManager.getLogger(State.class); + private static final long CLASS_ID = 0x2971b4ba7dd84402L; private static class ClassVersion { @@ -48,6 +52,7 @@ private static class ClassVersion { public static final int EVENT_REFACTOR = 3; public static final int MIGRATE_TO_SERIALIZABLE = 4; public static final int ADD_DUAL_STATE = 5; + public static final int REMOVE_DUAL_STATE = 6; } private static class ChildIndices { @@ -61,7 +66,7 @@ private static class ChildIndices { */ public static final int PLATFORM_STATE = 1; /** - * The state either read or written by the platform and the application + * Not used after migration. */ public static final int DUAL_STATE = 2; } @@ -86,26 +91,48 @@ private State(final State that) { if (that.getPlatformState() != null) { this.setPlatformState(that.getPlatformState().copy()); } - if (that.getDualState() != null) { - this.setDualState(that.getDualState().copy()); - } } /** * {@inheritDoc} */ @Override - public boolean childHasExpectedType(final int index, final long childClassId) { - switch (index) { - case ChildIndices.SWIRLD_STATE: - return true; - case ChildIndices.PLATFORM_STATE: - return childClassId == PlatformState.CLASS_ID; - case ChildIndices.DUAL_STATE: - return childClassId == DualStateImpl.CLASS_ID; - default: - throw new IllegalChildIndexException(getMinimumChildCount(), getMaximumChildCount(), index); + public MerkleNode migrate(final int version) { + if (version < ClassVersion.REMOVE_DUAL_STATE) { + logger.info( + STARTUP.getMarker(), + "Migrating legacy platform state to new platform state at version (State version {} -> {}).", + version, + getVersion()); + + final State newState = new State(); + + final PlatformState newPlatformState = new PlatformState(); + + final LegacyPlatformState platformState = getChild(ChildIndices.PLATFORM_STATE); + final PlatformData platformData = platformState.getPlatformData(); + final DualStateImpl dualState = getChild(ChildIndices.DUAL_STATE); + + newPlatformState.setAddressBook(platformState.getAddressBook()); + newPlatformState.setPreviousAddressBook(platformState.getPreviousAddressBook()); + newPlatformState.setRound(platformData.getRound()); + newPlatformState.setRunningEventHash(platformData.getHashEventsCons()); + newPlatformState.setConsensusTimestamp(platformData.getConsensusTimestamp()); + newPlatformState.setCreationSoftwareVersion(platformData.getCreationSoftwareVersion()); + newPlatformState.setEpochHash(platformData.getEpochHash()); + newPlatformState.setNextEpochHash(platformData.getNextEpochHash()); + newPlatformState.setRoundsNonAncient(platformData.getRoundsNonAncient()); + newPlatformState.setSnapshot(platformData.getSnapshot()); + newPlatformState.setFreezeTime(dualState.getFreezeTime()); + newPlatformState.setLastFrozenTime(dualState.getLastFrozenTime()); + newPlatformState.setUptimeData(dualState.getUptimeData()); + + newState.setPlatformState(newPlatformState); + newState.setSwirldState(getSwirldState()); + + return newState; } + return this; } /** @@ -113,7 +140,7 @@ public boolean childHasExpectedType(final int index, final long childClassId) { */ @Override public int getMinimumSupportedVersion() { - return ClassVersion.ADD_MIN_GEN; + return ClassVersion.ADD_DUAL_STATE; } /** @@ -152,42 +179,6 @@ public void setPlatformState(final PlatformState platformState) { setChild(ChildIndices.PLATFORM_STATE, platformState); } - /** - * Get the dualState. - * - * @return the dualState - */ - private DualStateImpl getDualState() { - return getChild(ChildIndices.DUAL_STATE); - } - - /** - * Get current dualState object which can be read/written by the platform - * - * @return current dualState object which can be read/written by the platform - */ - public PlatformDualState getPlatformDualState() { - return getDualState(); - } - - /** - * Get current dualState object which can be read/written by the application - * - * @return current dualState object which can be read/written by the application - */ - public SwirldDualState getSwirldDualState() { - return getDualState(); - } - - /** - * Set the dual state. - * - * @param dualState the dual state - */ - public void setDualState(final DualStateImpl dualState) { - setChild(ChildIndices.DUAL_STATE, dualState); - } - /** * {@inheritDoc} */ @@ -201,7 +192,7 @@ public long getClassId() { */ @Override public int getVersion() { - return ClassVersion.ADD_DUAL_STATE; + return ClassVersion.REMOVE_DUAL_STATE; } /** @@ -245,8 +236,7 @@ public boolean equals(final Object other) { } final State state = (State) other; return Objects.equals(getPlatformState(), state.getPlatformState()) - && Objects.equals(getSwirldState(), state.getSwirldState()) - && Objects.equals(getPlatformDualState(), state.getPlatformDualState()); + && Objects.equals(getSwirldState(), state.getSwirldState()); } /** @@ -254,7 +244,7 @@ public boolean equals(final Object other) { */ @Override public int hashCode() { - return Objects.hash(getPlatformState(), getSwirldState(), getPlatformDualState()); + return Objects.hash(getPlatformState(), getSwirldState()); } /** @@ -263,27 +253,26 @@ public int hashCode() { * @param hashDepth the depth of the tree to visit and print */ public String getInfoString(final int hashDepth) { - final PlatformData data = getPlatformState().getPlatformData(); - final Hash epochHash = data.getNextEpochHash(); - final Hash hashEventsCons = data.getHashEventsCons(); - final List minGenInfo = data.getMinGenInfo(); - final ConsensusSnapshot snapshot = data.getSnapshot(); + final PlatformState platformState = getPlatformState(); + final Hash epochHash = platformState.getNextEpochHash(); + final Hash hashEventsCons = platformState.getRunningEventHash(); + final List minGenInfo = platformState.getMinGenInfo(); + final ConsensusSnapshot snapshot = platformState.getSnapshot(); final StringBuilder sb = new StringBuilder(); new TextTable() .setBordersEnabled(false) - .addRow("Round:", data.getRound()) - .addRow("Timestamp:", data.getConsensusTimestamp()) + .addRow("Round:", platformState.getRound()) + .addRow("Timestamp:", platformState.getConsensusTimestamp()) .addRow("Next consensus number:", snapshot == null ? "null" : snapshot.nextConsensusNumber()) .addRow("Running event hash:", hashEventsCons) .addRow("Running event mnemonic:", hashEventsCons == null ? "null" : hashEventsCons.toMnemonic()) - .addRow("Rounds non-ancient:", data.getRoundsNonAncient()) - .addRow("Creation version:", data.getCreationSoftwareVersion()) + .addRow("Rounds non-ancient:", platformState.getRoundsNonAncient()) + .addRow("Creation version:", platformState.getCreationSoftwareVersion()) .addRow("Epoch mnemonic:", epochHash == null ? "null" : epochHash.toMnemonic()) .addRow("Epoch hash:", epochHash) .addRow("Min gen hash code:", minGenInfo == null ? "null" : minGenInfo.hashCode()) - .addRow("Events hash code:", Arrays.hashCode(data.getEvents())) .addRow("Root hash:", getHash()) .render(sb); @@ -300,7 +289,6 @@ public String toString() { return new ToStringBuilder(this) .append("platformState", getPlatformState()) .append("swirldState", getSwirldState()) - .append("dualState", getPlatformDualState()) .toString(); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index c061903902ae..6f8260f40440 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -32,7 +32,6 @@ import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; @@ -174,7 +173,7 @@ public void prehandleSystemTransactions(final EventImpl event) { /** * Handles the events in a consensus round. Implementations are responsible for invoking - * {@link SwirldState#handleConsensusRound(Round, SwirldDualState)}. + * {@link SwirldState#handleConsensusRound(Round, PlatformState)}. * * @param round the round to handle */ @@ -183,7 +182,7 @@ public void handleConsensusRound(final ConsensusRound round) { uptimeTracker.handleRound( round, - state.getPlatformDualState().getMutableUptimeData(), + state.getPlatformState().getUptimeData(), state.getPlatformState().getAddressBook()); transactionHandler.handleRound(round, state); consensusSystemTransactionManager.handleRound(state, round); @@ -207,7 +206,9 @@ public State getConsensusState() { */ public void savedStateInFreezePeriod() { // set current DualState's lastFrozenTime to be current freezeTime - stateRef.get().getPlatformDualState().setLastFrozenTimeToBeCurrentFreezeTime(); + stateRef.get() + .getPlatformState() + .setLastFrozenTime(stateRef.get().getPlatformState().getFreezeTime()); } /** @@ -272,7 +273,7 @@ private void setLatestImmutableState(final State immutableState) { private void updateEpoch() { final PlatformState platformState = stateRef.get().getPlatformState(); if (platformState != null) { - platformState.getPlatformData().updateEpochHash(); + platformState.updateEpochHash(); } } @@ -281,7 +282,9 @@ private void updateEpoch() { */ @Override public boolean isInFreezePeriod(final Instant timestamp) { - return SwirldStateManagerUtils.isInFreezePeriod(timestamp, getConsensusState()); + final PlatformState platformState = getConsensusState().getPlatformState(); + return SwirldStateManagerUtils.isInFreezePeriod( + timestamp, platformState.getFreezeTime(), platformState.getLastFrozenTime()); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java index fa863d6ec807..bc81b91db14f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java @@ -21,6 +21,7 @@ import com.swirlds.platform.metrics.SwirldStateMetrics; import com.swirlds.platform.system.SoftwareVersion; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; import java.util.Objects; @@ -51,7 +52,7 @@ public static State fastCopy( // Create a fast copy final State copy = state.copy(); - state.getPlatformState().getPlatformData().setCreationSoftwareVersion(softwareVersion); + state.getPlatformState().setCreationSoftwareVersion(softwareVersion); // Increment the reference count because this reference becomes the new value copy.reserve(); @@ -64,14 +65,27 @@ public static State fastCopy( } /** - * Determines if a {@code timestamp} is in a freeze period according to the provided state. + * Determines if a {@code timestamp} is in a freeze period according to the provided timestamps. * - * @param timestamp the timestamp to check - * @param consensusState the state that contains the freeze periods + * @param consensusTime the consensus time to check + * @param freezeTime the freeze time + * @param lastFrozenTime the last frozen time * @return true is the {@code timestamp} is in a freeze period */ - public static boolean isInFreezePeriod(final Instant timestamp, final State consensusState) { - final PlatformDualState dualState = consensusState.getPlatformDualState(); - return dualState != null && dualState.isInFreezePeriod(timestamp); + public static boolean isInFreezePeriod( + @NonNull final Instant consensusTime, + @Nullable final Instant freezeTime, + @Nullable final Instant lastFrozenTime) { + + // if freezeTime is not set, or consensusTime is before freezeTime, we are not in a freeze period + // if lastFrozenTime is equal to or after freezeTime, which means the nodes have been frozen once at/after the + // freezeTime, we are not in a freeze period + if (freezeTime == null || consensusTime.isBefore(freezeTime)) { + return false; + } + // Now we should check whether the nodes have been frozen at the freezeTime. + // when consensusTime is equal to or after freezeTime, + // and lastFrozenTime is before freezeTime, we are in a freeze period. + return lastFrozenTime == null || lastFrozenTime.isBefore(freezeTime); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java index b914d2c6a1ca..6ea1cfd3e6e7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java @@ -81,7 +81,7 @@ public void handleRound(final ConsensusRound round, final State state) { final Instant timeOfHandle = Instant.now(); final long startTime = System.nanoTime(); - state.getSwirldState().handleConsensusRound(round, state.getSwirldDualState()); + state.getSwirldState().handleConsensusRound(round, state.getPlatformState()); final double secondsElapsed = (System.nanoTime() - startTime) * NANOSECONDS_TO_SECONDS; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java index 1583eab61b6a..3d443cabb126 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SavedStateMetadata.java @@ -39,7 +39,6 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.formatting.TextTable; import com.swirlds.common.platform.NodeId; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -175,23 +174,22 @@ public static SavedStateMetadata create( Objects.requireNonNull(now, "now must not be null"); final PlatformState platformState = signedState.getState().getPlatformState(); - final PlatformData platformData = platformState.getPlatformData(); final List signingNodes = signedState.getSigSet().getSigningNodes(); Collections.sort(signingNodes); - final Hash epochHash = platformData.getEpochHash(); + final Hash epochHash = platformState.getEpochHash(); return new SavedStateMetadata( signedState.getRound(), signedState.getState().getHash(), signedState.getState().getHash().toMnemonic(), - platformData.getSnapshot().nextConsensusNumber(), + platformState.getSnapshot().nextConsensusNumber(), signedState.getConsensusTimestamp(), - platformData.getHashEventsCons(), - platformData.getHashEventsCons().toMnemonic(), - platformData.getMinimumGenerationNonAncient(), - convertToString(platformData.getCreationSoftwareVersion()), + platformState.getRunningEventHash(), + platformState.getRunningEventHash().toMnemonic(), + platformState.getMinimumGenerationNonAncient(), + convertToString(platformState.getCreationSoftwareVersion()), now, selfId, signingNodes, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java index 8504299d71c8..978b2a5f2b83 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java @@ -35,7 +35,6 @@ import com.swirlds.common.utility.RuntimeObjectRecord; import com.swirlds.common.utility.RuntimeObjectRegistry; import com.swirlds.common.utility.Threshold; -import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.state.MinGenInfo; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction; @@ -216,7 +215,7 @@ public synchronized void setGarbageCollector( */ @Override public long getRound() { - return state.getPlatformState().getPlatformData().getRound(); + return state.getPlatformState().getRound(); } /** @@ -225,7 +224,7 @@ public long getRound() { * @return true if this is the genesis state */ public boolean isGenesisState() { - return state.getPlatformState().getPlatformData().getRound() == GENESIS_ROUND; + return state.getPlatformState().getRound() == GENESIS_ROUND; } /** @@ -438,7 +437,7 @@ public String toString() { * @return the consensus timestamp for this signed state. */ public @NonNull Instant getConsensusTimestamp() { - return state.getPlatformState().getPlatformData().getConsensusTimestamp(); + return state.getPlatformState().getConsensusTimestamp(); } /** @@ -457,22 +456,13 @@ public String toString() { return state.getSwirldState(); } - /** - * Get events in the platformState. - * - * @return events in the platformState - */ - public @Nullable EventImpl[] getEvents() { - return state.getPlatformState().getPlatformData().getEvents(); - } - /** * Get the hash of the consensus events in this state. * * @return the hash of the consensus events in this state */ public @NonNull Hash getHashEventsCons() { - return state.getPlatformState().getPlatformData().getHashEventsCons(); + return state.getPlatformState().getRunningEventHash(); } /** @@ -481,7 +471,7 @@ public String toString() { * @return the minimum generation of famous witnesses per round */ public @NonNull List getMinGenInfo() { - return state.getPlatformState().getPlatformData().getMinGenInfo(); + return state.getPlatformState().getMinGenInfo(); } /** @@ -493,7 +483,7 @@ public String toString() { * @throws NoSuchElementException if the generation information for this round is not contained withing this state */ public long getMinGen(final long round) { - return getState().getPlatformState().getPlatformData().getMinGen(round); + return getState().getPlatformState().getMinGen(round); } /** @@ -502,7 +492,7 @@ public long getMinGen(final long round) { * @return the generation of the oldest round */ public long getMinRoundGeneration() { - return getState().getPlatformState().getPlatformData().getMinRoundGeneration(); + return getState().getPlatformState().getMinRoundGeneration(); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java index 001ebefb06f0..88f8c845e149 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateFileWriter.java @@ -163,7 +163,7 @@ public static void writeSignedStateFilesToDirectory( platformContext, selfId, directory, - signedState.getState().getPlatformState().getPlatformData().getMinimumGenerationNonAncient(), + signedState.getState().getPlatformState().getMinimumGenerationNonAncient(), signedState.getRound()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java index cbb87e51c9c6..7894bf817622 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateManager.java @@ -204,10 +204,8 @@ public synchronized void addState(@NonNull final SignedState signedState) { } if (firstStateTimestamp.get() == null) { - firstStateTimestamp.set( - signedState.getState().getPlatformState().getPlatformData().getConsensusTimestamp()); - firstStateRound.set( - signedState.getState().getPlatformState().getPlatformData().getRound()); + firstStateTimestamp.set(signedState.getState().getPlatformState().getConsensusTimestamp()); + firstStateRound.set(signedState.getState().getPlatformState().getRound()); } // Double check that the signatures on this state are valid. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateValidationData.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateValidationData.java index 08e668b3045f..6fa44b5fd7b9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateValidationData.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateValidationData.java @@ -17,7 +17,7 @@ package com.swirlds.platform.state.signed; import com.swirlds.common.crypto.Hash; -import com.swirlds.platform.state.PlatformData; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -44,12 +44,12 @@ public record SignedStateValidationData( @NonNull Hash consensusEventsRunningHash, @Nullable Hash epochHash) { - public SignedStateValidationData(@NonNull final PlatformData that, @Nullable final AddressBook addressBook) { + public SignedStateValidationData(@NonNull final PlatformState that, @Nullable final AddressBook addressBook) { this( that.getRound(), that.getConsensusTimestamp(), addressBook == null ? null : addressBook.getHash(), - that.getHashEventsCons(), + that.getRunningEventHash(), that.getEpochHash()); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java index f9bd7fd6020c..cd50c552ef15 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java @@ -409,9 +409,8 @@ private static ReservedSignedState processRecoveryState( final Hash targetEpoch = emergencyRecoveryManager.getEmergencyRecoveryFile().hash(); final Hash stateHash = state == null ? null : state.get().getState().getHash(); - final Hash stateEpoch = state == null - ? null - : state.get().getState().getPlatformState().getPlatformData().getEpochHash(); + final Hash stateEpoch = + state == null ? null : state.get().getState().getPlatformState().getEpochHash(); final boolean inEpoch = isInEpoch(targetEpoch, stateHash, stateEpoch); @@ -433,7 +432,6 @@ private static ReservedSignedState processRecoveryState( state.get() .getState() .getPlatformState() - .getPlatformData() .setNextEpochHash( emergencyRecoveryManager.getEmergencyRecoveryFile().hash()); @@ -520,8 +518,7 @@ private static ReservedSignedState loadStateFile( final Hash oldHash = deserializedSignedState.originalHash(); final Hash newHash = rehashTree(state); - final SoftwareVersion loadedVersion = - state.getPlatformState().getPlatformData().getCreationSoftwareVersion(); + final SoftwareVersion loadedVersion = state.getPlatformState().getCreationSoftwareVersion(); if (oldHash.equals(newHash)) { logger.info(STARTUP.getMarker(), "Loaded state's hash is the same as when it was saved."); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/DualState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/DualState.java deleted file mode 100644 index b6153b4a2a5f..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/DualState.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2016-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.system; - -import java.time.Instant; - -/** - * Contains any data that is either read or written by the platform and the application, - * and contains methods for reading or writing those data. - * The methods are available to both the platform and the application. - */ -public interface DualState { - /** - * Sets the instant after which the platform will enter FREEZING status. - * When consensus timestamp of a signed state is after this instant, - * the platform will stop creating events and accepting transactions. - * This is used to safely shut down the platform for maintenance. - * - * @param freezeTime - * an Instant in UTC - */ - void setFreezeTime(Instant freezeTime); - - /** - * Gets the time when the freeze starts - * - * @return the time when the freeze starts - */ - Instant getFreezeTime(); - - /** - * Gets the last freezeTime based on which the nodes were frozen - * - * @return the last freezeTime based on which the nodes were frozen - */ - Instant getLastFrozenTime(); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldDualState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldDualState.java deleted file mode 100644 index 6ae5afe0799b..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldDualState.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2016-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.system; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * Contains any data that is either read or written by the platform and the application, - * and contains methods available to the application - */ -public interface SwirldDualState extends DualState { - - /** - * Get the node uptime data. - */ - @NonNull - UptimeData getUptimeData(); -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java index bf4c6e4dc7a9..290c9e83f288 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/SwirldState.java @@ -17,13 +17,12 @@ package com.swirlds.platform.system; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.TransactionSignature; import com.swirlds.common.merkle.MerkleNode; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.transaction.Transaction; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; import java.util.Objects; /** @@ -45,7 +44,7 @@ public interface SwirldState extends MerkleNode { *

    * * @param platform the Platform that instantiated this state - * @param swirldDualState the dual state instance used by the initialization function + * @param platformState the platform state * @param trigger describes the reason why the state was created/recreated * @param previousSoftwareVersion the previous version of the software, {@link SoftwareVersion#NO_VERSION} if this * is genesis or if migrating from code from before the concept of an application @@ -53,7 +52,7 @@ public interface SwirldState extends MerkleNode { */ default void init( final Platform platform, - final SwirldDualState swirldDualState, + final PlatformState platformState, final InitTrigger trigger, final SoftwareVersion previousSoftwareVersion) { // Override if needed @@ -67,47 +66,23 @@ default void init( * actual public key, or attaching metadata. Additional signatures extracted from the transaction payload can also * be added to the list of signatures to be verified. *

    - * If signature verification is desired, it is recommended that process be started in this method on a background - * thread using one of the methods below to give it time to complete before the transaction is handled - * post-consensus. - *

      - *
    • {@link com.swirlds.common.crypto.Cryptography#verifyAsync(TransactionSignature)}
    • - *
    • {@link com.swirlds.common.crypto.Cryptography#verifyAsync(List)}
    • - *
    - *

    * This method is always invoked on an immutable state. * * @param event the event to perform pre-handling on - * @see #handleConsensusRound(Round, SwirldDualState) */ default void preHandle(final Event event) {} /** - * {@inheritDoc} - *

    - * The state of this object must NEVER change except inside the methods below. - * - *

      - *
    • {@link #init(Platform, SwirldDualState, InitTrigger, SoftwareVersion)}
    • - *
    • {@link #copy()}
    • - *
    • {@link #handleConsensusRound(Round, SwirldDualState)}
    • - *
    - *

    - * If signature verification was started on a background thread in {@link #preHandle(Event)}, the process - * should be checked for completion. Accessing {@link TransactionSignature#getSignatureStatus()} before this - * process is complete will cause it to return {@code null}: + * This method should apply the transactions in the provided round to the state. Only called on mutable states. * - *

    -     *     for (TransactionSignature sig : transaction.getSignatures()) {
    -     *         Future<Void> future = sig.waitForFuture().get();
    -     *     }
    -     * 
    + * @param round the round to apply + * @param platformState the platform state */ - void handleConsensusRound(final Round round, final SwirldDualState swirldDualState); + void handleConsensusRound(final Round round, final PlatformState platformState); /** * Implementations of the SwirldState should always override this method in production. The AddressBook returned - * should have the same Adddress entries as the configuration AddressBook, but with the weight values updated. + * should have the same Address entries as the configuration AddressBook, but with the weight values updated. *

    * The default implementation of this method is provided for use in testing and to prevent compilation failure of * implementing classes that have not yet implemented this method. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/state/notifications/NewSignedStateNotification.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/state/notifications/NewSignedStateNotification.java index a36ac0c1c27c..3a4584746de5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/state/notifications/NewSignedStateNotification.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/state/notifications/NewSignedStateNotification.java @@ -17,7 +17,7 @@ package com.swirlds.platform.system.state.notifications; import com.swirlds.common.notification.AbstractNotification; -import com.swirlds.platform.system.DualState; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.SwirldState; import java.time.Instant; @@ -29,7 +29,7 @@ public class NewSignedStateNotification extends AbstractNotification { private final SwirldState swirldState; - private final DualState dualState; + private final PlatformState platformState; private final long round; private final Instant consensusTimestamp; @@ -37,18 +37,18 @@ public class NewSignedStateNotification extends AbstractNotification { * Create a notification for a newly signed state. * * @param swirldState the swirld state from the round that is now fully signed - * @param dualState the dual state from the round that is now fully signed + * @param platformState the platform state from the round that is now fully signed * @param round the round that is now fully signed * @param consensusTimestamp the consensus timestamp of the round that is now fully signed */ public NewSignedStateNotification( final SwirldState swirldState, - final DualState dualState, + final PlatformState platformState, final long round, final Instant consensusTimestamp) { this.swirldState = swirldState; - this.dualState = dualState; + this.platformState = platformState; this.round = round; this.consensusTimestamp = consensusTimestamp; } @@ -63,10 +63,10 @@ public T getSwirldState() { } /** - * Get the dual state from the round that is now fully signed. + * Get the platform state from the round that is now fully signed. */ - public DualState getDualState() { - return dualState; + public PlatformState getPlatformState() { + return platformState; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java index 610a7bc3944e..ebcab160b676 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java @@ -270,11 +270,7 @@ public static boolean detectSoftwareUpgrade( final SoftwareVersion loadedSoftwareVersion = loadedSignedState == null ? null - : loadedSignedState - .getState() - .getPlatformState() - .getPlatformData() - .getCreationSoftwareVersion(); + : loadedSignedState.getState().getPlatformState().getCreationSoftwareVersion(); final int versionComparison = loadedSoftwareVersion == null ? 1 : appVersion.compareTo(loadedSoftwareVersion); final boolean softwareUpgrade; if (versionComparison < 0) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java index 988b31c82424..4302fe677acc 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java @@ -34,7 +34,6 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; import com.swirlds.platform.config.AddressBookConfig_; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.address.AddressBookInitializer; @@ -340,10 +339,8 @@ private SignedState getMockSignedState( final SwirldState swirldState = getMockSwirldStateSupplier(weightValue).get(); when(signedState.getAddressBook()).thenReturn(stateAddressBook); when(signedState.getSwirldState()).thenReturn(swirldState); - final PlatformData platformData = mock(PlatformData.class); - when(platformData.getCreationSoftwareVersion()).thenReturn(softwareVersion); final PlatformState platformState = mock(PlatformState.class); - when(platformState.getPlatformData()).thenReturn(platformData); + when(platformState.getCreationSoftwareVersion()).thenReturn(softwareVersion); final State state = mock(State.class); when(state.getPlatformState()).thenReturn(platformState); when(signedState.getState()).thenReturn(state); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java index ded222e73f30..8f7654760e0f 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java @@ -43,7 +43,6 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.platform.consensus.ConsensusSnapshot; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.SavedStateMetadata; @@ -224,15 +223,13 @@ void signingNodesSortedTest() { final State state = mock(State.class); when(state.getHash()).thenReturn(randomHash(random)); final PlatformState platformState = mock(PlatformState.class); - final PlatformData platformData = mock(PlatformData.class); - when(platformData.getHashEventsCons()).thenReturn(randomHash(random)); - when(platformData.getSnapshot()).thenReturn(mock(ConsensusSnapshot.class)); + when(platformState.getRunningEventHash()).thenReturn(randomHash(random)); + when(platformState.getSnapshot()).thenReturn(mock(ConsensusSnapshot.class)); final AddressBook addressBook = mock(AddressBook.class); when(signedState.getState()).thenReturn(state); when(state.getPlatformState()).thenReturn(platformState); when(platformState.getAddressBook()).thenReturn(addressBook); - when(platformState.getPlatformData()).thenReturn(platformData); when(signedState.getSigSet()).thenReturn(sigSet); when(sigSet.getSigningNodes()) .thenReturn(new ArrayList<>(List.of(new NodeId(3L), new NodeId(1L), new NodeId(2L)))); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SwirldStateManagerFreezePeriodCheckerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SwirldStateManagerFreezePeriodCheckerTest.java index acce93b7f1b0..03a5434be4e1 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SwirldStateManagerFreezePeriodCheckerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SwirldStateManagerFreezePeriodCheckerTest.java @@ -16,45 +16,42 @@ package com.swirlds.platform; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertTrue; -import com.swirlds.platform.state.DualStateImpl; -import com.swirlds.platform.state.State; import com.swirlds.platform.state.SwirldStateManagerUtils; -import com.swirlds.platform.system.address.AddressBook; import java.time.Instant; -import java.util.Collections; -import java.util.List; import org.junit.jupiter.api.Test; class SwirldStateManagerFreezePeriodCheckerTest { @Test void isInFreezePeriodTest() { - final State mockState = mock(State.class); - - final DualStateImpl mockDualState = mock(DualStateImpl.class); - final Instant consensusTime = Instant.now(); - - final AddressBook addressBook = mock(AddressBook.class); - when(addressBook.iterator()).thenReturn(Collections.emptyIterator()); - - when(mockState.getPlatformDualState()).thenReturn(null); - assertFalse( - SwirldStateManagerUtils.isInFreezePeriod(Instant.now(), mockState), - "when DualState is null, any Instant should not be in freezePeriod"); - - when(mockState.getPlatformDualState()).thenReturn(mockDualState); - for (boolean isInFreezeTime : List.of(true, false)) { - when(mockDualState.isInFreezePeriod(consensusTime)).thenReturn(isInFreezeTime); - assertEquals( - isInFreezeTime, - SwirldStateManagerUtils.isInFreezePeriod(consensusTime, mockState), - "swirldStateManager#isInFreezePeriod() should return the same result " - + "as current consensus DualState#isInFreezePeriod"); - } + + final Instant t1 = Instant.now(); + final Instant t2 = t1.plusSeconds(1); + final Instant t3 = t2.plusSeconds(1); + final Instant t4 = t3.plusSeconds(1); + + // No freeze time set + assertFalse(SwirldStateManagerUtils.isInFreezePeriod(t1, null, null)); + + // No freeze time set, previous freeze time set + assertFalse(SwirldStateManagerUtils.isInFreezePeriod(t2, null, t1)); + + // Freeze time is in the future, never frozen before + assertFalse(SwirldStateManagerUtils.isInFreezePeriod(t2, t3, null)); + + // Freeze time is in the future, frozen before + assertFalse(SwirldStateManagerUtils.isInFreezePeriod(t2, t3, t1)); + + // Freeze time is in the past, never frozen before + assertTrue(SwirldStateManagerUtils.isInFreezePeriod(t2, t1, null)); + + // Freeze time is in the past, frozen before at an earlier time + assertTrue(SwirldStateManagerUtils.isInFreezePeriod(t3, t2, t1)); + + // Freeze time in the past, already froze at that exact time + assertFalse(SwirldStateManagerUtils.isInFreezePeriod(t3, t2, t2)); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java index a3ccd644f6a5..4b8ab3f9bb4a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java @@ -18,7 +18,6 @@ import static org.mockito.Mockito.when; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.SignedState; @@ -72,16 +71,14 @@ void getMinGenNonAncientFromSignedState() { final SignedState signedState = Mockito.mock(SignedState.class); final State state = Mockito.mock(State.class); final PlatformState platformState = Mockito.mock(PlatformState.class); - final PlatformData platformData = Mockito.mock(PlatformData.class); when(signedState.getState()).thenReturn(state); when(state.getPlatformState()).thenReturn(platformState); - when(platformState.getPlatformData()).thenReturn(platformData); final AtomicLong lastRoundDecided = new AtomicLong(); when(signedState.getRound()).thenAnswer(a -> lastRoundDecided.get()); when(signedState.getMinGen(Mockito.anyLong())).thenAnswer(a -> map.get(a.getArgument(0, Long.class))); - when(platformData.getRound()).thenAnswer(a -> lastRoundDecided.get()); - when(platformData.getMinGen(Mockito.anyLong())).thenAnswer(a -> map.get(a.getArgument(0, Long.class))); + when(platformState.getRound()).thenAnswer(a -> lastRoundDecided.get()); + when(platformState.getMinGen(Mockito.anyLong())).thenAnswer(a -> map.get(a.getArgument(0, Long.class))); lastRoundDecided.set(10); Assertions.assertEquals( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java index d98ebecc0fcc..aac4457aedc0 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/ConsensusRoundHandlerTests.java @@ -36,8 +36,6 @@ import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.SwirldStateMetrics; -import com.swirlds.platform.state.DualStateImpl; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.SwirldStateManager; @@ -194,15 +192,6 @@ private void initConsensusHandler(final SwirldState swirldState) { state.setPlatformState(platformState); - final DualStateImpl platformDualState = mock(DualStateImpl.class); - when(platformDualState.getClassId()).thenReturn(DualStateImpl.CLASS_ID); - when(platformDualState.copy()).thenReturn(platformDualState); - - state.setDualState(platformDualState); - - final PlatformData platformData = mock(PlatformData.class); - when(platformState.getPlatformData()).thenReturn(platformData); - final Configuration configuration = new TestConfigBuilder() .withValue(EventConfig_.MAX_EVENT_QUEUE_FOR_CONS, 500) .getOrCreateConfig(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java index 4c6d6110994a..6c57533222d4 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java @@ -247,8 +247,8 @@ void testSignedStateValidationRandom(final String desc, final List nodes, validator = new DefaultSignedStateValidator(platformContext); final SignedState signedState = stateSignedByNodes(signingNodes); - final SignedStateValidationData originalData = new SignedStateValidationData( - signedState.getState().getPlatformState().getPlatformData(), addressBook); + final SignedStateValidationData originalData = + new SignedStateValidationData(signedState.getState().getPlatformState(), addressBook); final boolean shouldSucceed = stateHasEnoughWeight(nodes, signingNodes); if (shouldSucceed) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java index 5af32105a982..0860bf63dd33 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java @@ -126,7 +126,6 @@ private void executeReconnect(final ReconnectMetrics reconnectMetrics) throws In .build(); final MerkleCryptography cryptography = MerkleCryptoFactory.getInstance(); - cryptography.digestSync(signedState.getState().getPlatformState()); cryptography.digestSync(signedState.getState()); final ReconnectLearner receiver = buildReceiver( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java index 6ed39a784556..90e44f11950e 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/emergency/EmergencySignedStateValidatorTests.java @@ -130,10 +130,7 @@ void stateMatchesRoundAndHash() { } private void assertNextEpochHashEquals(final Hash hash, final SignedState signedState, final String msg) { - assertEquals( - hash, - signedState.getState().getPlatformState().getPlatformData().getNextEpochHash(), - msg); + assertEquals(hash, signedState.getState().getPlatformState().getNextEpochHash(), msg); } /** @@ -154,7 +151,7 @@ void validLaterState() { .build(); final Hash emergencyHash = RandomUtils.randomHash(random); - laterState.getState().getPlatformState().getPlatformData().setEpochHash(emergencyHash); + laterState.getState().getPlatformState().setEpochHash(emergencyHash); validator = new EmergencySignedStateValidator( STATE_CONFIG, @@ -184,7 +181,7 @@ void invalidLaterStateWrongEpochHash() { final Hash emergencyHash = RandomUtils.randomHash(random); final Hash badEpochHash = RandomUtils.randomHash(random); - laterState.getState().getPlatformState().getPlatformData().setNextEpochHash(badEpochHash); + laterState.getState().getPlatformState().setNextEpochHash(badEpochHash); validator = new EmergencySignedStateValidator( STATE_CONFIG, @@ -215,7 +212,7 @@ void invalidLaterStateNotSignedByMajority() { .build(); final Hash emergencyHash = RandomUtils.randomHash(random); - laterState.getState().getPlatformState().getPlatformData().setEpochHash(emergencyHash); + laterState.getState().getPlatformState().setEpochHash(emergencyHash); validator = new EmergencySignedStateValidator( STATE_CONFIG, diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java index dd2f80c3108b..6743cce45439 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/recovery/EventRecoveryWorkflowTests.java @@ -38,8 +38,8 @@ import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.test.framework.config.TestConfigBuilder; @@ -105,7 +105,7 @@ void isFreezeStateTest() { @Test @DisplayName("applyTransactions() Test") void applyTransactionsTest() { - final SwirldDualState dualState = mock(SwirldDualState.class); + final PlatformState platformState = mock(PlatformState.class); final List events = new ArrayList<>(); for (int i = 0; i < 100; i++) { @@ -143,14 +143,14 @@ void applyTransactionsTest() { doAnswer(invocation -> { assertFalse(roundHandled.get(), "round should only be handled once"); assertSame(round, invocation.getArgument(0), "unexpected round"); - assertSame(dualState, invocation.getArgument(1), "unexpected dual state"); + assertSame(platformState, invocation.getArgument(1), "unexpected dual state"); roundHandled.set(true); return null; }) .when(mutableState) .handleConsensusRound(any(), any()); - EventRecoveryWorkflow.applyTransactions(immutableState, mutableState, dualState, round); + EventRecoveryWorkflow.applyTransactions(immutableState, mutableState, platformState, round); assertEquals(events.size(), preHandleList.size(), "incorrect number of pre-handle calls"); for (int index = 0; index < events.size(); index++) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/DualStateImplTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/DualStateImplTest.java deleted file mode 100644 index 7bd83eadeb24..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/DualStateImplTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state; - -import static java.time.temporal.ChronoUnit.DAYS; -import static java.time.temporal.ChronoUnit.HOURS; -import static java.time.temporal.ChronoUnit.MICROS; -import static java.time.temporal.ChronoUnit.MILLIS; -import static java.time.temporal.ChronoUnit.MINUTES; -import static java.time.temporal.ChronoUnit.NANOS; -import static java.time.temporal.ChronoUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.List; -import org.junit.jupiter.api.Test; - -class DualStateImplTest { - private static final int TIME_DIFF = 1; - - static final List timeUnits = List.of(NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, DAYS); - - private final Instant freezeTime = Instant.now(); - - @Test - void isInFreezePeriodTest() { - final DualStateImpl dualState = new DualStateImpl(); - assertFalse( - dualState.isInFreezePeriod(Instant.now()), - "when freezeTime is null, any Instant should not be in freezePeriod"); - - dualState.setFreezeTime(freezeTime); - - // when lastFrozenTime is null - for (ChronoUnit unit : timeUnits) { - assertFalse( - dualState.isInFreezePeriod(freezeTime.minus(TIME_DIFF, unit)), - "Instant before freeze time should not be in freeze period"); - - assertTrue( - dualState.isInFreezePeriod(freezeTime), - "when lastFrozenTime is null, Instant the same as freeze time should be in freeze period"); - - assertTrue( - dualState.isInFreezePeriod(freezeTime.plus(TIME_DIFF, unit)), - "when lastFrozenTime is null, Instant after freeze time should be in freeze period"); - } - - // when lastFrozenTime is the same as freezeTime - dualState.setLastFrozenTime(freezeTime); - for (ChronoUnit unit : timeUnits) { - assertFalse( - dualState.isInFreezePeriod(freezeTime.minus(TIME_DIFF, unit)), - "Instant before freeze time should not be in freeze period"); - assertFalse( - dualState.isInFreezePeriod(this.freezeTime), - "when lastFrozenTime is the same as freezeTime, any Instant should be not in freeze period"); - assertFalse( - dualState.isInFreezePeriod(freezeTime.plus(TIME_DIFF, unit)), - "when lastFrozenTime is the same as freezeTime, any Instant should not be in freeze period"); - } - - // when lastFrozenTime is after freezeTime - for (ChronoUnit unit : timeUnits) { - dualState.setLastFrozenTime(freezeTime.plus(TIME_DIFF * 2, unit)); - assertFalse( - dualState.isInFreezePeriod(freezeTime.minus(TIME_DIFF, unit)), - "Instant before freeze time should not be in freeze period"); - assertFalse( - dualState.isInFreezePeriod(this.freezeTime), - "when lastFrozenTime is after freezeTime, any Instant should be not in freeze period"); - assertFalse( - dualState.isInFreezePeriod(freezeTime.plus(TIME_DIFF, unit)), - "when lastFrozenTime is after freezeTime, any Instant should not be in freeze period"); - } - } -} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java index 9897985b24ce..9689db4babdf 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/RandomSignedStateGenerator.java @@ -113,9 +113,8 @@ public SignedState build() { final DummySwirldState swirldState = new DummySwirldState(addressBookInstance); stateInstance.setSwirldState(swirldState); PlatformState platformState = new PlatformState(); - final PlatformData platformData = new PlatformData(); - platformData.setEpochHash(epoch); - platformState.setPlatformData(platformData); + platformState.setAddressBook(addressBookInstance); + platformState.setEpochHash(epoch); stateInstance.setPlatformState(platformState); } else { stateInstance = state; @@ -163,23 +162,21 @@ public SignedState build() { softwareVersionInstance = softwareVersion; } - stateInstance.getPlatformState().setAddressBook(addressBookInstance); - stateInstance - .getPlatformState() - .getPlatformData() - .setRound(roundInstance) - .setHashEventsCons(hashEventsConsInstance) - .setConsensusTimestamp(consensusTimestampInstance) - .setCreationSoftwareVersion(softwareVersionInstance) - .setRoundsNonAncient(roundsNonAncientInstance) - .setSnapshot(new ConsensusSnapshot( - roundInstance, - Stream.generate(() -> randomHash(random)).limit(10).toList(), - IntStream.range(0, roundsNonAncientInstance) - .mapToObj(i -> new MinGenInfo(roundInstance - i, 0L)) - .toList(), - roundInstance, - consensusTimestampInstance)); + final PlatformState platformState = stateInstance.getPlatformState(); + + platformState.setRound(roundInstance); + platformState.setRunningEventHash(hashEventsConsInstance); + platformState.setConsensusTimestamp(consensusTimestampInstance); + platformState.setCreationSoftwareVersion(softwareVersionInstance); + platformState.setRoundsNonAncient(roundsNonAncientInstance); + platformState.setSnapshot(new ConsensusSnapshot( + roundInstance, + Stream.generate(() -> randomHash(random)).limit(10).toList(), + IntStream.range(0, roundsNonAncientInstance) + .mapToObj(i -> new MinGenInfo(roundInstance - i, 0L)) + .toList(), + roundInstance, + consensusTimestampInstance)); final SignedState signedState = new SignedState( new TestConfigBuilder() diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java index 7a952b588fba..cd94bcd97beb 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java @@ -69,9 +69,7 @@ private State buildMockState(final Runnable reserveCallback, final Runnable rele final State state = spy(new State()); final SwirldState swirldState = mock(SwirldState.class); - final PlatformData platformData = new PlatformData(); final PlatformState platformState = new PlatformState(); - platformState.setPlatformData(platformData); platformState.setAddressBook(mock(AddressBook.class)); when(state.getPlatformState()).thenReturn(platformState); @@ -279,9 +277,7 @@ void alternateConstructorReservationsTest() { final State state = spy(new State()); final PlatformState platformState = mock(PlatformState.class); when(state.getPlatformState()).thenReturn(platformState); - final PlatformData platformData = mock(PlatformData.class); - when(platformState.getPlatformData()).thenReturn(platformData); - when(platformData.getRound()).thenReturn(0L); + when(platformState.getRound()).thenReturn(0L); final SignedState signedState = new SignedState(TestPlatformContextBuilder.create().build(), state, "test", false); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java index a799d4fa8789..a168b588cc60 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerTests.java @@ -119,9 +119,6 @@ private static State newState() { when(platformState.getClassId()).thenReturn(PlatformState.CLASS_ID); when(platformState.copy()).thenReturn(platformState); - final PlatformData platformData = mock(PlatformData.class); - when(platformState.getPlatformData()).thenReturn(platformData); - state.setPlatformState(platformState); assertEquals(0, state.getReservationCount(), "A brand new state should have no references."); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerUtilsTests.java index 6be8d4c17c53..4075158d2db3 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SwirldStateManagerUtilsTests.java @@ -43,15 +43,9 @@ void testFastCopyIsMutable() { when(platformState.copy()).thenReturn(platformState); state.setPlatformState(platformState); - final PlatformData platformData = mock(PlatformData.class); - when(platformState.getPlatformData()).thenReturn(platformData); - final SwirldState swirldState = new DummySwirldState(); state.setSwirldState(swirldState); - final DualStateImpl dualState = new DualStateImpl(); - state.setDualState(dualState); - state.reserve(); final SwirldStateMetrics stats = mock(SwirldStateMetrics.class); final State result = SwirldStateManagerUtils.fastCopy(state, stats, new BasicSoftwareVersion(1)); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/TransactionHandlerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/TransactionHandlerTest.java index 3928cb3dbbd6..d4e44da4614f 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/TransactionHandlerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/TransactionHandlerTest.java @@ -28,7 +28,6 @@ import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.SwirldStateMetrics; import com.swirlds.platform.system.BasicSoftwareVersion; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.events.BaseEventHashedData; import com.swirlds.platform.system.events.BaseEventUnhashedData; @@ -55,13 +54,13 @@ class TransactionHandlerTest { private final State state = mock(State.class); private final SwirldState swirldState = mock(SwirldState.class); - private final SwirldDualState dualState = mock(SwirldDualState.class); + private final PlatformState platformState = mock(PlatformState.class); private TransactionHandler handler; @BeforeEach void setup() { - when(state.getSwirldDualState()).thenReturn(dualState); + when(state.getPlatformState()).thenReturn(platformState); handler = new TransactionHandler(selfId, mock(SwirldStateMetrics.class)); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java index 38d50f9b5596..64d6a2594443 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AddIncompleteStateTest.java @@ -113,10 +113,8 @@ void addIncompleteStateTest() { manager.addState(stateFromDisk); assertEquals( - stateFromDisk.getState().getPlatformState().getPlatformData().getConsensusTimestamp(), - manager.getFirstStateTimestamp()); - assertEquals( - stateFromDisk.getState().getPlatformState().getPlatformData().getRound(), manager.getFirstStateRound()); + stateFromDisk.getState().getPlatformState().getConsensusTimestamp(), manager.getFirstStateTimestamp()); + assertEquals(stateFromDisk.getState().getPlatformState().getRound(), manager.getFirstStateRound()); assertNull(manager.getLatestSignedState("test")); assertEquals(-1, manager.getLastCompleteRound()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java index 3bbc054d05d3..3e58b542c991 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/EarlySignaturesTest.java @@ -143,11 +143,7 @@ void earlySignaturesTest() throws InterruptedException { manager.addState(signedState); if (round == 0) { - firstTimestamp = signedState - .getState() - .getPlatformState() - .getPlatformData() - .getConsensusTimestamp(); + firstTimestamp = signedState.getState().getPlatformState().getConsensusTimestamp(); } assertEquals(firstTimestamp, manager.getFirstStateTimestamp()); assertEquals(firstRound, manager.getFirstStateRound()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java index aa01f2f82df9..e35546589209 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/RegisterStatesWithoutSignaturesTest.java @@ -103,11 +103,7 @@ void registerStatesWithoutSignatures() throws InterruptedException { manager.addState(signedState); if (round == 0) { - firstTimestamp = signedState - .getState() - .getPlatformState() - .getPlatformData() - .getConsensusTimestamp(); + firstTimestamp = signedState.getState().getPlatformState().getConsensusTimestamp(); } assertEquals(firstTimestamp, manager.getFirstStateTimestamp()); assertEquals(firstRound, manager.getFirstStateRound()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java index d3c8b681982d..7911437ced59 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/SequentialSignaturesTest.java @@ -104,11 +104,7 @@ void sequentialSignaturesTest() throws InterruptedException { manager.addState(signedState); if (round == 0) { - firstTimestamp = signedState - .getState() - .getPlatformState() - .getPlatformData() - .getConsensusTimestamp(); + firstTimestamp = signedState.getState().getPlatformState().getConsensusTimestamp(); } assertEquals(firstTimestamp, manager.getFirstStateTimestamp()); assertEquals(firstRound, manager.getFirstStateRound()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java index 06e174a6a36c..9844f2891327 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java @@ -458,13 +458,12 @@ void noStateHasEpochHashPreviousRoundExistsTest(final int startingStateIndex) assertEquals(targetState.getRound(), loadedState.getRound()); assertEquals(targetState.getState().getHash(), loadedState.getState().getHash()); - assertNull(loadedState.getState().getPlatformState().getPlatformData().getEvents()); // As a sanity check, make sure the consensus timestamp is the same. This is generated randomly, so if this // matches then it's a good signal that the correct state was loaded. assertEquals( - targetState.getState().getPlatformState().getPlatformData().getConsensusTimestamp(), - loadedState.getState().getPlatformState().getPlatformData().getConsensusTimestamp()); + targetState.getState().getPlatformState().getConsensusTimestamp(), + loadedState.getState().getPlatformState().getConsensusTimestamp()); assertFalse(emergencyStateLoaded.get()); } @@ -701,12 +700,8 @@ void recoveryCorruptedStateRecyclingPermittedTest(final int invalidStateCount) // As a sanity check, make sure the consensus timestamp is the same. This is generated randomly, so if this // matches then it's a good signal that the correct state was loaded. assertEquals( - latestUncorruptedState - .getState() - .getPlatformState() - .getPlatformData() - .getConsensusTimestamp(), - loadedState.getState().getPlatformState().getPlatformData().getConsensusTimestamp()); + latestUncorruptedState.getState().getPlatformState().getConsensusTimestamp(), + loadedState.getState().getPlatformState().getConsensusTimestamp()); } else { assertNull(loadedState); } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java index 6a7e6b39385c..11a7740fc62d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/HashLoggerTest.java @@ -30,7 +30,6 @@ import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.test.merkle.util.MerkleTestUtils; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.SignedState; @@ -155,13 +154,11 @@ private SignedState createSignedState(final long round) { final AddressBook addressBook = new AddressBook(); addressBook.setHash(merkleNode.getHash()); - final PlatformData platformData = new PlatformData(); - platformData.setRound(round); - platformData.setHash(merkleNode.getHash()); - final PlatformState platformState = new PlatformState(); - platformState.setChild(0, platformData); - platformState.setChild(1, addressBook); + + platformState.setRound(round); + platformState.setHash(merkleNode.getHash()); + platformState.setAddressBook(addressBook); when(state.getPlatformState()).thenReturn(platformState); when(state.getRoute()).thenReturn(merkleNode.getRoute()); diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/EventUtils.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/EventUtils.java index 4ab1743a712f..78daf428d029 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/EventUtils.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/event/EventUtils.java @@ -18,18 +18,8 @@ import static java.lang.Integer.max; -import com.swirlds.common.constructable.ConstructableRegistry; -import com.swirlds.common.constructable.ConstructableRegistryException; -import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; -import com.swirlds.common.test.fixtures.merkle.util.MerkleSerializeUtils; -import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.state.State; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.test.framework.context.TestPlatformContextBuilder; import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; -import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -41,74 +31,6 @@ public abstract class EventUtils { - /** - * Creates a copy of the supplies SignedState to simulate a restart or reconnect - * - * @param signedState the state to copy - * @return a copy of the state - */ - public static SignedState serializeDeserialize(final Path dir, final SignedState signedState) - throws ConstructableRegistryException, IOException { - final ConstructableRegistry registry = ConstructableRegistry.getInstance(); - registry.registerConstructables("com.swirlds.platform.state"); - registry.registerConstructables("com.swirlds.common.*"); - final State stateCopy = MerkleSerializeUtils.serializeDeserialize(dir, signedState.getState()); - final SignedState signedStateCopy = - new SignedState(TestPlatformContextBuilder.create().build(), stateCopy, "test", false); - signedStateCopy.setSigSet(signedState.getSigSet()); - return signedStateCopy; - } - - /** - * Converts events in the SignedState to IndexedEvent - * - * @param signedState the state where events are stored - */ - public static void convertEvents(final SignedState signedState) { - final EventImpl[] events = - signedState.getState().getPlatformState().getPlatformData().getEvents(); - for (int i = 0; i < events.length; i++) { - final IndexedEvent ie = new IndexedEvent(events[i]); - events[i] = ie; - } - State.linkParents(events); - } - - /** - * Get a map from creator sequence pairs to the corresponding event. - * - * @param events - * an array of events - */ - public static Map getEventMap(final EventImpl[] events) { - final Map map = new HashMap<>(); - for (final EventImpl event : events) { - map.put(event.getBaseHash(), event); - } - return map; - } - - /** - * Find the max generation number for each node in a sequence of events. Assumes events are sorted and that there - * are no forks. - * - * @param events - * a list of events - * @param numberOfNodes - * the total number of nodes - * @return a map containing the max generation number for each node - */ - @NonNull - public static Map getLastGenerationInState( - @NonNull final EventImpl[] events, final int numberOfNodes) { - Objects.requireNonNull(events, "events must not be null"); - final Map last = new HashMap<>(numberOfNodes); - for (final EventImpl event : events) { - last.put(event.getCreatorId(), event.getGeneration()); - } - return last; - } - /** * Choose a random integer given a list of probabilistic weights. * diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/DummySwirldState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/DummySwirldState.java index 407d26591e4e..a03083d47fd6 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/DummySwirldState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/DummySwirldState.java @@ -20,8 +20,8 @@ import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; +import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.SwirldDualState; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; import java.io.IOException; @@ -85,7 +85,7 @@ public AddressBook getAddressBookCopy() { } @Override - public void handleConsensusRound(final Round round, final SwirldDualState swirldDualState) { + public void handleConsensusRound(final Round round, final PlatformState platformState) { // intentionally does nothing } diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java index 56de115a7b9c..bcb4ba89fd29 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/module-info.java @@ -3,7 +3,6 @@ requires transitive com.swirlds.common; requires transitive com.swirlds.platform.core; requires com.swirlds.logging; - requires com.swirlds.test.framework; requires org.apache.logging.log4j; requires org.junit.jupiter.api; requires static com.github.spotbugs.annotations; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java index d742f98ed89a..9c2455cd0df5 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/PlatformStateUtils.java @@ -23,7 +23,6 @@ import com.swirlds.common.test.fixtures.RandomAddressBookGenerator.WeightDistributionStrategy; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.state.MinGenInfo; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.address.AddressBook; import java.util.LinkedList; @@ -46,8 +45,6 @@ public static PlatformState randomPlatformState() { */ public static PlatformState randomPlatformState(final Random random) { final PlatformState platformState = new PlatformState(); - final PlatformData platformData = new PlatformData(); - platformState.setPlatformData(platformData); final AddressBook addressBook = new RandomAddressBookGenerator() .setSize(4) @@ -55,22 +52,20 @@ public static PlatformState randomPlatformState(final Random random) { .build(); platformState.setAddressBook(addressBook); - platformData.setHashEventsCons(randomHash(random)); - platformData.setRound(random.nextLong()); - platformData.setConsensusTimestamp(randomInstant(random)); + platformState.setRunningEventHash(randomHash(random)); + platformState.setRound(random.nextLong()); + platformState.setConsensusTimestamp(randomInstant(random)); final List minGenInfo = new LinkedList<>(); for (int index = 0; index < 10; index++) { minGenInfo.add(new MinGenInfo(random.nextLong(), random.nextLong())); } - platformState - .getPlatformData() - .setSnapshot(new ConsensusSnapshot( - random.nextLong(), - List.of(randomHash(random), randomHash(random), randomHash(random)), - minGenInfo, - random.nextLong(), - randomInstant(random))); + platformState.setSnapshot(new ConsensusSnapshot( + random.nextLong(), + List.of(randomHash(random), randomHash(random), randomHash(random)), + minGenInfo, + random.nextLong(), + randomInstant(random))); return platformState; } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java index 1f71ddae2ccd..9594c0c6175f 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/ConsensusUtils.java @@ -23,7 +23,6 @@ import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.metrics.ConsensusMetrics; -import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.NoOpConsensusMetrics; @@ -65,18 +64,6 @@ public static ConsensusImpl buildSimpleConsensus(final AddressBook addressBook) ConfigurationHolder.getConfigData(ConsensusConfig.class), NOOP_CONSENSUS_METRICS, addressBook); } - /** - * Load events from a signed state into a generator, so that they can be used as other parents - * - * @param signedState the source of events - * @param generator the generator to load into to - * @param random a source of randomness - */ - public static void loadEventsIntoGenerator( - final SignedState signedState, final GraphGenerator generator, final Random random) { - loadEventsIntoGenerator(signedState.getEvents(), generator, random); - } - public static void loadEventsIntoGenerator( final EventImpl[] events, final GraphGenerator generator, final Random random) { Instant lastTimestamp = Instant.MIN; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java index 7c004513a30f..8e32b895b4fe 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/TestIntake.java @@ -49,7 +49,6 @@ import com.swirlds.test.framework.context.TestPlatformContextBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Arrays; import java.util.Deque; import java.util.List; @@ -184,7 +183,6 @@ public void addLinkedEvent(@NonNull final EventImpl event) { public void loadFromSignedState(@NonNull final SignedState signedState) { consensus.loadFromSignedState(signedState); shadowGraph.clear(); - shadowGraph.initFromEvents(Arrays.asList(signedState.getEvents()), consensus.getMinRoundGeneration()); } public void loadSnapshot(@NonNull final ConsensusSnapshot snapshot) { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java index 03a6ddf2a80d..cbfe6dd9261d 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestNode.java @@ -20,8 +20,6 @@ import com.swirlds.platform.Consensus; import com.swirlds.platform.consensus.ConsensusSnapshot; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.test.consensus.ConsensusUtils; import com.swirlds.platform.test.consensus.TestIntake; import com.swirlds.platform.test.event.emitter.EventEmitter; import edu.umd.cs.findbugs.annotations.NonNull; @@ -93,13 +91,6 @@ public void restart() { return consensusTestNode; } - public void loadSignedState(@NonNull final SignedState signedState) { - eventEmitter.reset(); - intake.reset(); - ConsensusUtils.loadEventsIntoGenerator(signedState, eventEmitter.getGraphGenerator(), random); - intake.loadFromSignedState(signedState); - } - /** * Adds a number of events from the emitter to the node * diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java index fae25850d93a..00ad595751a8 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/consensus/framework/ConsensusTestOrchestrator.java @@ -16,7 +16,6 @@ package com.swirlds.platform.test.consensus.framework; -import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.EventConstants; import com.swirlds.platform.test.consensus.framework.validation.ConsensusOutputValidation; @@ -60,10 +59,6 @@ public void runGui() { new TestGuiSource(node.getEventEmitter().getGraphGenerator(), node.getIntake()).runGui(); } - public void loadSignedState(final SignedState signedState) { - nodes.forEach(node -> node.loadSignedState(signedState)); - } - /** Generates all events defined in the input */ public ConsensusTestOrchestrator generateAllEvents() { return generateEvents(1d); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/StateTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/StateTests.java index 015233545049..859e8722021b 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/StateTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/StateTests.java @@ -25,7 +25,6 @@ import com.swirlds.common.constructable.ConstructableRegistryException; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.test.fixtures.io.InputOutputStream; -import com.swirlds.platform.state.DualStateImpl; import com.swirlds.platform.state.State; import com.swirlds.platform.test.fixtures.state.DummySwirldState; import com.swirlds.test.framework.TestComponentTags; @@ -58,7 +57,6 @@ static void setUp() throws ConstructableRegistryException { state = new State(); state.setPlatformState(randomPlatformState()); state.setSwirldState(new DummySwirldState()); - state.setDualState(new DualStateImpl()); state.invalidateHash(); MerkleCryptoFactory.getInstance().digestTreeSync(state); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java index ce13aef29e47..84b12be84be1 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTestDefinitions.java @@ -22,8 +22,6 @@ import static com.swirlds.platform.test.graph.OtherParentMatrixFactory.createPartitionedOtherParentAffinityMatrix; import static com.swirlds.platform.test.graph.OtherParentMatrixFactory.createShunnedNodeOtherParentAffinityMatrix; -import com.swirlds.common.constructable.ConstructableRegistry; -import com.swirlds.common.constructable.ConstructableRegistryException; import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.Threshold; import com.swirlds.config.api.ConfigurationBuilder; @@ -32,8 +30,6 @@ import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.SyntheticSnapshot; import com.swirlds.platform.internal.EventImpl; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.state.signed.SignedStateFileReader; import com.swirlds.platform.test.consensus.framework.ConsensusTestNode; import com.swirlds.platform.test.consensus.framework.ConsensusTestOrchestrator; import com.swirlds.platform.test.consensus.framework.ConsensusTestUtils; @@ -45,19 +41,22 @@ import com.swirlds.platform.test.event.emitter.StandardEventEmitter; import com.swirlds.platform.test.event.source.ForkingEventSource; import com.swirlds.platform.test.fixtures.event.DynamicValue; -import com.swirlds.platform.test.fixtures.event.EventUtils; import com.swirlds.platform.test.fixtures.event.IndexedEvent; import com.swirlds.platform.test.fixtures.event.generator.StandardGraphGenerator; import com.swirlds.platform.test.fixtures.event.source.EventSource; import com.swirlds.platform.test.fixtures.event.source.StandardEventSource; -import com.swirlds.test.framework.ResourceLoader; -import com.swirlds.test.framework.context.TestPlatformContextBuilder; import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Path; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.Spliterators; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.IntStream; @@ -68,8 +67,7 @@ public final class ConsensusTestDefinitions { private ConsensusTestDefinitions() {} /** - * Changing the order of events (without breaking topological order) should result in the same - * consensus events. + * Changing the order of events (without breaking topological order) should result in the same consensus events. */ public static void orderInvarianceTests(@NonNull final TestInput input) { OrchestratorBuilder.builder() @@ -380,8 +378,7 @@ public static void providesStaleOtherParents(@NonNull final TestInput input) { } /** - * A quorum of nodes stop producing events, thus preventing consensus and round created - * advancement + * A quorum of nodes stop producing events, thus preventing consensus and round created advancement */ public static void quorumOfNodesGoDown(@NonNull final TestInput input) { // Test setup @@ -470,8 +467,7 @@ public static void subQuorumOfNodesGoDown(@NonNull final TestInput input) { } /** - * There should be no problems when the probability of events landing on the same timestamp is - * higher than usual. + * There should be no problems when the probability of events landing on the same timestamp is higher than usual. */ public static void repeatedTimestampTest(@NonNull final TestInput input) { OrchestratorBuilder.builder() @@ -507,8 +503,8 @@ public static void stale(@NonNull final TestInput input) { } /** - * Simulates a consensus restart. The number of nodes and number of events is chosen randomly - * between the supplied bounds + * Simulates a consensus restart. The number of nodes and number of events is chosen randomly between the supplied + * bounds */ public static void restart(@NonNull final TestInput input) { @@ -541,23 +537,6 @@ public static void reconnect(@NonNull final TestInput input) { Validations.standard().ratios(EventRatioValidation.blank().setMinimumConsensusRatio(0.5))); } - public static void migrationTest(@NonNull final TestInput input) - throws URISyntaxException, ConstructableRegistryException, IOException { - ConstructableRegistry.getInstance().registerConstructables("com.swirlds"); - final Path ssPath = ResourceLoader.getFile("modified-mainnet-state.swh.bin"); - final SignedState state = SignedStateFileReader.readSignedStateOnly( - TestPlatformContextBuilder.create().build(), ssPath); - EventUtils.convertEvents(state); - - final ConsensusTestOrchestrator orchestrator = - OrchestratorBuilder.builder().setTestInput(input).build(); - orchestrator.loadSignedState(state); - - orchestrator.generateEvents(1); - orchestrator.validateAndClear( - Validations.standard().ratios(EventRatioValidation.blank().setMinimumConsensusRatio(0.5))); - } - public static void removeNode(@NonNull final TestInput input) { final ConsensusTestOrchestrator orchestrator1 = OrchestratorBuilder.builder().setTestInput(input).build(); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java index a0ca78c345b5..2d29c8bfd8ff 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/consensus/ConsensusTests.java @@ -264,20 +264,6 @@ void fastRestartWithEvents(final ConsensusTestParams params) { .run(); } - @ParameterizedTest - @MethodSource("com.swirlds.platform.test.consensus.ConsensusTestArgs#migrationTestParams") - @Tag(TestTypeTags.FUNCTIONAL) - @Tag(TestComponentTags.PLATFORM) - @Tag(TestComponentTags.CONSENSUS) - @DisplayName("Migration from a state with events to new consensus") - void migrationTest(final ConsensusTestParams params) { - ConsensusTestRunner.create() - .setTest(ConsensusTestDefinitions::migrationTest) - .setParams(params) - .setIterations(NUM_ITER) - .run(); - } - @ParameterizedTest @MethodSource("com.swirlds.platform.test.consensus.ConsensusTestArgs#nodeRemoveTestParams") @Tag(TestTypeTags.FUNCTIONAL) diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/linking/OrphanBufferingLinkerTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/linking/OrphanBufferingLinkerTest.java index 7a7e08ce721e..28759e869a67 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/linking/OrphanBufferingLinkerTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/linking/OrphanBufferingLinkerTest.java @@ -29,7 +29,6 @@ import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.shadowgraph.Generations; import com.swirlds.platform.state.MinGenInfo; -import com.swirlds.platform.state.PlatformData; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.state.State; import com.swirlds.platform.state.signed.SignedState; @@ -229,13 +228,11 @@ void loadState() { final SignedState signedState = mock(SignedState.class); final State state = mock(State.class); final PlatformState platformState = mock(PlatformState.class); - final PlatformData platformData = mock(PlatformData.class); when(signedState.getState()).thenReturn(state); when(state.getPlatformState()).thenReturn(platformState); - when(platformState.getPlatformData()).thenReturn(platformData); - when(platformData.getRound()).thenReturn(roundGenStart); - when(platformData.getMinGen(Mockito.anyLong())).thenCallRealMethod(); - when(platformData.getMinGenInfo()).thenReturn(minGenInfos); + when(platformState.getRound()).thenReturn(roundGenStart); + when(platformState.getMinGen(Mockito.anyLong())).thenCallRealMethod(); + when(platformState.getMinGenInfo()).thenReturn(minGenInfos); when(signedState.getRound()).thenReturn(roundGenEnd); when(signedState.getMinGenInfo()).thenReturn(minGenInfos); when(signedState.getMinGen(Mockito.anyLong())).thenCallRealMethod(); From 68f1290368a60ef8f819c6d7e8b1fd879293c02d Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:49:36 -0600 Subject: [PATCH 55/80] chore: Fix StakingSuite in mono and modular service (#10590) Signed-off-by: Neeharika-Sompalli --- .../configuration/dev/application.properties | 4 + .../configuration/dev/bootstrap.properties | 4 + .../main/java/com/hedera/node/app/Hedera.java | 41 +- .../app/state/merkle/MerkleHederaState.java | 22 +- .../node/app/state/merkle/OnUpdateWeight.java | 31 ++ .../java/com/hedera/node/app/HederaTest.java | 178 +++++- .../app/state/merkle/AddresBookUtils.java | 66 +++ .../state/merkle/DependencyMigrationTest.java | 3 +- .../state/merkle/MerkleHederaStateTest.java | 23 +- .../merkle/MerkleSchemaRegistryTest.java | 8 +- .../app/state/merkle/SerializationTest.java | 8 +- .../impl/handlers/ContractUpdateHandler.java | 8 +- .../handlers/ContractUpdateHandlerTest.java | 3 - .../impl/handlers/CryptoCreateHandler.java | 4 + .../impl/handlers/CryptoUpdateHandler.java | 11 +- .../handlers/staking/StakeIdChangeType.java | 25 +- .../staking/StakeRewardCalculatorImpl.java | 3 + .../staking/StakingRewardsDistributor.java | 18 +- .../staking/StakingRewardsHandlerImpl.java | 169 ++++-- .../staking/StakingRewardsHelper.java | 26 +- .../handlers/staking/StakingUtilities.java | 7 +- .../token/impl/schemas/TokenSchema.java | 25 +- .../impl/validators/StakingValidator.java | 1 - .../handlers/CryptoCreateHandlerTest.java | 8 +- .../StakingRewardsHandlerImplTest.java | 60 +- .../token/api/AccountSummariesApi.java | 2 +- .../service/token/api/StakingRewardsApi.java | 6 +- .../src/main/java/module-info.java | 1 + .../src/itest/java/SequentialSuites.java | 2 + .../network/config/application.properties | 3 + .../network/config/bootstrap.properties | 4 + .../spec/assertions/AccountInfoAsserts.java | 8 + .../queries/crypto/HapiGetAccountInfo.java | 2 +- .../spec/queries/meta/HapiGetTxnRecord.java | 19 +- .../opcodes/GlobalPropertiesSuite.java | 22 +- .../precompile/CreatePrecompileSuite.java | 34 +- .../precompile/PrngPrecompileSuite.java | 55 +- .../suites/crypto/staking/StakingSuite.java | 514 ++++++++---------- .../suites/leaky/LeakyCryptoTestsSuite.java | 6 +- 39 files changed, 951 insertions(+), 483 deletions(-) create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnUpdateWeight.java create mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java diff --git a/hedera-node/configuration/dev/application.properties b/hedera-node/configuration/dev/application.properties index 7345d6c50c4d..0bd47fc439b9 100644 --- a/hedera-node/configuration/dev/application.properties +++ b/hedera-node/configuration/dev/application.properties @@ -3,3 +3,7 @@ ledger.autoRenewPeriod.minDuration=10 ledger.autoRenewPeriod.maxDuration=1000000000 upgrade.artifacts.path=data/upgrade contracts.chainId=298 +# Needed for end-end tests running on mod-service code +staking.periodMins=1 +staking.fees.nodeRewardPercentage=10 +staking.fees.stakingRewardPercentage=10 diff --git a/hedera-node/configuration/dev/bootstrap.properties b/hedera-node/configuration/dev/bootstrap.properties index 7af8c9ed2c27..6f3c0aea293d 100644 --- a/hedera-node/configuration/dev/bootstrap.properties +++ b/hedera-node/configuration/dev/bootstrap.properties @@ -4,5 +4,9 @@ tokens.storeRelsOnDisk=true tokens.nfts.useVirtualMerkle=true accounts.blocklist.enabled=false accounts.blocklist.path= +# Needed for end-end tests running on mono-service code +staking.periodMins=1 +staking.fees.nodeRewardPercentage=10 +staking.fees.stakingRewardPercentage=10 contracts.knownBlockHash= \ No newline at end of file diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java index ba09ead52f0f..24c772baba79 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java @@ -53,6 +53,8 @@ import com.hedera.node.app.service.networkadmin.impl.FreezeServiceImpl; import com.hedera.node.app.service.networkadmin.impl.NetworkServiceImpl; import com.hedera.node.app.service.schedule.impl.ScheduleServiceImpl; +import com.hedera.node.app.service.token.ReadableStakingInfoStore; +import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.impl.TokenServiceImpl; import com.hedera.node.app.service.token.impl.schemas.SyntheticRecordsGenerator; import com.hedera.node.app.service.util.impl.UtilServiceImpl; @@ -81,6 +83,7 @@ import com.swirlds.common.constructable.ClassConstructorPair; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.listeners.PlatformStatusChangeListener; @@ -91,6 +94,7 @@ import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SwirldState; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; import com.swirlds.platform.system.status.PlatformStatus; import com.swirlds.platform.system.transaction.Transaction; @@ -352,7 +356,8 @@ public SoftwareVersion getSoftwareVersion() { @Override @NonNull public SwirldState newState() { - return new MerkleHederaState(this::onPreHandle, this::onHandleConsensusRound, this::onStateInitialized); + return new MerkleHederaState( + this::onPreHandle, this::onHandleConsensusRound, this::onStateInitialized, this::onUpdateWeight); } /*================================================================================================================== @@ -725,6 +730,40 @@ private void onHandleConsensusRound( daggerApp.handleWorkflow().handleRound(state, platformState, round); } + /** + * Invoked by the platform to update weights of all nodes, to be used in consensus. + * This only happens during upgrade. + * @param state current state + * @param configAddressBook address book from config.txt + * @param context platform context + */ + public void onUpdateWeight( + @NonNull final MerkleHederaState state, + @NonNull AddressBook configAddressBook, + @NonNull final PlatformContext context) { + final var tokenServiceState = state.createReadableStates(TokenService.NAME); + if (!tokenServiceState.isEmpty()) { + final var readableStoreFactory = new ReadableStoreFactory(state); + // Get all nodeIds added in the config.txt + Set configNodeIds = configAddressBook.getNodeIdSet(); + final var stakingInfoStore = readableStoreFactory.getStore(ReadableStakingInfoStore.class); + final var allNodeIds = stakingInfoStore.getAll(); + for (final var nodeId : allNodeIds) { + final var stakingInfo = stakingInfoStore.get(nodeId); + NodeId id = new NodeId(nodeId.longValue()); + // ste weight for the nodes that exist in state and remove from + // nodes given in config.txt. This is needed to recognize newly added nodes + configAddressBook.updateWeight(id, stakingInfo.weight()); + configNodeIds.remove(id); + } + // for any newly added nodes that doesn't exist in state, weight should be set to 0 + // irrespective of the weight provided in config.txt + configNodeIds.forEach(nodeId -> configAddressBook.updateWeight(nodeId, 0)); + } else { + logger.warn("Token service state is empty to update weights from StakingInfo Map"); + } + } + /*================================================================================================================== * * gRPC Server Lifecycle diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java index 6f3dd5052cf3..2bb6e0bb83ea 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleHederaState.java @@ -47,6 +47,7 @@ import com.hedera.node.app.state.merkle.singleton.SingletonNode; import com.hedera.node.app.state.merkle.singleton.WritableSingletonStateImpl; import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleInternal; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; @@ -59,6 +60,7 @@ import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SwirldState; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; import com.swirlds.virtualmap.VirtualMap; import edu.umd.cs.findbugs.annotations.NonNull; @@ -143,6 +145,11 @@ public class MerkleHederaState extends PartialNaryMerkleInternal implements Merk */ private OnStateInitialized onInit; + /** + * This callback is invoked whenever the updateWeight is called. + */ + private OnUpdateWeight onUpdateWeight; + /** * Maintains information about each service, and each state of each service, known by this * instance. The key is the "service-name.state-key". @@ -159,10 +166,12 @@ public class MerkleHederaState extends PartialNaryMerkleInternal implements Merk public MerkleHederaState( @NonNull final PreHandleListener onPreHandle, @NonNull final HandleConsensusRoundListener onHandleConsensusRound, - @NonNull final OnStateInitialized onInit) { + @NonNull final OnStateInitialized onInit, + @NonNull final OnUpdateWeight onUpdateWeight) { this.onPreHandle = requireNonNull(onPreHandle); this.onHandleConsensusRound = requireNonNull(onHandleConsensusRound); this.onInit = requireNonNull(onInit); + this.onUpdateWeight = requireNonNull(onUpdateWeight); this.classId = CLASS_ID; } @@ -200,6 +209,17 @@ public void init( this.onInit.onStateInitialized(this, platform, platformState, trigger, deserializedVersion); } + /** + * {@inheritDoc} + */ + @NonNull + @Override + public AddressBook updateWeight( + @NonNull final AddressBook configAddressBook, @NonNull final PlatformContext context) { + this.onUpdateWeight.updateWeight(this, configAddressBook, context); + return configAddressBook; + } + /** * Private constructor for fast-copy. * diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnUpdateWeight.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnUpdateWeight.java new file mode 100644 index 000000000000..2ccd88ca504b --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/OnUpdateWeight.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.state.merkle; + +import com.swirlds.common.context.PlatformContext; +import com.swirlds.platform.system.address.AddressBook; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A callback that is invoked when platform calls updateWeight during upgrade. + */ +public interface OnUpdateWeight { + void updateWeight( + @NonNull final MerkleHederaState state, + @NonNull AddressBook configAddressBook, + @NonNull final PlatformContext context); +} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/HederaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/HederaTest.java index 7c9ac3ee3984..c56673f27122 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/HederaTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/HederaTest.java @@ -16,7 +16,35 @@ package com.hedera.node.app; -final class HederaTest { +import static com.hedera.node.app.service.token.impl.TokenServiceImpl.STAKING_INFO_KEY; +import static com.hedera.node.app.state.merkle.AddresBookUtils.createPretendBookFrom; +import static com.swirlds.common.constructable.ConstructableRegistryFactory.createConstructableRegistry; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.lenient; + +import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.hapi.node.state.token.StakingNodeInfo; +import com.hedera.node.app.service.token.TokenService; +import com.hedera.node.app.spi.fixtures.state.MapReadableKVState; +import com.hedera.node.app.spi.fixtures.state.MapReadableStates; +import com.hedera.node.app.state.merkle.MerkleHederaState; +import com.hedera.node.app.state.merkle.MerkleTestBase; +import com.swirlds.common.constructable.ConstructableRegistry; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.system.Platform; +import java.util.Iterator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +final class HederaTest extends MerkleTestBase { // Constructor: null registry throws // Constructor: bootstrap props throws // Constructor: Null version throws (pass the version in) @@ -70,4 +98,152 @@ final class HederaTest { // restart: housekeeping? freeze? etc. // rebuild aliases? + + @Mock + private Platform platform; + + @Mock + private PlatformContext platformContext; + + @Mock + private MerkleHederaState merkleHederaState; + + @Mock + private MapReadableStates readableStates; + + @Mock + private MapReadableKVState mockReadableKVState; + + @Mock + private Iterator mockIterator; + + private ConstructableRegistry constructableRegistry = createConstructableRegistry(); + private Hedera hedera = new Hedera(constructableRegistry); + + @BeforeEach + void setUp() { + given(merkleHederaState.createReadableStates(TokenService.NAME)).willReturn(readableStates); + final var readableStakingNodes = MapReadableKVState.builder(STAKING_INFO_KEY) + .value( + EntityNumber.newBuilder().number(0L).build(), + StakingNodeInfo.newBuilder().nodeNumber(0L).build()) + .value( + EntityNumber.newBuilder().number(1L).build(), + StakingNodeInfo.newBuilder().nodeNumber(1L).build()) + .build(); + given(readableStates.get(STAKING_INFO_KEY)) + .willReturn(readableStakingNodes); + lenient().when(mockReadableKVState.keys()).thenReturn(mockIterator); + } + + @Nested + @DisplayName("Handling updateWeight Tests") + final class UpdateWeightTest { + @Test + void updatesAddressBookWithZeroWeightOnGenesisStart() { + final var node0 = new NodeId(0); + final var node1 = new NodeId(1); + given(platform.getSelfId()).willReturn(node0); + given(platform.getContext()).willReturn(platformContext); + + final var pretendAddressBook = createPretendBookFrom(platform, true); + + assertEquals(10L, pretendAddressBook.getAddress(node0).getWeight()); + assertEquals(10L, pretendAddressBook.getAddress(node1).getWeight()); + + hedera.onUpdateWeight(merkleHederaState, pretendAddressBook, platform.getContext()); + + // if staking info map has node with 0 weight and a new node is added, + // both gets weight of 0 + assertEquals(0L, pretendAddressBook.getAddress(node0).getWeight()); + assertEquals(0L, pretendAddressBook.getAddress(node1).getWeight()); + } + + @Test + void updatesAddressBookWithZeroWeightForNewNodes() { + final var node0 = new NodeId(0); + final var node1 = new NodeId(1); + given(platform.getSelfId()).willReturn(node0); + given(platform.getContext()).willReturn(platformContext); + + final var pretendAddressBook = createPretendBookFrom(platform, true); + final var readableStakingNodes = MapReadableKVState.builder(STAKING_INFO_KEY) + .value( + EntityNumber.newBuilder().number(0L).build(), + StakingNodeInfo.newBuilder() + .nodeNumber(0L) + .stake(1000L) + .weight(500) + .build()) + .build(); + given(readableStates.get(STAKING_INFO_KEY)) + .willReturn(readableStakingNodes); + + assertEquals( + 1000L, + readableStakingNodes + .get(EntityNumber.newBuilder().number(0L).build()) + .stake()); + + assertEquals(10L, pretendAddressBook.getAddress(node0).getWeight()); + assertEquals(10L, pretendAddressBook.getAddress(node1).getWeight()); + + hedera.onUpdateWeight(merkleHederaState, pretendAddressBook, platform.getContext()); + + // if staking info map has node with 0 weight and a new node is added, + // new nodes gets weight of 0 + assertEquals(500, pretendAddressBook.getAddress(node0).getWeight()); + assertEquals(0L, pretendAddressBook.getAddress(node1).getWeight()); + } + + @Test + void updatesAddressBookWithNonZeroWeightsOnGenesisStartIfStakesExist() { + final var node0 = new NodeId(0); + final var node1 = new NodeId(1); + given(platform.getSelfId()).willReturn(node0); + given(platform.getContext()).willReturn(platformContext); + + final var pretendAddressBook = createPretendBookFrom(platform, true); + + final var readableStakingNodes = MapReadableKVState.builder(STAKING_INFO_KEY) + .value( + EntityNumber.newBuilder().number(0L).build(), + StakingNodeInfo.newBuilder() + .nodeNumber(0L) + .stake(1000L) + .weight(250) + .build()) + .value( + EntityNumber.newBuilder().number(1L).build(), + StakingNodeInfo.newBuilder() + .nodeNumber(1L) + .stake(1000L) + .weight(250) + .build()) + .build(); + given(readableStates.get(STAKING_INFO_KEY)) + .willReturn(readableStakingNodes); + + assertEquals( + 1000L, + readableStakingNodes + .get(EntityNumber.newBuilder().number(0L).build()) + .stake()); + assertEquals( + 1000L, + readableStakingNodes + .get(EntityNumber.newBuilder().number(1L).build()) + .stake()); + + assertEquals(10L, pretendAddressBook.getAddress(node0).getWeight()); + assertEquals(10L, pretendAddressBook.getAddress(node1).getWeight()); + + hedera.onUpdateWeight(merkleHederaState, pretendAddressBook, platform.getContext()); + + // if staking info map has node with 250L weight and a new node is added, + // both gets weight of 250L + assertEquals(250L, pretendAddressBook.getAddress(node0).getWeight()); + assertEquals(250L, pretendAddressBook.getAddress(node1).getWeight()); + } + } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java new file mode 100644 index 000000000000..ba81c28acf46 --- /dev/null +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/AddresBookUtils.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.state.merkle; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import com.swirlds.common.crypto.SerializablePublicKey; +import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.system.Platform; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.system.address.AddressBook; +import java.security.PublicKey; +import java.util.List; + +/** + * Utilities for constructing AddressBook needed for tests + */ +public class AddresBookUtils { + + public static AddressBook createPretendBookFrom(final Platform platform, final boolean withKeyDetails) { + final var pubKey = mock(PublicKey.class); + given(pubKey.getAlgorithm()).willReturn("EC"); + final var address1 = new Address( + platform.getSelfId(), + "", + "", + 10L, + null, + -1, + "123456789", + -1, + new SerializablePublicKey(pubKey), + null, + new SerializablePublicKey(pubKey), + ""); + final var address2 = new Address( + new NodeId(1), + "", + "", + 10L, + null, + -1, + "123456789", + -1, + new SerializablePublicKey(pubKey), + null, + new SerializablePublicKey(pubKey), + ""); + return new AddressBook(List.of(address1, address2)); + } +} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java index 66649a39df60..08c5895e1d4d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/DependencyMigrationTest.java @@ -72,7 +72,8 @@ class DependencyMigrationTest extends MerkleTestBase { @BeforeEach void setUp() { registry = mock(ConstructableRegistry.class); - merkleTree = new MerkleHederaState((tree, state) -> {}, (e, m, s) -> {}, (s, p, ds, t, dv) -> {}); + merkleTree = + new MerkleHederaState((tree, state) -> {}, (e, m, s) -> {}, (s, p, ds, t, dv) -> {}, (s, ab, pc) -> {}); } @Nested diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java index 47af6d8bc664..e6a8de0920b1 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleHederaStateTest.java @@ -29,10 +29,12 @@ import com.hedera.node.app.spi.state.WritableQueueState; import com.hedera.node.app.spi.state.WritableSingletonState; import com.swirlds.base.state.MutabilityException; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleNode; import com.swirlds.merkle.map.MerkleMap; import com.swirlds.platform.state.PlatformState; import com.swirlds.platform.system.Round; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.Event; import java.util.ArrayList; import java.util.Collections; @@ -54,6 +56,7 @@ class MerkleHederaStateTest extends MerkleTestBase { private final AtomicBoolean onMigrateCalled = new AtomicBoolean(false); private final AtomicBoolean onPreHandleCalled = new AtomicBoolean(false); private final AtomicBoolean onHandleCalled = new AtomicBoolean(false); + private final AtomicBoolean onUpdateWeightCalled = new AtomicBoolean(false); /** * Start with an empty Merkle Tree, but with the "fruit" map and metadata created and ready to @@ -65,7 +68,10 @@ void setUp() { hederaMerkle = new MerkleHederaState( (tree, state) -> onPreHandleCalled.set(true), (evt, meta, state) -> onHandleCalled.set(true), - (state, platform, platformState, trigger, version) -> onMigrateCalled.set(true)); + (state, platform, platformState, trigger, version) -> onMigrateCalled.set(true), + (state, configAddressBook, context) -> { + onUpdateWeightCalled.set(true); + }); } /** Looks for a merkle node with the given label */ @@ -695,7 +701,8 @@ void handleConsensusRoundCallback() { assertThat(round).isSameAs(evt); onHandleCalled.set(true); }, - (s, p, d, t, v) -> {}); + (s, p, d, t, v) -> {}, + (s, p, d) -> {}); state.handleConsensusRound(round, platformState); assertThat(onHandleCalled).isTrue(); @@ -754,4 +761,16 @@ void createWritableStatesOnOriginalAfterCopyThrows() { .isInstanceOf(MutabilityException.class); } } + + @Nested + @DisplayName("Handling updateWeight Tests") + final class UpdateWeightTest { + @Test + @DisplayName("The onUpdateWeight handler is called when a updateWeight is called") + void onUpdateWeightCalled() { + assertThat(onUpdateWeightCalled).isFalse(); + hederaMerkle.updateWeight(Mockito.mock(AddressBook.class), Mockito.mock(PlatformContext.class)); + assertThat(onUpdateWeightCalled).isTrue(); + } + } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java index e3336a3018d0..83a27b3da142 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistryTest.java @@ -163,7 +163,10 @@ void registerSameVersionDifferentInstances() { void migrateFromV9ToV10() { schemaRegistry.migrate( new MerkleHederaState( - (tree, state) -> {}, (e, m, s) -> {}, (state, platform, dualState, trigger, version) -> {}), + (tree, state) -> {}, + (e, m, s) -> {}, + (state, platform, dualState, trigger, version) -> {}, + (s, ab, pc) -> {}), version(9, 0, 0), version(10, 0, 0), config, @@ -181,7 +184,8 @@ class MigrationTest { @BeforeEach void setUp() { - merkleTree = new MerkleHederaState((tree, state) -> {}, (e, m, s) -> {}, (s, p, ds, t, dv) -> {}); + merkleTree = new MerkleHederaState( + (tree, state) -> {}, (e, m, s) -> {}, (s, p, ds, t, dv) -> {}, (s, ab, pc) -> {}); // Let the first version[0] be null, and all others have a number versions = new SemanticVersion[10]; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index b4d4aeaeff74..20445841edfc 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -124,7 +124,10 @@ void simpleReadAndWrite() throws IOException, ConstructableRegistryException { // Given a merkle tree with some fruit and animals and country final var v1 = version(1, 0, 0); final var originalTree = new MerkleHederaState( - (tree, state) -> {}, (evt, meta, provider) -> {}, (state, platform, dual, trigger, version) -> {}); + (tree, state) -> {}, + (evt, meta, provider) -> {}, + (state, platform, dual, trigger, version) -> {}, + (state, addressBook, platformContext) -> {}); final var originalRegistry = new MerkleSchemaRegistry(registry, FIRST_SERVICE, mock(GenesisRecordsBuilder.class)); final var schemaV1 = createV1Schema(); @@ -151,7 +154,8 @@ void simpleReadAndWrite() throws IOException, ConstructableRegistryException { handleThrottling, mock(WritableEntityIdStore.class)), (event, meta, provider) -> {}, - (state, platform, dualState, trigger, version) -> {}); + (state, platform, dualState, trigger, version) -> {}, + (state, addressBook, platformContext) -> {}); final var pair = new ClassConstructorPair(MerkleHederaState.class, constructor); registry.registerConstructable(pair); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java index e262b76ffe8b..d3898fada5f7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java @@ -176,10 +176,10 @@ private void validateSemantics( context.configuration() .getConfigData(StakingConfig.class) .isEnabled(), - contract.declineReward(), - contract.stakedId().kind().name(), - contract.stakedAccountId(), - contract.stakedNodeId(), + op.hasDeclineReward(), + op.stakedId().kind().name(), + op.stakedAccountId(), + op.stakedNodeId(), accountStore, context.networkInfo()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java index 2aa034eb61ea..c136353d415e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java @@ -54,7 +54,6 @@ import com.hedera.hapi.node.contract.ContractUpdateTransactionBody; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.state.token.Account.Builder; -import com.hedera.hapi.node.state.token.Account.StakedIdOneOfType; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.handlers.ContractUpdateHandler; import com.hedera.node.app.service.contract.impl.records.ContractUpdateRecordBuilder; @@ -71,7 +70,6 @@ import com.hedera.node.config.data.LedgerConfig; import com.hedera.node.config.data.StakingConfig; import com.hedera.node.config.data.TokensConfig; -import com.hedera.pbj.runtime.OneOf; import com.hedera.test.utils.KeyUtils; import com.swirlds.config.api.Configuration; import org.junit.jupiter.api.BeforeEach; @@ -431,7 +429,6 @@ void verifyTheCorrectOutsideValidatorsAndUpdateContractAPIAreCalled() { doReturn(attributeValidator).when(context).attributeValidator(); when(accountStore.getContractById(targetContract)).thenReturn(contract); when(contract.key()).thenReturn(Key.newBuilder().build()); - when(contract.stakedId()).thenReturn(new OneOf<>(StakedIdOneOfType.STAKED_ACCOUNT_ID, null)); when(context.expiryValidator()).thenReturn(expiryValidator); when(context.serviceApi(TokenServiceApi.class)).thenReturn(tokenServiceApi); given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java index e47f27bbb09f..b4d66ac8093a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoCreateHandler.java @@ -47,6 +47,8 @@ import static com.hedera.node.app.service.token.AliasUtils.isEntityNumAlias; import static com.hedera.node.app.service.token.AliasUtils.isKeyAlias; import static com.hedera.node.app.service.token.AliasUtils.isOfEvmAddressSize; +import static com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities.NOT_REWARDED_SINCE_LAST_STAKING_META_CHANGE; +import static com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities.NO_STAKE_PERIOD_START; import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable; import static com.hedera.node.app.spi.key.KeyUtils.isEmpty; import static com.hedera.node.app.spi.key.KeyUtils.isValid; @@ -391,6 +393,8 @@ private Account buildAccount(CryptoCreateTransactionBody op, HandleContext handl .tinybarBalance(op.initialBalance()) .declineReward(op.declineReward()) .key(op.keyOrThrow()) + .stakeAtStartOfLastRewardedPeriod(NOT_REWARDED_SINCE_LAST_STAKING_META_CHANGE) + .stakePeriodStart(NO_STAKE_PERIOD_START) .alias(op.alias()); // We do this separately because we want to let the protobuf object remain UNSET for the staked ID if neither diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java index 482a1a8963e5..80bf89d66f7c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java @@ -33,6 +33,7 @@ import static com.hedera.node.app.hapi.utils.fee.FeeBuilder.getAccountKeyStorageSize; import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; import static com.hedera.node.app.service.token.api.AccountSummariesApi.SENTINEL_ACCOUNT_ID; +import static com.hedera.node.app.service.token.api.AccountSummariesApi.SENTINEL_NODE_ID; import static com.hedera.node.app.spi.validation.ExpiryMeta.NA; import static com.hedera.node.app.spi.workflows.HandleException.validateFalse; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; @@ -202,13 +203,21 @@ private Account.Builder updateBuilder( builder.declineReward(op.declineReward().booleanValue()); } if (op.hasStakedAccountId()) { + // 0.0.0 is used a sentinel value for removing staked account id + // Once https://github.com/hashgraph/pbj/issues/160 this is closed, reset stakedId to UNSET if (SENTINEL_ACCOUNT_ID.equals(op.stakedAccountId())) { builder.stakedAccountId((AccountID) null); } else { builder.stakedAccountId(op.stakedAccountId()); } } else if (op.hasStakedNodeId()) { - builder.stakedNodeId(op.stakedNodeId()); + // -1 is used a sentinel value for removing staked node id + // Once https://github.com/hashgraph/pbj/issues/160 this is closed, reset stakedId to UNSET + if (SENTINEL_NODE_ID == op.stakedNodeId()) { + builder.stakedNodeId(SENTINEL_NODE_ID); + } else { + builder.stakedNodeId(op.stakedNodeId()); + } } return builder; } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java index 0ec72e850f53..f7e545406fd3 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeIdChangeType.java @@ -107,9 +107,8 @@ boolean withdrawsFromNode() { public static StakeIdChangeType forCase( @Nullable final Account currentAccount, @NonNull final Account modifiedAccount) { - final var curStakedIdCase = - currentAccount == null ? UNSET : currentAccount.stakedId().kind(); - final var newStakedIdCase = modifiedAccount.stakedId().kind(); + final var curStakedIdCase = currentAccount == null ? UNSET : getCurrentStakedIdCase(currentAccount); + final var newStakedIdCase = getCurrentStakedIdCase(modifiedAccount); // Ends with staking to a node if (newStakedIdCase.equals(STAKED_NODE_ID)) { @@ -142,6 +141,26 @@ public static StakeIdChangeType forCase( } } + /** + * Returns the current stakedId case for the given account. + * A temporary measure until PBJ supports setting UNSET values for OneOfTypes. + * When we use sentinel value to reset the stakedAccountId or stakedNodeId, + * we currently don't get UnSET for stakedId().kind() and this causes issues when determining the case. + * This method will be removed once PBJ supports setting UNSET values for OneOfTypes(https://github.com/hashgraph/pbj/issues/160). + * @param account the account to check + * @return the current stakedId case for the given account + */ + private static Account.StakedIdOneOfType getCurrentStakedIdCase(final Account account) { + final var kind = account.stakedId().kind(); + if (kind.equals(STAKED_NODE_ID)) { + return account.stakedNodeIdOrElse(-1L) == -1L ? UNSET : STAKED_NODE_ID; + } else if (kind.equals(STAKED_ACCOUNT_ID)) { + return account.stakedAccountId() == null ? UNSET : STAKED_ACCOUNT_ID; + } else { + return UNSET; + } + } + boolean withdrawsFromNode() { return false; } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java index 24e47c06159a..5a6b6cb548ce 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeRewardCalculatorImpl.java @@ -27,9 +27,12 @@ import java.time.Instant; import javax.inject.Inject; import javax.inject.Singleton; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; @Singleton public class StakeRewardCalculatorImpl implements StakeRewardCalculator { + private static final Logger logger = LogManager.getLogger(StakeRewardCalculatorImpl.class); private final StakePeriodManager stakePeriodManager; @Inject diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java index 7ba8cd1d6e83..d2afd718a922 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsDistributor.java @@ -70,7 +70,7 @@ public Map payRewardsIfPending( originalAccount, stakingInfoStore, stakingRewardsStore, consensusNow); var receiverId = receiver; - var beneficiary = modifiedAccount; + var beneficiary = originalAccount; // Only if reward is greater than zero do all operations below. // But, even if reward is zero add it to the rewardsPaid map, if the account is not declining reward. // It is important to know that if the reward is zero because of its zero stake in last period. @@ -97,12 +97,18 @@ public Map payRewardsIfPending( } while (beneficiary.deleted()); } } - - if (!beneficiary.declineReward() && reward > 0) { - // even if reward is zero it will be added to rewardsPaid - applyReward(reward, beneficiary, writableStore); - rewardsPaid.merge(receiverId, reward, Long::sum); + // Even if the account has declineReward set or if reward is 0, it should still + // be added to the rewardsPaid map because ONLY this map's keys have their + // stakeAtStartOfLastRewardedPeriod value updated; and for some reward=0 + // situations it is critical to update stakeAtStartOfLastRewardedPeriod + final var mutableBeneficiary = writableStore.get(beneficiary.accountId()); + // If the beneficiary has declineReward set, then the reward is zero + final var finalReward = beneficiary.declineReward() ? 0 : reward; + // even if reward is zero it will be added to rewardsPaid + if (finalReward > 0) { + applyReward(finalReward, mutableBeneficiary, writableStore); } + rewardsPaid.merge(receiverId, finalReward, Long::sum); } return rewardsPaid; } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java index 4368f4ce92fa..7d4a545dbb29 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHandlerImpl.java @@ -20,7 +20,7 @@ import static com.hedera.node.app.service.token.api.AccountSummariesApi.SENTINEL_NODE_ID; import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; import static com.hedera.node.app.service.token.impl.handlers.staking.StakeIdChangeType.FROM_ACCOUNT_TO_ACCOUNT; -import static com.hedera.node.app.service.token.impl.handlers.staking.StakingRewardsHelper.getPossibleRewardReceivers; +import static com.hedera.node.app.service.token.impl.handlers.staking.StakingRewardsHelper.getAllRewardReceivers; import static com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities.hasStakeMetaChanges; import static com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities.roundedToHbar; import static com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities.totalStake; @@ -39,8 +39,11 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; import org.apache.logging.log4j.LogManager; @@ -72,18 +75,22 @@ public Map applyStakingRewards(final FinalizeContext context) { final var accountsConfig = context.configuration().getConfigData(AccountsConfig.class); final var stakingRewardAccountId = asAccount(accountsConfig.stakingRewardAccount()); final var consensusNow = context.consensusTime(); - - // Apply all changes related to stakedId changes, and adjust stakedToMe - // for all accounts staking to an account - adjustStakedToMeForAccountStakees(writableStore); - // Get list of possible reward receivers and pay rewards to them - final var rewardReceivers = getPossibleRewardReceivers(writableStore); + // When an account StakedIdType is FROM_ACCOUNT or TO_ACCOUNT, we need to assess if the staked accountId + // could be in a reward situation. So add those staked accountIds to the list of possible reward receivers + final var specialRewardReceivers = getStakedToMeRewardReceivers(writableStore); + // In addition to the above set, iterate through all modifications in state and + // get list of possible reward receivers which are staked to node + final var rewardReceivers = getAllRewardReceivers(writableStore, specialRewardReceivers); // Pay rewards to all possible reward receivers, returns all rewards paid final var recordBuilder = context.userTransactionRecordBuilder(DeleteCapableTransactionRecordBuilder.class); final var rewardsPaid = rewardsPayer.payRewardsIfPending( rewardReceivers, writableStore, stakingRewardsStore, stakingInfoStore, consensusNow, recordBuilder); - // Adjust stakes for nodes - adjustStakeMetadata(writableStore, stakingInfoStore, stakingRewardsStore, consensusNow, rewardsPaid); + // Apply all changes related to stakedId changes, and adjust stakedToMe + // for all accounts staking to an account + adjustStakedToMeForAccountStakees(writableStore); + // Adjust stakes for nodes and account's staking metadata + adjustStakeMetadata( + writableStore, stakingInfoStore, stakingRewardsStore, consensusNow, rewardsPaid, rewardReceivers); // Decrease staking reward account balance by rewardPaid amount decreaseStakeRewardAccountBalance(rewardsPaid, stakingRewardAccountId, writableStore); return rewardsPaid; @@ -104,7 +111,6 @@ public void adjustStakedToMeForAccountStakees(@NonNull final WritableAccountStor // create a new ArrayList to iterate through just the accounts modified by the initial // transaction final var modifiedAccounts = new ArrayList<>(writableStore.modifiedAccountsInState()); - for (final var id : modifiedAccounts) { final var originalAccount = writableStore.getOriginalValue(id); final var modifiedAccount = writableStore.get(id); @@ -122,22 +128,18 @@ public void adjustStakedToMeForAccountStakees(@NonNull final WritableAccountStor final var delta = roundedFinalBalance - roundedInitialBalance; // Even if the stakee's total stake hasn't changed, we still want to // trigger a reward situation whenever the staker balance changes - if (roundedFinalBalance != roundedInitialBalance) { + if (modifiedAccount.tinybarBalance() != originalAccount.tinybarBalance()) { updateStakedToMeFor(modifiedAccount.stakedAccountId(), delta, writableStore); } } else { if (scenario.withdrawsFromAccount()) { final var curStakedAccountId = originalAccount.stakedAccountId(); final var roundedInitialBalance = roundedToHbar(originalAccount.tinybarBalance()); - // Always trigger a reward situation for the old stakee when they are - // losing an indirect staker, even if it doesn't change their total stake updateStakedToMeFor(curStakedAccountId, -roundedInitialBalance, writableStore); } if (scenario.awardsToAccount()) { final var newStakedAccountId = modifiedAccount.stakedAccountId(); - final var balance = originalAccount == null ? 0 : originalAccount.tinybarBalance(); - // Always trigger a reward situation for the new stakee when they are - // gaining an indirect staker, even if it doesn't change their total stake + final var balance = modifiedAccount.tinybarBalance(); final var roundedFinalBalance = roundedToHbar(balance); updateStakedToMeFor(newStakedAccountId, roundedFinalBalance, writableStore); } @@ -145,25 +147,110 @@ public void adjustStakedToMeForAccountStakees(@NonNull final WritableAccountStor } } + /** + * Gets the special reward receivers. If an account is staked to an account, the stakedAccountId should be added + * to assess if that account is staked to a node, to trigger rewards. Even if the tinybarBalance of the account + * doesn't change in the transaction, we still want to check if it is a reward situation for the staked account. + * @param writableStore The store to write to for updated values + * @return the special reward receivers + */ + public Set getStakedToMeRewardReceivers(@NonNull final WritableAccountStore writableStore) { + // If there is a FROM_ACCOUNT_ or _TO_ACCOUNT stake change scenario, the set of modified + // accounts in the writable store can change inside the body of the for loop below; so we + // create a new ArrayList to iterate through just the accounts modified by the initial + // transaction + final var modifiedAccounts = new ArrayList<>(writableStore.modifiedAccountsInState()); + Set specialRewardReceivers = null; + for (final var id : modifiedAccounts) { + final var originalAccount = writableStore.getOriginalValue(id); + final var modifiedAccount = writableStore.get(id); + + // check if stakedId has changed + final var scenario = StakeIdChangeType.forCase(originalAccount, modifiedAccount); + + // If the stakedId is changed from account or to account. Then we need to update the + // stakedToMe balance of new account. This is needed in order to trigger next level rewards + // if the account is staked to node + if (scenario.equals(FROM_ACCOUNT_TO_ACCOUNT) + && originalAccount.stakedAccountId().equals(modifiedAccount.stakedAccountId())) { + // Even if the stakee's total stake hasn't changed, we still want to + // trigger a reward situation whenever the staker balance changes + if (modifiedAccount.tinybarBalance() != originalAccount.tinybarBalance()) { + specialRewardReceivers = + updateSpecialRewardReceivers(specialRewardReceivers, modifiedAccount, writableStore); + } + } else { + // When withdrawing from account stakedId, we are interested to assess the original account + // that has stakedAccountId + if (scenario.withdrawsFromAccount()) { + specialRewardReceivers = + updateSpecialRewardReceivers(specialRewardReceivers, originalAccount, writableStore); + } + // When adding from stakedAccountId to an account, we are interested to assess the modified account + // that has new stakedAccountId + if (scenario.awardsToAccount()) { + specialRewardReceivers = + updateSpecialRewardReceivers(specialRewardReceivers, modifiedAccount, writableStore); + } + } + } + return specialRewardReceivers == null ? Collections.emptySet() : specialRewardReceivers; + } + + /** + * Updates specialRewardReceivers set with the stakedAccountId of the account, when the stakedAccountId is + * staked to a node. + * @param specialRewardReceivers the set of special reward receivers + * @param account the account to check + * @param accountStore the account store + * @return the updated special reward receivers + */ + @NonNull + private Set updateSpecialRewardReceivers( + @Nullable Set specialRewardReceivers, + @NonNull final Account account, + @NonNull final WritableAccountStore accountStore) { + if (specialRewardReceivers == null) { + // Always trigger a reward situation for the new stakee when they are + // gaining an indirect staker, even if it doesn't change their total stake + specialRewardReceivers = new LinkedHashSet<>(); + } + final var stakedAccountId = account.stakedAccountId(); + final var stakedAccount = accountStore.getOriginalValue(stakedAccountId); + // if the special reward receiver account is not staked to a node, it will not need to receive reward + if (stakedAccount != null && stakedAccount.hasStakedNodeId()) { + specialRewardReceivers.add(stakedAccountId); + } + return specialRewardReceivers; + } + /** * If the account is updated to be staking to a node or withdraws staking from node, adjusts the stakes for those * nodes. It also updates stakeAtStartOfLastRewardedPeriod and stakePeriodStart for accounts. * - * @param writableStore writable account store - * @param stakingInfoStore writable staking info store + * @param writableStore writable account store + * @param stakingInfoStore writable staking info store * @param stakingRewardStore writable staking reward store - * @param consensusNow consensus time - * @param paidRewards map of account to rewards paid + * @param consensusNow consensus time + * @param paidRewards map of account to rewards paid + * @param rewardReceivers set of reward receivers */ private void adjustStakeMetadata( final WritableAccountStore writableStore, final WritableStakingInfoStore stakingInfoStore, final WritableNetworkStakingRewardsStore stakingRewardStore, final Instant consensusNow, - final Map paidRewards) { - for (final var id : writableStore.modifiedAccountsInState()) { + final Map paidRewards, + final Set rewardReceivers) { + // We need to assess all the accounts modified in state and also possible rewardReceivers + Set accountsToBeReviewed = writableStore.modifiedAccountsInState(); + if (!writableStore.modifiedAccountsInState().containsAll(rewardReceivers)) { + accountsToBeReviewed = new LinkedHashSet<>(writableStore.modifiedAccountsInState()); + accountsToBeReviewed.addAll(rewardReceivers); + } + for (final var id : accountsToBeReviewed) { final var originalAccount = writableStore.getOriginalValue(id); - final var modifiedAccount = writableStore.get(id); + var modifiedAccount = writableStore.get(id); final var scenario = StakeIdChangeType.forCase(originalAccount, modifiedAccount); final var containStakeMetaChanges = hasStakeMetaChanges(originalAccount, modifiedAccount); @@ -181,24 +268,31 @@ private void adjustStakeMetadata( } // If the account is rewarded. The reward can also be zero, if the account has zero stake - final var isRewarded = paidRewards.containsKey(id); + final var rewardSituation = paidRewards.containsKey(id); final var reward = paidRewards.getOrDefault(id, 0L); // If account chose to change decline reward field or stakeId field, we don't need // to update stakeAtStartOfLastRewardedPeriod because it is not rewarded for that period // Check if the stakeAtStartOfLastRewardedPeriod needs to be updated // If the account is autoCreated containStakeMetaChanges will not be true - if (!containStakeMetaChanges - && shouldUpdateStakeStart(originalAccount, isRewarded, reward, stakingRewardStore, consensusNow)) { + if (containStakeMetaChanges) { + // If there are any stake metadata changes, we need to reset stakeAtStartOfLastRewardedPeriod + final var copy = modifiedAccount.copyBuilder(); + copy.stakeAtStartOfLastRewardedPeriod(NOT_REWARDED_SINCE_LAST_STAKING_META_CHANGE); + writableStore.put(copy.build()); + } else if (shouldUpdateStakeAtStartOfLastRewardPeriod( + originalAccount, rewardSituation, reward, stakingRewardStore, consensusNow)) { final var copy = modifiedAccount.copyBuilder(); copy.stakeAtStartOfLastRewardedPeriod(roundedToHbar(totalStake(originalAccount))); writableStore.put(copy.build()); } - // Update stakePeriodStart if account is rewarded or if reward is zero and account - // has zero stake + // Get latest account from store again since it is modified before + modifiedAccount = writableStore.get(id); + + // Update stakePeriodStart if account is rewarded or if reward is zero and account has zero stake // If the account is autoCreated it will not be rewarded - final var wasRewarded = isRewarded + final var wasRewarded = rewardSituation && (reward > 0 || (reward == 0 && earnedZeroRewardsBecauseOfZeroStake( @@ -273,13 +367,24 @@ private void adjustNodeStakes( } } - public boolean shouldUpdateStakeStart( + /** + * List of condition sto validate if the account need to update stakeAtStartOfLastRewardedPeriod. + * @param account the account + * @param isRewarded if the account is rewarded + * @param reward the reward amount + * @param stakingRewardStore the staking reward store + * @param consensusNow the consensus time + * @return true if the account need to update stakeAtStartOfLastRewardedPeriod, false otherwise + */ + public boolean shouldUpdateStakeAtStartOfLastRewardPeriod( @Nullable final Account account, final boolean isRewarded, final long reward, @NonNull final ReadableNetworkStakingRewardsStore stakingRewardStore, @NonNull final Instant consensusNow) { - if (account == null || account.hasStakedAccountId() || account.declineReward()) { + if (account == null + || account.stakedNodeIdOrElse(SENTINEL_NODE_ID) == SENTINEL_NODE_ID + || account.declineReward()) { // If the account is created in this transaction, or it is not staking to a node, // or it has chosen to decline reward, we don't need to remember stakeStart, // because it can't receive reward today @@ -379,7 +484,7 @@ private void updateStakedToMeFor( final var initialStakedToMe = account.stakedToMe(); final var finalStakedToMe = initialStakedToMe + roundedFinalBalance; if (finalStakedToMe < 0) { - log.warn("StakedToMe for account {} is negative after reward distribution, set it to 0", stakee); + log.error("StakedToMe for account {} is negative after reward distribution, set it to 0", stakee); } final var copy = account.copyBuilder() .stakedToMe(finalStakedToMe < 0 ? 0 : finalStakedToMe) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java index 7e95b2d8dc19..3598e4bc8623 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingRewardsHelper.java @@ -17,7 +17,9 @@ package com.hedera.node.app.service.token.impl.handlers.staking; import static com.hedera.node.app.service.mono.utils.Units.HBARS_TO_TINYBARS; +import static com.hedera.node.app.service.token.api.AccountSummariesApi.SENTINEL_NODE_ID; import static com.hedera.node.app.service.token.impl.comparator.TokenComparators.ACCOUNT_AMOUNT_COMPARATOR; +import static com.hedera.node.app.service.token.impl.handlers.staking.StakingUtilities.hasStakeMetaChanges; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountAmount; @@ -28,7 +30,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -53,11 +55,14 @@ public StakingRewardsHelper() { /** * Looks through all the accounts modified in state and returns a list of accounts which are staked to a node * and has stakedId or stakedToMe or balance or declineReward changed in this transaction. - * @param writableAccountStore The store to write to for updated values and original values + * + * @param writableAccountStore The store to write to for updated values and original values + * @param specialRewardReceivers The accounts which are staked to a node and are special reward receivers * @return A list of accounts which are staked to a node and could possibly receive a reward */ - public static Set getPossibleRewardReceivers(final WritableAccountStore writableAccountStore) { - final var possibleRewardReceivers = new HashSet(); + public static Set getAllRewardReceivers( + final WritableAccountStore writableAccountStore, final Set specialRewardReceivers) { + final var possibleRewardReceivers = new LinkedHashSet<>(specialRewardReceivers); for (final AccountID id : writableAccountStore.modifiedAccountsInState()) { final var modifiedAcct = writableAccountStore.get(id); final var originalAcct = writableAccountStore.getOriginalValue(id); @@ -84,18 +89,17 @@ public static Set getPossibleRewardReceivers(final WritableAccountSto private static boolean isRewardSituation( @NonNull final Account modifiedAccount, @Nullable final Account originalAccount) { requireNonNull(modifiedAccount); - if (originalAccount == null) { + if (originalAccount == null || originalAccount.stakedNodeIdOrElse(SENTINEL_NODE_ID) == SENTINEL_NODE_ID) { return false; } - final var hasStakedToMeUpdate = modifiedAccount.stakedToMe() != originalAccount.stakedToMe(); + + // No need to check for stakeMetaChanges again here, since they are captured in possibleRewardReceivers + // in previous step final var hasBalanceChange = modifiedAccount.tinybarBalance() != originalAccount.tinybarBalance(); - final var hasStakeMetaChanges = (modifiedAccount.declineReward() != originalAccount.declineReward()) - || (modifiedAccount.stakedId() != originalAccount.stakedId()); + final var hasStakeMetaChanges = hasStakeMetaChanges(originalAccount, modifiedAccount); // We do this for backward compatibility with mono-service - // TODO: Also check that the HAPI operation was a ContractCreate/ContractCall/EthereumTransaction final var isCalledContract = modifiedAccount.smartContract(); - final var isStakedToNode = originalAccount.stakedNodeIdOrElse(-1L) >= 0L; - return isStakedToNode && (isCalledContract || hasStakedToMeUpdate || hasBalanceChange || hasStakeMetaChanges); + return (isCalledContract || hasBalanceChange || hasStakeMetaChanges); } /** diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java index 1108f500369a..b261125c5342 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakingUtilities.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.token.impl.handlers.staking; import static com.hedera.node.app.service.mono.utils.Units.HBARS_TO_TINYBARS; +import static com.hedera.node.app.service.token.api.AccountSummariesApi.SENTINEL_NODE_ID; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.state.token.Account; @@ -29,6 +30,7 @@ private StakingUtilities() { } public static final long NOT_REWARDED_SINCE_LAST_STAKING_META_CHANGE = -1; + public static final long NO_STAKE_PERIOD_START = -1; public static long roundedToHbar(final long value) { return (value / HBARS_TO_TINYBARS) * HBARS_TO_TINYBARS; @@ -52,8 +54,9 @@ public static boolean hasStakeMetaChanges( || modifiedAccount.declineReward(); } final var differDeclineReward = originalAccount.declineReward() != modifiedAccount.declineReward(); - final var differStakedNodeId = - !originalAccount.stakedNodeIdOrElse(0L).equals(modifiedAccount.stakedNodeIdOrElse(0L)); + final var differStakedNodeId = !originalAccount + .stakedNodeIdOrElse(SENTINEL_NODE_ID) + .equals(modifiedAccount.stakedNodeIdOrElse(SENTINEL_NODE_ID)); final var differStakeAccountId = !originalAccount .stakedAccountIdOrElse(AccountID.DEFAULT) .equals(modifiedAccount.stakedAccountIdOrElse(AccountID.DEFAULT)); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/TokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/TokenSchema.java index 3d9bde8010db..065adcdaab1a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/TokenSchema.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/TokenSchema.java @@ -265,28 +265,31 @@ public static long[] nonContractSystemNums(final long numReservedSystemEntities) } private void initializeStakingNodeInfo(@NonNull final MigrationContext ctx) { - // TODO: This need to go through address book and set all the nodes final var config = ctx.configuration(); final var ledgerConfig = config.getConfigData(LedgerConfig.class); final var stakingConfig = config.getConfigData(StakingConfig.class); - final var numberOfNodes = 1; + final var addressBook = ctx.networkInfo().addressBook(); + final var numberOfNodes = addressBook.size(); final long maxStakePerNode = ledgerConfig.totalTinyBarFloat() / numberOfNodes; final long minStakePerNode = maxStakePerNode / 2; final var numRewardHistoryStoredPeriods = stakingConfig.rewardHistoryNumStoredPeriods(); final var stakingInfoState = ctx.newStates().get(STAKING_INFO_KEY); - final var rewardSumHistory = new Long[numRewardHistoryStoredPeriods]; + final var rewardSumHistory = new Long[numRewardHistoryStoredPeriods + 1]; Arrays.fill(rewardSumHistory, 0L); - final var stakingInfo = StakingNodeInfo.newBuilder() - .nodeNumber(0) - .maxStake(maxStakePerNode) - .minStake(minStakePerNode) - .rewardSumHistory(Arrays.asList(rewardSumHistory)) - .weight(500) - .build(); - stakingInfoState.put(EntityNumber.newBuilder().number(0L).build(), stakingInfo); + for (final var node : addressBook) { + final var nodeNumber = node.nodeId(); + final var stakingInfo = StakingNodeInfo.newBuilder() + .nodeNumber(nodeNumber) + .maxStake(maxStakePerNode) + .minStake(minStakePerNode) + .rewardSumHistory(Arrays.asList(rewardSumHistory)) + .weight(500) + .build(); + stakingInfoState.put(EntityNumber.newBuilder().number(nodeNumber).build(), stakingInfo); + } } private void initializeNetworkRewards(@NonNull final MigrationContext ctx) { diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java index 877ee9a1233f..e9fb26957c52 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/validators/StakingValidator.java @@ -35,7 +35,6 @@ */ @Singleton public class StakingValidator { - @Inject public StakingValidator() { // Dagger2 diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java index efa6d0331e90..4a0d55d0e1e7 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoCreateHandlerTest.java @@ -291,7 +291,7 @@ void handleCryptoCreateVanilla() { assertEquals("Create Account", createdAccount.memo()); assertFalse(createdAccount.deleted()); assertEquals(0L, createdAccount.stakedToMe()); - assertEquals(0L, createdAccount.stakePeriodStart()); + assertEquals(-1L, createdAccount.stakePeriodStart()); // staked node id is stored in state as negative long assertEquals(3, createdAccount.stakedAccountId().accountNum()); assertFalse(createdAccount.declineReward()); @@ -306,7 +306,7 @@ void handleCryptoCreateVanilla() { assertFalse(createdAccount.smartContract()); assertEquals(0, createdAccount.numberPositiveBalances()); assertEquals(0L, createdAccount.ethereumNonce()); - assertEquals(0L, createdAccount.stakeAtStartOfLastRewardedPeriod()); + assertEquals(-1L, createdAccount.stakeAtStartOfLastRewardedPeriod()); assertNull(createdAccount.autoRenewAccountId()); assertEquals(defaultAutoRenewPeriod, createdAccount.autoRenewSeconds()); assertEquals(0, createdAccount.contractKvPairsNumber()); @@ -362,7 +362,7 @@ void handleCryptoCreateVanillaWithStakedAccountId() { assertEquals("Create Account", createdAccount.memo()); assertFalse(createdAccount.deleted()); assertEquals(0L, createdAccount.stakedToMe()); - assertEquals(0L, createdAccount.stakePeriodStart()); + assertEquals(-1L, createdAccount.stakePeriodStart()); // staked node id is stored in state as negative long assertEquals(3, createdAccount.stakedAccountId().accountNum()); assertFalse(createdAccount.declineReward()); @@ -377,7 +377,7 @@ void handleCryptoCreateVanillaWithStakedAccountId() { assertFalse(createdAccount.smartContract()); assertEquals(0, createdAccount.numberPositiveBalances()); assertEquals(0L, createdAccount.ethereumNonce()); - assertEquals(0L, createdAccount.stakeAtStartOfLastRewardedPeriod()); + assertEquals(-1L, createdAccount.stakeAtStartOfLastRewardedPeriod()); assertNull(createdAccount.autoRenewAccountId()); assertEquals(defaultAutoRenewPeriod, createdAccount.autoRenewSeconds()); assertEquals(0, createdAccount.contractKvPairsNumber()); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java index f37c0bdd5e43..c9be3b53b1cf 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakingRewardsHandlerImplTest.java @@ -116,14 +116,15 @@ void changingKeyOnlyIsNotRewardSituation() { void rewardsWhenStakingFieldsModified() { final var stakedToMeBefore = account.stakedToMe(); final var stakePeriodStartBefore = account.stakePeriodStart(); - final var stakeAtStartOfLastRewardedPeriodBefore = account.stakeAtStartOfLastRewardedPeriod(); + final var balanceBefore = account.tinybarBalance(); randomStakeNodeChanges(); final var rewards = subject.applyStakingRewards(context); // earned zero rewards due to zero stake - assertThat(rewards).hasSize(0); + assertThat(rewards).hasSize(1); + assertThat(rewards).containsEntry(payerId, 0L); final var modifiedAccount = writableAccountStore.get(payerId); // stakedToMe will not change as this is not staked by another account @@ -135,7 +136,8 @@ void rewardsWhenStakingFieldsModified() { final var expectedStakePeriodStart = stakePeriodManager.currentStakePeriod(consensusInstant); assertThat(stakedToMeAfter).isEqualTo(stakedToMeBefore); assertThat(stakePeriodStartAfter).isNotEqualTo(stakePeriodStartBefore).isEqualTo(expectedStakePeriodStart); - assertThat(stakeAtStartOfLastRewardedPeriodAfter).isEqualTo(stakeAtStartOfLastRewardedPeriodBefore); + // staking metadata is updated, so stakeAtStartOfLastRewardedPeriod will be set to -1 + assertThat(stakeAtStartOfLastRewardedPeriodAfter).isEqualTo(-1); } @Test @@ -316,7 +318,8 @@ void earningZeroRewardsWithStartBeforeLastNonRewardableStillUpdatesSASOLARP() { final StakingRewardsHandlerImpl impl = new StakingRewardsHandlerImpl(rewardsPayer, manager, stakeInfoHelper); - assertThat(impl.shouldUpdateStakeStart(account, true, 0L, readableRewardsStore, consensusInstant)) + assertThat(impl.shouldUpdateStakeAtStartOfLastRewardPeriod( + account, true, 0L, readableRewardsStore, consensusInstant)) .isTrue(); } @@ -538,7 +541,8 @@ void stakingEffectsWorkAsExpectedWhenStakingToNodeWithNoStakingMetaChangesAndNoR final var node1InfoAfter = writableStakingInfoState.get(node1Id); // No rewards rewarded - assertThat(rewards).hasSize(0); + assertThat(rewards).hasSize(1); + assertThat(rewards).containsEntry(payerId, 0L); assertThat(node1InfoAfter.stake()).isEqualTo(node1InfoBefore.stake()); assertThat(node1InfoAfter.unclaimedStakeRewardStart()).isEqualTo(node1InfoBefore.unclaimedStakeRewardStart()); @@ -551,10 +555,11 @@ void stakingEffectsWorkAsExpectedWhenStakingToNodeWithNoStakingMetaChangesAndNoR @Test void sasolarpMgmtWorksAsExpectedWhenStakingToNodeWithNoStakingMetaChangesAndNoReward() { - final var accountBalance = 55L * HBARS_TO_TINYBARS; + final var payerInitialBalance = 55L * HBARS_TO_TINYBARS; + final var payerAfterBalance = 54L * HBARS_TO_TINYBARS; final var payerAccountBefore = new AccountCustomizer() .withAccount(account) - .withBalance(accountBalance) + .withBalance(payerInitialBalance) .withStakeAtStartOfLastRewardPeriod(-1L) .withStakePeriodStart(stakePeriodStart) .withDeclineReward(false) @@ -568,7 +573,7 @@ void sasolarpMgmtWorksAsExpectedWhenStakingToNodeWithNoStakingMetaChangesAndNoRe final var node1InfoBefore = writableStakingInfoState.get(node1Id); writableAccountStore.put(payerAccountBefore .copyBuilder() - .tinybarBalance(accountBalance - HBARS_TO_TINYBARS) + .tinybarBalance(payerAfterBalance) .build()); given(context.consensusTime()) @@ -582,25 +587,27 @@ void sasolarpMgmtWorksAsExpectedWhenStakingToNodeWithNoStakingMetaChangesAndNoRe final var node1InfoAfter = writableStakingInfoState.get(node1Id); // Since it has not declined rewards and has zero stake, no rewards rewarded - assertThat(rewards).hasSize(0); + assertThat(rewards).hasSize(1); + assertThat(rewards).containsEntry(payerId, 0L); assertThat(node1InfoAfter.stake()).isEqualTo(node1InfoBefore.stake()); assertThat(node1InfoAfter.unclaimedStakeRewardStart()).isEqualTo(node1InfoBefore.unclaimedStakeRewardStart()); assertThat(node1Info.unclaimedStakeRewardStart()).isZero(); final var modifiedAccount = writableAccountStore.get(payerId); - assertThat(modifiedAccount.tinybarBalance()).isEqualTo(accountBalance - HBARS_TO_TINYBARS); + assertThat(modifiedAccount.tinybarBalance()).isEqualTo(payerInitialBalance - HBARS_TO_TINYBARS); assertThat(modifiedAccount.stakePeriodStart()).isEqualTo(stakePeriodStart); - assertThat(modifiedAccount.stakeAtStartOfLastRewardedPeriod()).isEqualTo(-1); + assertThat(modifiedAccount.stakeAtStartOfLastRewardedPeriod()).isEqualTo(payerInitialBalance); } @Test void stakingEffectsWorkAsExpectedWhenStakingToAccount() { - final var accountBalance = 55L * HBARS_TO_TINYBARS; - final var ownerBalance = 11L * HBARS_TO_TINYBARS; + final var payerInitialBalance = 55L * HBARS_TO_TINYBARS; + final var ownerInitialBalance = 11L * HBARS_TO_TINYBARS; + final var payerLaterBalance = 54L * HBARS_TO_TINYBARS; final var payerAccountBefore = new AccountCustomizer() .withAccount(account) - .withBalance(accountBalance) + .withBalance(payerInitialBalance) .withStakeAtStartOfLastRewardPeriod(-1L) .withStakePeriodStart(stakePeriodStart) .withDeclineReward(false) @@ -609,7 +616,7 @@ void stakingEffectsWorkAsExpectedWhenStakingToAccount() { .build(); final var ownerAccountBefore = new AccountCustomizer() .withAccount(ownerAccount) - .withBalance(ownerBalance) + .withBalance(ownerInitialBalance) .withStakeAtStartOfLastRewardPeriod(-1L) .withStakePeriodStart(stakePeriodStart) .withDeclineReward(false) @@ -622,7 +629,7 @@ void stakingEffectsWorkAsExpectedWhenStakingToAccount() { writableAccountStore.put(payerAccountBefore .copyBuilder() - .tinybarBalance(accountBalance - HBARS_TO_TINYBARS) + .tinybarBalance(payerLaterBalance) .stakedAccountId(ownerId) .build()); @@ -637,14 +644,14 @@ void stakingEffectsWorkAsExpectedWhenStakingToAccount() { assertThat(rewards).hasSize(1).containsEntry(payerId, 5500L); - assertThat(node1InfoAfter.stakeToReward()).isEqualTo(node1InfoBefore.stakeToReward() - accountBalance); - assertThat(node1InfoAfter.unclaimedStakeRewardStart()).isEqualTo(accountBalance); + assertThat(node1InfoAfter.stakeToReward()).isEqualTo(node1InfoBefore.stakeToReward() - payerInitialBalance); + assertThat(node1InfoAfter.unclaimedStakeRewardStart()).isEqualTo(payerInitialBalance); // stake field of the account is updated once a day final var modifiedOwner = writableAccountStore.get(ownerId); final var modifiedPayer = writableAccountStore.get(payerId); - assertThat(modifiedOwner.stakedToMe()).isEqualTo(accountBalance); + assertThat(modifiedOwner.stakedToMe()).isEqualTo(payerLaterBalance); // stakePeriodStart is updated only when reward is applied assertThat(modifiedOwner.stakePeriodStart()).isEqualTo(stakePeriodStart); @@ -704,11 +711,13 @@ void rewardsUltimateBeneficiaryInsteadOfDeletedAccount() { @Test void doesntTrackAnythingIfRedirectBeneficiaryDeclinedReward() { - final var accountBalance = 555L * HBARS_TO_TINYBARS; - final var ownerBalance = 111L * HBARS_TO_TINYBARS; + final var payerInitialBalance = 555L * HBARS_TO_TINYBARS; + final var ownerInitialBalance = 111L * HBARS_TO_TINYBARS; + final var ownerAfterBalance = ownerInitialBalance + payerInitialBalance; final var payerAccountBefore = new AccountCustomizer() .withAccount(account) - .withBalance(accountBalance) + .withBalance(payerInitialBalance) + .withStakedToMe(0L) .withStakeAtStartOfLastRewardPeriod(-1L) .withStakePeriodStart(stakePeriodStart) .withDeclineReward(false) @@ -716,7 +725,8 @@ void doesntTrackAnythingIfRedirectBeneficiaryDeclinedReward() { .build(); final var ownerAccountBefore = new AccountCustomizer() .withAccount(ownerAccount) - .withBalance(ownerBalance) + .withStakedToMe(0L) + .withBalance(ownerInitialBalance) .withStakeAtStartOfLastRewardPeriod(-1L) .withStakePeriodStart(stakePeriodStart) .withDeclineReward(true) @@ -731,7 +741,7 @@ void doesntTrackAnythingIfRedirectBeneficiaryDeclinedReward() { .build()); writableAccountStore.put(ownerAccountBefore .copyBuilder() - .tinybarBalance(ownerBalance + accountBalance) + .tinybarBalance(ownerAfterBalance) .stakedNodeId(0L) .build()); @@ -747,7 +757,7 @@ void doesntTrackAnythingIfRedirectBeneficiaryDeclinedReward() { final var rewards = subject.applyStakingRewards(context); // because the transferId is owner and it declined reward - assertThat(rewards).hasSize(0); + assertThat(rewards).hasSize(1); } @Test diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java index b8aeb52cfca1..95ff6fe95c8d 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/AccountSummariesApi.java @@ -57,7 +57,7 @@ */ public interface AccountSummariesApi { int EVM_ADDRESS_SIZE = 20; - int SENTINEL_NODE_ID = -1; + long SENTINEL_NODE_ID = -1L; AccountID SENTINEL_ACCOUNT_ID = AccountID.newBuilder().accountNum(0).build(); /** diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java index 56dbf15caf03..df37e83b6603 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/StakingRewardsApi.java @@ -30,11 +30,14 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Methods for computing an account's pending staking rewards. */ public interface StakingRewardsApi { + Logger log = LogManager.getLogger(StakingRewardsApi.class); int MINUTES_TO_SECONDS = 60; long MINUTES_TO_MILLISECONDS = 60_000L; long DAILY_STAKING_PERIOD_MINS = 1440L; @@ -56,6 +59,7 @@ static long computeRewardFromDetails( final long currentStakePeriod, final long stakePeriodStart) { requireNonNull(account); + log.info("NodeStakingInfo", nodeStakingInfo); if (nodeStakingInfo == null) { return 0L; } @@ -88,7 +92,7 @@ static long estimatePendingReward( account, readableStakingInfoStore.get(account.stakedNodeIdOrThrow()), currentStakePeriod, - account.stakePeriodStart()); + clampedStakePeriodStart); } } return 0; diff --git a/hedera-node/hedera-token-service/src/main/java/module-info.java b/hedera-node/hedera-token-service/src/main/java/module-info.java index 933e7f0a75e0..c43dc0820ad5 100644 --- a/hedera-node/hedera-token-service/src/main/java/module-info.java +++ b/hedera-node/hedera-token-service/src/main/java/module-info.java @@ -20,4 +20,5 @@ requires com.hedera.node.app.service.evm; requires com.github.spotbugs.annotations; requires com.swirlds.common; + requires transitive org.apache.logging.log4j; } diff --git a/hedera-node/test-clients/src/itest/java/SequentialSuites.java b/hedera-node/test-clients/src/itest/java/SequentialSuites.java index 20590aa1c15c..e2d7972beb2f 100644 --- a/hedera-node/test-clients/src/itest/java/SequentialSuites.java +++ b/hedera-node/test-clients/src/itest/java/SequentialSuites.java @@ -17,6 +17,7 @@ import com.hedera.services.bdd.suites.HapiSuite; import com.hedera.services.bdd.suites.contract.opcodes.Create2OperationSuite; import com.hedera.services.bdd.suites.contract.traceability.TraceabilitySuite; +import com.hedera.services.bdd.suites.crypto.staking.StakingSuite; import com.hedera.services.bdd.suites.fees.SpecialAccountsAreExempted; import com.hedera.services.bdd.suites.leaky.FeatureFlagSuite; import com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite; @@ -47,6 +48,7 @@ static Supplier[] sequentialSuites() { LeakyCryptoTestsSuite::new, LeakySecurityModelV1Suite::new, Create2OperationSuite::new, + StakingSuite::new }; } } diff --git a/hedera-node/test-clients/src/itest/resources/network/config/application.properties b/hedera-node/test-clients/src/itest/resources/network/config/application.properties index fff60abb65ac..1568789dd56e 100644 --- a/hedera-node/test-clients/src/itest/resources/network/config/application.properties +++ b/hedera-node/test-clients/src/itest/resources/network/config/application.properties @@ -1 +1,4 @@ balances.exportDir.path=network/itest/data/accountBalances/ +# Needed for end-end tests running on mono-service code +staking.fees.nodeRewardPercentage=10 +staking.fees.stakingRewardPercentage=10 diff --git a/hedera-node/test-clients/src/itest/resources/network/config/bootstrap.properties b/hedera-node/test-clients/src/itest/resources/network/config/bootstrap.properties index 17cb2854b1ad..9af0ba0f515c 100644 --- a/hedera-node/test-clients/src/itest/resources/network/config/bootstrap.properties +++ b/hedera-node/test-clients/src/itest/resources/network/config/bootstrap.properties @@ -5,3 +5,7 @@ contracts.sidecars=CONTRACT_BYTECODE,CONTRACT_STATE_CHANGE,CONTRACT_ACTION scheduling.whitelist=ConsensusSubmitMessage,CryptoTransfer,TokenMint,TokenBurn,CryptoApproveAllowance hedera.recordStream.compressFilesOnCreation=true hedera.recordStream.sidecarDir= +# Needed for end-end tests running on mono-service code +staking.periodMins=1 +staking.fees.nodeRewardPercentage=10 +staking.fees.stakingRewardPercentage=10 diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/AccountInfoAsserts.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/AccountInfoAsserts.java index 0587dd8cb2ed..58780592b0c9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/AccountInfoAsserts.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/assertions/AccountInfoAsserts.java @@ -41,11 +41,14 @@ public class AccountInfoAsserts extends BaseErroringAssertsProvider public static final String BAD_ALIAS = "Bad Alias!"; + boolean hasTokenAssociationExpectations = false; + public static AccountInfoAsserts accountWith() { return new AccountInfoAsserts(); } public AccountInfoAsserts noChangesFromSnapshot(final String snapshot) { + hasTokenAssociationExpectations = true; registerProvider((spec, o) -> { final var expected = spec.registry().getAccountInfo(snapshot); final var actual = (AccountInfo) o; @@ -55,6 +58,7 @@ public AccountInfoAsserts noChangesFromSnapshot(final String snapshot) { } public AccountInfoAsserts newAssociationsFromSnapshot(final String snapshot, final List newRels) { + hasTokenAssociationExpectations = true; for (final var newRel : newRels) { registerProvider((spec, o) -> { final var baseline = spec.registry().getAccountInfo(snapshot); @@ -383,4 +387,8 @@ public AccountInfoAsserts ownedNfts(int num) { registerProvider((spec, o) -> assertEquals(num, ((AccountInfo) o).getOwnedNfts(), "Bad ownedNfts!")); return this; } + + public boolean hasTokenAssociationExpectation() { + return hasTokenAssociationExpectations; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java index d2d3b3cb34a3..2e11b63fcaa1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountInfo.java @@ -237,7 +237,7 @@ protected void assertExpectationsGiven(HapiSpec spec) throws Throwable { if (!relationships.isEmpty() || alreadyUsedAutomaticAssociations.isPresent() || !absentRelationships.isEmpty() - || expectations.isPresent() + || (expectations.isPresent() && expectations.get().hasTokenAssociationExpectation()) || registryEntry.isPresent()) { final var detailsLookup = QueryVerbs.getAccountDetails( "0.0." + actualInfo.getAccountID().getAccountNum()) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java index d5d9cdd18b91..c2a6c8f9bf71 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java @@ -99,6 +99,8 @@ public class HapiGetTxnRecord extends HapiQueryOp { private boolean requestDuplicates = false; private boolean requestChildRecords = false; private boolean includeStakingRecordsInCount = true; + // This field should be merged with includeStakingRecordsInCount in the future + private boolean countStakingRecords = false; private boolean shouldBeTransferFree = false; private boolean assertOnlyPriority = false; private boolean assertNothingAboutHashes = false; @@ -249,6 +251,11 @@ public HapiGetTxnRecord omittingAnyPaymentForCostAnswer() { return this; } + public HapiGetTxnRecord countStakingRecords() { + countStakingRecords = true; + return this; + } + public HapiGetTxnRecord scheduledBy(final String creation) { scheduled = true; creationName = Optional.of(creation); @@ -478,18 +485,22 @@ private void assertChildRecords(final HapiSpec spec, final List !isEndOfStakingPeriodRecord(r)) .map(TransactionRecord::toString) .collect(Collectors.joining(", ")); assertEquals( expectedChildRecords.size(), - numActualRecords - numStakingRecords, + numActualRecords - numRecordsToSubtract, "Wrong # of (non-staking) child records, got: " + printableActualRecords); } - for (int i = numStakingRecords; i < numActualRecords; i++) { - final var expectedChildRecord = expectedChildRecords.get(i - numStakingRecords); + for (int i = numRecordsToSubtract; i < numActualRecords; i++) { + final var expectedChildRecord = expectedChildRecords.get(i - numRecordsToSubtract); final var actualChildRecord = actualRecords.get(i); final ErroringAsserts asserts = expectedChildRecord.assertsFor(spec); final List errors = asserts.errorsIn(actualChildRecord); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java index 0360290630e5..a37021e57a82 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/GlobalPropertiesSuite.java @@ -26,9 +26,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; @@ -83,9 +81,8 @@ final HapiSpec chainIdWorks() { final Set acceptableChainIds = Set.of(devChainId, defaultChainId); return defaultHapiSpec("chainIdWorks") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, GET_CHAIN_ID).via("chainId")) .then( getTxnRecord("chainId") @@ -108,9 +105,8 @@ final HapiSpec baseFeeWorks() { final var expectedBaseFee = BigInteger.valueOf(0); return defaultHapiSpec("baseFeeWorks") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, GET_BASE_FEE).via("baseFee")) .then( getTxnRecord("baseFee") @@ -134,9 +130,8 @@ final HapiSpec baseFeeWorks() { final HapiSpec coinbaseWorks() { return defaultHapiSpec("coinbaseWorks") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "getCoinbase").via("coinbase")) .then(withOpContext((spec, opLog) -> { final var expectedCoinbase = @@ -161,9 +156,8 @@ final HapiSpec gasLimitWorks() { final var gasLimit = Long.parseLong(HapiSpecSetup.getDefaultNodeProps().get("contracts.maxGasPerSec")); return defaultHapiSpec("gasLimitWorks") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), + uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, GET_GAS_LIMIT).via("gasLimit").gas(gasLimit)) .then( getTxnRecord("gasLimit") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java index a8af92b79f95..5290bbd710dd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/CreatePrecompileSuite.java @@ -34,11 +34,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; @@ -245,9 +241,9 @@ final HapiSpec tokenCreateWithFixedFeeWithMultiplePaymentsReverts() { final HapiSpec createTokenWithEmptyTokenStruct() { return defaultHapiSpec("createTokenWithEmptyTokenStruct") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), - cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), - uploadInitCode(TOKEN_CREATE_CONTRACT)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), uploadInitCode(TOKEN_CREATE_CONTRACT)) .when(withOpContext((spec, opLog) -> allRunFor(spec, contractCreate(TOKEN_CREATE_CONTRACT).gas(GAS_TO_OFFER)))) .then( @@ -296,10 +292,10 @@ final HapiSpec createTokenWithEmptyTokenStruct() { final HapiSpec createTokenWithInvalidExpiry() { return defaultHapiSpec("createTokenWithInvalidExpiry") .given( - snapshotMode( - FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - NONDETERMINISTIC_TRANSACTION_FEES, - NONDETERMINISTIC_FUNCTION_PARAMETERS), + // snapshotMode( + // FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES, + // NONDETERMINISTIC_FUNCTION_PARAMETERS), newKeyNamed(ECDSA_KEY).shape(SECP256K1), cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), uploadInitCode(TOKEN_CREATE_CONTRACT), @@ -336,10 +332,10 @@ final HapiSpec createTokenWithInvalidExpiry() { final HapiSpec createTokenWithInvalidTreasury() { return defaultHapiSpec("createTokenWithInvalidTreasury") .given( - snapshotMode( - FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - NONDETERMINISTIC_TRANSACTION_FEES, - NONDETERMINISTIC_FUNCTION_PARAMETERS), + // snapshotMode( + // FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES, + // NONDETERMINISTIC_FUNCTION_PARAMETERS), newKeyNamed(ED25519KEY).shape(ED25519), cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS).key(ED25519KEY), uploadInitCode(TOKEN_CREATE_CONTRACT), @@ -383,10 +379,10 @@ final HapiSpec createTokenWithInvalidTreasury() { final HapiSpec createTokenWithInsufficientValueSent() { return defaultHapiSpec("createTokenWithInsufficientValueSent") .given( - snapshotMode( - FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - NONDETERMINISTIC_TRANSACTION_FEES, - NONDETERMINISTIC_FUNCTION_PARAMETERS), + // snapshotMode( + // FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES, + // NONDETERMINISTIC_FUNCTION_PARAMETERS), newKeyNamed(ED25519KEY).shape(ED25519), cryptoCreate(ACCOUNT).key(ED25519KEY).balance(ONE_MILLION_HBARS), uploadInitCode(TOKEN_CREATE_CONTRACT)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java index c333ac31a69f..2340cf1aaab1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/PrngPrecompileSuite.java @@ -28,12 +28,8 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CONTRACT_REVERT_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; @@ -105,12 +101,11 @@ final HapiSpec multipleCallsHaveIndependentResults() { final List prngSeeds = new ArrayList<>(); return defaultHapiSpec("MultipleCallsHaveIndependentResults") .given( - snapshotMode( - FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - NONDETERMINISTIC_TRANSACTION_FEES, - NONDETERMINISTIC_CONTRACT_CALL_RESULTS), - uploadInitCode(prng), - contractCreate(prng)) + // snapshotMode( + // FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES, + // NONDETERMINISTIC_CONTRACT_CALL_RESULTS), + uploadInitCode(prng), contractCreate(prng)) .when(withOpContext((spec, opLog) -> { for (int i = 0; i < numCalls; i++) { final var txn = "call" + i; @@ -154,10 +149,9 @@ final HapiSpec emptyInputCallFails() { final var emptyInputCall = "emptyInputCall"; return defaultHapiSpec("emptyInputCallFails") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), - cryptoCreate(BOB), - uploadInitCode(prng), - contractCreate(prng)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) .when(sourcing(() -> contractCall(prng, GET_SEED) .withExplicitParams( () -> CommonUtils.hex(Bytes.fromBase64String("").toArray())) @@ -186,10 +180,9 @@ final HapiSpec invalidLargeInputFails() { final var largeInputCall = "largeInputCall"; return defaultHapiSpec("invalidLargeInputFails") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), - cryptoCreate(BOB), - uploadInitCode(prng), - contractCreate(prng)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) .when(sourcing(() -> contractCall(prng, GET_SEED) .withExplicitParams(() -> CommonUtils.hex( Bytes.fromBase64String(EXPLICIT_LARGE_PARAMS).toArray())) @@ -218,10 +211,9 @@ final HapiSpec nonSupportedAbiCallGracefullyFails() { final var failedCall = "failedCall"; return defaultHapiSpec("nonSupportedAbiCallGracefullyFails") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), - cryptoCreate(BOB), - uploadInitCode(prng), - contractCreate(prng)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) .when(sourcing(() -> contractCall(prng, "performNonExistingServiceFunctionCall") .gas(GAS_TO_OFFER) .payingWith(BOB) @@ -244,10 +236,9 @@ final HapiSpec functionCallWithLessThanFourBytesFailsGracefully() { final var lessThan4Bytes = "lessThan4Bytes"; return defaultHapiSpec("functionCallWithLessThanFourBytesFailsGracefully") .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_TRANSACTION_FEES), - cryptoCreate(BOB), - uploadInitCode(THE_PRNG_CONTRACT), - contractCreate(THE_PRNG_CONTRACT)) + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES), + cryptoCreate(BOB), uploadInitCode(THE_PRNG_CONTRACT), contractCreate(THE_PRNG_CONTRACT)) .when( sourcing(() -> contractCall(THE_PRNG_CONTRACT, GET_SEED) .withExplicitParams( @@ -277,13 +268,11 @@ final HapiSpec prngPrecompileHappyPathWorks() { final var randomBits = "randomBits"; return defaultHapiSpec("prngPrecompileHappyPathWorks") .given( - snapshotMode( - FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - NONDETERMINISTIC_TRANSACTION_FEES, - NONDETERMINISTIC_CONTRACT_CALL_RESULTS), - cryptoCreate(BOB), - uploadInitCode(prng), - contractCreate(prng)) + // snapshotMode( + // FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // NONDETERMINISTIC_TRANSACTION_FEES, + // NONDETERMINISTIC_CONTRACT_CALL_RESULTS), + cryptoCreate(BOB), uploadInitCode(prng), contractCreate(prng)) .when(sourcing(() -> contractCall(prng, GET_SEED) .gas(GAS_TO_OFFER) .payingWith(BOB) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java index a40d186924fe..a6ebaa711be2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/staking/StakingSuite.java @@ -55,6 +55,7 @@ import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountAmount; +import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -64,9 +65,14 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestMethodOrder; @HapiTestSuite +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Tag(TIME_CONSUMING) public class StakingSuite extends HapiSuite { @@ -80,12 +86,12 @@ public class StakingSuite extends HapiSuite { private static final String CAROL = "carol"; private static final long INTER_PERIOD_SLEEP_MS = ONE_STAKING_PERIOD + BUFFER; public static final String STAKING_START_THRESHOLD = "staking.startThreshold"; + public static final String REWARD_BALANCE_THRESHOLD = "staking.rewardBalanceThreshold"; + public static final String PER_HBAR_REWARD_RATE = "staking.perHbarRewardRate"; public static final String STAKING_REWARD_RATE = "staking.perHbarRewardRate"; public static final String FIRST_TRANSFER = "firstTransfer"; public static final String FIRST_TXN = "firstTxn"; - public static final String STANDARD_STAKING_RATE = "273972602739726"; - private static final String TRX_TYPE = "noRewardTransfer"; - private static final String SECOND_TRANSFER = "secondTransfer"; + private static final long STAKING_PERIOD_MINS = 1L; public static void main(String... args) { new StakingSuite().runSuiteSync(); @@ -103,15 +109,11 @@ public boolean canRunConcurrent() { @Override public List getSpecsInSuite() { - // These specs cannot really be run in CI; they are mostly useful for local - // validation on a network started with a staking.periodMins=1 override return List.of( + setUp(), losingEvenAZeroBalanceStakerTriggersStakeeRewardSituation(), evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation(), - rewardsWorkAsExpected(), - rewardsWorkAsExpectedWithReceiverSigRequired(), - rewardPaymentsNotRepeatedInSamePeriod(), - getInfoQueriesReturnsPendingRewards(), + stakingMetadataUpdateIsRewardOpportunity(), secondOrderRewardSituationsWork(), endOfStakingPeriodRecTest(), rewardsOfDeletedAreRedirectedToBeneficiary(), @@ -122,12 +124,27 @@ public List getSpecsInSuite() { zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds()); } + @HapiTest + @Order(1) + @BeforeAll + final HapiSpec setUp() { + return defaultHapiSpec("setUp") + .given( + overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), + overriding(PER_HBAR_REWARD_RATE, "" + 3_333_333), + overriding(REWARD_BALANCE_THRESHOLD, "" + 0), + cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) + .when() + .then(); + } + /** * Tests a scenario in which many zero stake accounts are created, and then after a few staking * periods, a series of credits and debits are made to them, and they are confirmed to have * received the expected rewards (all zero). */ @HapiTest + @Order(12) final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { final var zeroStakeAccount = "zeroStakeAccount"; final var numZeroStakeAccounts = 10; @@ -135,9 +152,6 @@ final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { return defaultHapiSpec("ZeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds") .given( - overridingAllOf(Map.of( - STAKING_START_THRESHOLD, "" + ONE_HBAR, STAKING_REWARD_RATE, STANDARD_STAKING_RATE)), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, 250 * ONE_MILLION_HBARS)), inParallel(IntStream.range(0, numZeroStakeAccounts) .mapToObj(i -> cryptoCreate(zeroStakeAccount + i) .stakedNodeId(0) @@ -180,6 +194,7 @@ final HapiSpec zeroStakeAccountsHaveMetadataResetOnFirstDayTheyReceiveFunds() { * end of a staking period, only to receive it back shortly after that period starts. */ @HapiTest + @Order(11) final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { final var alice = "alice"; final var baldwin = "baldwin"; @@ -199,9 +214,6 @@ final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { return defaultHapiSpec("StakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries") .given( - overridingAllOf(Map.of( - STAKING_START_THRESHOLD, "" + ONE_HBAR, STAKING_REWARD_RATE, STANDARD_STAKING_RATE)), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, 250 * ONE_MILLION_HBARS)), cryptoCreate(alice).stakedNodeId(0).balance(ONE_MILLION_HBARS), cryptoCreate(baldwin).stakedNodeId(0).balance(0L), // Reach a period where stakers can collect rewards @@ -262,6 +274,7 @@ final HapiSpec stakeIsManagedCorrectlyInTxnsAroundPeriodBoundaries() { * @return the spec described above */ @HapiTest + @Order(10) final HapiSpec autoRenewalsCanTriggerStakingRewards() { final var initBalance = ONE_HBAR * 1000; final var minimalLifetime = 3; @@ -269,12 +282,9 @@ final HapiSpec autoRenewalsCanTriggerStakingRewards() { return defaultHapiSpec("AutoRenewalsCanTriggerStakingRewards") .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS)), cryptoCreate("miscStaker").stakedNodeId(0).balance(ONE_HUNDRED_HBARS * 1000), uploadInitCode(PAY_RECEIVABLE_CONTRACT), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .when( enableContractAutoRenewWith(minimalLifetime, 0), contractCreate(PAY_RECEIVABLE_CONTRACT) @@ -285,13 +295,19 @@ final HapiSpec autoRenewalsCanTriggerStakingRewards() { .autoRenewSecs((INTER_PERIOD_SLEEP_MS + BUFFER) / 1000) .balance(initBalance) .via(creation), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)), - sleepFor(INTER_PERIOD_SLEEP_MS)) - .then(cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L))); + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) + .then( + cryptoTransfer(tinyBarsFromTo(GENESIS, NODE, 1L)).via("triggerRenewal"), + getTxnRecord("triggerRenewal") + .andAllChildRecords() + .countStakingRecords() + .logged()); } @HapiTest + @Order(8) final HapiSpec canBeRewardedWithoutMinStakeIfSoConfigured() { final var patientlyWaiting = "patientlyWaiting"; @@ -303,419 +319,331 @@ final HapiSpec canBeRewardedWithoutMinStakeIfSoConfigured() { "staking.requireMinStakeToReward", "true", STAKING_START_THRESHOLD, - "100_000_000", - STAKING_REWARD_RATE, - STANDARD_STAKING_RATE)), + "100_000_000")), // Create the patiently waiting staker - cryptoCreate(patientlyWaiting).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), - // Activate staking (but without achieving the 25B hbar minstake) - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) + cryptoCreate(patientlyWaiting).stakedNodeId(0).balance(ONE_HUNDRED_HBARS)) .when( - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), cryptoTransfer(tinyBarsFromTo(patientlyWaiting, FUNDING, 1)), getAccountBalance(patientlyWaiting).logged(), // Now we should be rewardable even though node0 is far from minStake overriding("staking.requireMinStakeToReward", "false"), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), cryptoTransfer(tinyBarsFromTo(patientlyWaiting, FUNDING, 1))) .then(getAccountBalance(patientlyWaiting).logged()); } - + // HERE + @HapiTest + @Order(5) final HapiSpec secondOrderRewardSituationsWork() { final long totalStakeStartCase1 = 3 * ONE_HUNDRED_HBARS; - final long expectedRewardRate = Math.max(0, Math.min(10 * ONE_HBAR, SOME_REWARD_RATE)); - final long rewardSumHistoryCase1 = - expectedRewardRate / (totalStakeStartCase1 / TINY_PARTS_PER_WHOLE); // should be 333333333 + final long rewardSumHistoryCase1 = SOME_REWARD_RATE / (totalStakeStartCase1 / TINY_PARTS_PER_WHOLE) / 100; final long alicePendingRewardsCase1 = rewardSumHistoryCase1 * (2 * ONE_HUNDRED_HBARS / TINY_PARTS_PER_WHOLE); final long bobPendingRewardsCase1 = rewardSumHistoryCase1 * (ONE_HUNDRED_HBARS / TINY_PARTS_PER_WHOLE); return defaultHapiSpec("SecondOrderRewardSituationsWork") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) - .when( + .given() + .when( // period 1 cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), cryptoCreate(BOB).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), cryptoCreate(CAROL).stakedAccountId(ALICE).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( - /* --- paid_rewards 0 for first period --- */ + /* --- period 2 - paid_rewards 0 for first period --- */ cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(FIRST_TRANSFER), getTxnRecord(FIRST_TRANSFER) .andAllChildRecords() + .countStakingRecords() .stakingFeeExempted() .hasChildRecordCount(1) .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .hasPaidStakingRewards(List.of()), /* --- second period reward eligible --- */ - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)).via("endOfStakingPeriodXfer"), + getAccountInfo(ALICE) + .has(accountWith().stakedNodeId(0L).pendingRewards(666666600L)) + .logged(), + getAccountInfo(BOB) + .has(accountWith().stakedNodeId(0L).pendingRewards(333333300L)) + .logged(), + getTxnRecord("endOfStakingPeriodXfer") + .andAllChildRecords() + .hasChildRecordCount(1) + .countStakingRecords() + .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), + getAccountInfo(ALICE) + .has(accountWith().stakedNodeId(0L).pendingRewards(alicePendingRewardsCase1)) + .logged(), + getAccountInfo(BOB) + .has(accountWith().stakedNodeId(0L).pendingRewards(bobPendingRewardsCase1)) + .logged(), cryptoUpdate(CAROL).newStakedAccountId(BOB).via("secondOrderRewardSituation"), getTxnRecord("secondOrderRewardSituation") .andAllChildRecords() - .hasChildRecordCount(1) + .countStakingRecords() .hasStakingFeesPaid() - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .hasPaidStakingRewards(List.of( Pair.of(ALICE, alicePendingRewardsCase1), Pair.of(BOB, bobPendingRewardsCase1))) + .logged(), + /* Within the same period rewards are not awarded twice */ + cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) + .payingWith(BOB) + .via("expectNoReward"), + getTxnRecord("expectNoReward") + .andAllChildRecords() + .countStakingRecords() + .hasChildRecordCount(0) + .hasStakingFeesPaid() + // .hasPaidStakingRewards(List.of()) .logged()); } - final HapiSpec evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation() { - return defaultHapiSpec("EvenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation") + @HapiTest + @Order(13) + final HapiSpec pendingRewardsPaidBeforeStakedToMeUpdates() { + return defaultHapiSpec("PendingRewardsPaidBeforeStakedToMeUpdates") .given( overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), + overriding(PER_HBAR_REWARD_RATE, "" + ONE_HBAR), + overriding(REWARD_BALANCE_THRESHOLD, "" + 0), cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) + .when( // period 1 + cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), + cryptoCreate(CAROL).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) + .then( + /* --- period 2 - paid_rewards 0 for first period --- */ + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, ONE_HBAR)) + .via(FIRST_TRANSFER), + getTxnRecord(FIRST_TRANSFER) + .andAllChildRecords() + .countStakingRecords() + .stakingFeeExempted() + .hasChildRecordCount(1) + .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), + // alice - 100, carol - 100 + /* --- third period reward eligible from period 2--- */ + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoUpdate(CAROL).newStakedAccountId(ALICE).via("stakedIdUpdate"), + getTxnRecord("stakedIdUpdate") + .andAllChildRecords() + .hasChildRecordCount(1) + .countStakingRecords() + .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) + .hasPaidStakingRewardsCount(2) + .hasPaidStakingRewards( + List.of(Pair.of(ALICE, 100 * ONE_HBAR), Pair.of(CAROL, 100 * ONE_HBAR))), + // alice - 200, stakedToMe - 200 + // carol - 200 + /* fourth period */ + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, ONE_HBAR)) + .via("fourthPeriod"), + getTxnRecord("fourthPeriod") + .andAllChildRecords() + .countStakingRecords() + .logged(), + cryptoTransfer(tinyBarsFromTo(GENESIS, ALICE, ONE_HBAR)).via("aliceFirstXfer"), + getTxnRecord("aliceFirstXfer") + .hasPaidStakingRewards(List.of(Pair.of(ALICE, 100 * ONE_HBAR))) + .logged(), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + + /* fifth period */ + cryptoTransfer(tinyBarsFromTo(GENESIS, ALICE, ONE_HBAR)).via("aliceSecondXfer"), + getTxnRecord("aliceSecondXfer") + .hasPaidStakingRewards(List.of(Pair.of(ALICE, 400 * ONE_HBAR))) + .logged()); + } + + @HapiTest + @Order(3) + final HapiSpec evenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation() { + return defaultHapiSpec("EvenOneTinybarChangeInIndirectStakingAccountTriggersStakeeRewardSituation") + .given() .when( cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), cryptoCreate(BOB).stakedAccountId(ALICE).balance(ONE_HUNDRED_HBARS), cryptoCreate(CAROL).stakedAccountId(ALICE).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L)), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, CAROL, 1)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewardsCount(1)); + getTxnRecord(FIRST_TRANSFER) + .andAllChildRecords() + .countStakingRecords() + .hasPaidStakingRewardsCount(1)); } + @HapiTest + @Order(9) final HapiSpec zeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP() { return defaultHapiSpec("ZeroRewardEarnedWithZeroWholeHbarsStillSetsSASOLARP") .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS)), - // Ensure all periods have a non-zero reward rate cryptoCreate("helpfulStaker").stakedNodeId(0).balance(ONE_MILLION_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .when( cryptoCreate(ALICE).stakedNodeId(0).balance(0L), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(GENESIS, ALICE, ONE_HUNDRED_HBARS)), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(ALICE, FUNDING, ONE_HUNDRED_HBARS)), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, ALICE, 1)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewardsCount(1)); + getTxnRecord(FIRST_TRANSFER).countStakingRecords().hasPaidStakingRewardsCount(1)); } + @HapiTest + @Order(2) final HapiSpec losingEvenAZeroBalanceStakerTriggersStakeeRewardSituation() { return defaultHapiSpec("LosingEvenAZeroBalanceStakerTriggersStakeeRewardSituation") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) + .given() .when( cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), cryptoCreate(BOB).stakedAccountId(ALICE).balance(0L), cryptoCreate(CAROL).stakedAccountId(ALICE).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, FUNDING, 1L)), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoUpdate(BOB).newStakedNodeId(0L).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER).hasPaidStakingRewardsCount(1)); - } - - final HapiSpec getInfoQueriesReturnsPendingRewards() { - final long expectedTotalStakedRewardStart = ONE_HUNDRED_HBARS + ONE_HUNDRED_HBARS; - final long accountTotalStake = ONE_HUNDRED_HBARS; - final long expectedRewardRate = Math.max(0, Math.min(10 * ONE_HBAR, SOME_REWARD_RATE)); - final long expectedRewardSumHistory = - expectedRewardRate / (expectedTotalStakedRewardStart / TINY_PARTS_PER_WHOLE); // should be 500_000_000L - final long expectedPendingReward = expectedRewardSumHistory * (accountTotalStake / TINY_PARTS_PER_WHOLE); // - // should be 500_000_000L - - return defaultHapiSpec("getInfoQueriesReturnsPendingRewards") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, 10 * ONE_HBAR))) - .when( - cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), - cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), - uploadInitCode(PAYABLE_CONTRACT), - contractCreate(PAYABLE_CONTRACT).stakedNodeId(0L).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS)) - .then( - /* --- staking will be activated, child record is generated at end of staking period --- */ - cryptoTransfer(tinyBarsFromTo(GENESIS, BOB, ONE_HBAR)).via(FIRST_TXN), - getTxnRecord(FIRST_TXN) - .andAllChildRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of()), - sleepFor(INTER_PERIOD_SLEEP_MS), - cryptoTransfer(tinyBarsFromTo(GENESIS, BOB, ONE_HBAR)), - - /* --- waited enough and account and contract should be eligible for rewards */ - getAccountInfo(ALICE).has(accountWith().stakedNodeId(0L).pendingRewards(expectedPendingReward)), - getContractInfo(PAYABLE_CONTRACT) - .has(contractWith().stakedNodeId(0L).pendingRewards(expectedPendingReward)), - - /* -- trigger a txn and see if pays expected reward */ - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) - .payingWith(BOB) - .via("rewardTxn"), - getTxnRecord("rewardTxn") - .andAllChildRecords() - .hasChildRecordCount(0) - .hasPaidStakingRewards(List.of(Pair.of(ALICE, expectedPendingReward))), - contractCall(PAYABLE_CONTRACT, "deposit", 1_000L) - .payingWith(BOB) - .sending(1_000L) - .via("contractRewardTxn"), - getTxnRecord("contractRewardTxn") + getTxnRecord(FIRST_TRANSFER) .andAllChildRecords() - .hasChildRecordCount(0) - .hasPaidStakingRewards(List.of(Pair.of(PAYABLE_CONTRACT, expectedPendingReward)))); + .countStakingRecords() + .hasPaidStakingRewardsCount(1) + .logged()); } - final HapiSpec rewardPaymentsNotRepeatedInSamePeriod() { - return defaultHapiSpec("rewardPaymentsNotRepeatedInSamePeriod") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, 10 * ONE_HBAR))) + @HapiTest + @Order(4) + final HapiSpec stakingMetadataUpdateIsRewardOpportunity() { + return defaultHapiSpec("stakingMetadataUpdateIsRewardOpportunity") + .given() .when( cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), uploadInitCode(PAYABLE_CONTRACT), contractCreate(PAYABLE_CONTRACT).stakedNodeId(0L).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( - /* --- staking will be activated in the previous suite, child record is generated at end of - staking period. But - since rewardsSunHistory will be 0 for the first staking period after rewards are activated , - paid_rewards will be 0 --- */ + /* --- Since rewardsSunHistory will be 0 for the first staking period, paid_rewards will be 0 --- */ cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(FIRST_TXN), getTxnRecord(FIRST_TXN) .andAllChildRecords() + .countStakingRecords() .hasChildRecordCount(1) .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .hasPaidStakingRewards(List.of()), /* should receive reward */ - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + // info queries return rewards + getContractInfo(PAYABLE_CONTRACT) + .has(contractWith().stakedNodeId(0L).pendingRewards(333333300L)), contractUpdate(PAYABLE_CONTRACT).newDeclinedReward(true).via("acceptsReward"), getTxnRecord("acceptsReward") .logged() .andAllChildRecords() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of(Pair.of(PAYABLE_CONTRACT, 500000000L))), - contractUpdate(PAYABLE_CONTRACT).newStakedNodeId(1L).hasPrecheck(INVALID_STAKING_ID), + .countStakingRecords() + // .hasChildRecordCount(1) + // + // .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) + .hasPaidStakingRewards(List.of(Pair.of(PAYABLE_CONTRACT, 333333300L))), + + // same period should not trigger reward again + contractUpdate(PAYABLE_CONTRACT).newStakedNodeId(111L).hasPrecheck(INVALID_STAKING_ID), contractUpdate(PAYABLE_CONTRACT).newStakedAccountId(BOB).via("samePeriodTxn"), getTxnRecord("samePeriodTxn") .andAllChildRecords() + .countStakingRecords() .hasChildRecordCount(0) .hasPaidStakingRewards(List.of()), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), - /* --- next period, so child record is generated at end of staking period. - Since rewardsSumHistory is updated during the previous staking period after rewards are - activated ,paid_rewards will be non-empty in this record --- */ - sleepFor(INTER_PERIOD_SLEEP_MS), - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) + /* -- trigger a deposit and see if pays expected reward */ + contractCall(PAYABLE_CONTRACT, "deposit", BigInteger.valueOf(1_000L)) .payingWith(BOB) - .via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) + .sending(1_000L) + .via("contractRewardTxn"), + getTxnRecord("contractRewardTxn") .andAllChildRecords() + .countStakingRecords() .hasChildRecordCount(1) - .hasStakingFeesPaid() .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of(Pair.of(ALICE, 500000000L))) - .logged(), - /* Within the same period rewards are not awarded twice */ - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) - .payingWith(BOB) - .via("samePeriodTransfer"), - getTxnRecord("samePeriodTransfer") - .andAllChildRecords() - .hasChildRecordCount(0) - .hasStakingFeesPaid() - .hasPaidStakingRewards(List.of()) - .logged(), - cryptoUpdate(ALICE).newStakedAccountId(BOB).via("samePeriodUpdate"), - getTxnRecord("samePeriodUpdate") .logged() - .andAllChildRecords() - .hasChildRecordCount(0) - .stakingFeeExempted() - .hasPaidStakingRewards(List.of())); - } - - final HapiSpec rewardsWorkAsExpectedWithReceiverSigRequired() { - final long expectedTotalStakedRewardStart = ONE_HUNDRED_HBARS + ONE_HBAR; - final long expectedRewardRate = Math.max(0, Math.min(10 * ONE_HBAR, SOME_REWARD_RATE)); - final long expectedRewardSumHistory = - expectedRewardRate / (expectedTotalStakedRewardStart / TINY_PARTS_PER_WHOLE); - // should be 9900990L - final long expectedPendingRewards = 1798608L; // base on current reward rate for 24 hours - - return defaultHapiSpec("rewardsWorkAsExpectedWithReceiverSigRequired") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - getAccountInfo(STAKING_REWARD).logged(), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_HBAR))) - .when( - cryptoCreate(ALICE) - .stakedNodeId(0) - .balance(ONE_HUNDRED_HBARS) - .receiverSigRequired(true), - cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS)) - .then( - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(TRX_TYPE), - getTxnRecord(TRX_TYPE) - .stakingFeeExempted() - .andAllChildRecords() - .hasNonStakingChildRecordCount(0) // only end of staking period record generated - .logged(), - - /* --- - since rewardsSumHistory will be 0 for the first staking period after rewards - are activated, paid_rewards will be 0 --- */ - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, 9 * ONE_HBAR)), - sleepFor(INTER_PERIOD_SLEEP_MS), - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) - .andAllChildRecords() - .stakingFeeExempted() - .hasNonStakingChildRecordCount(0) - .hasPaidStakingRewards(List.of()) - .logged(), - - /* --- Since rewardsSumHistory is updated during the previous staking period after rewards are - activated, paid_rewards will be non-empty in this record --- */ - sleepFor(INTER_PERIOD_SLEEP_MS), - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) - .payingWith(BOB) - .via(SECOND_TRANSFER), - getTxnRecord(SECOND_TRANSFER) - .andAllChildRecords() - .hasNonStakingChildRecordCount(0) - .hasStakingFeesPaid() - .hasPaidStakingRewards(List.of(Pair.of(ALICE, expectedPendingRewards))) - .logged()); - } - - final HapiSpec rewardsWorkAsExpected() { - final long expectedTotalStakedRewardStart = ONE_HUNDRED_HBARS + ONE_HBAR; - final long expectedRewardRate = Math.max(0, Math.min(10 * ONE_HBAR, SOME_REWARD_RATE)); - final long expectedRewardSumHistory = - expectedRewardRate / (expectedTotalStakedRewardStart / TINY_PARTS_PER_WHOLE); // should be 9900990L - final long expectedPendingRewards = - expectedRewardSumHistory * (expectedTotalStakedRewardStart / TINY_PARTS_PER_WHOLE); // should be - // 999999990L - - return defaultHapiSpec("rewardsWorkAsExpected") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - overriding(STAKING_REWARD_RATE, "" + SOME_REWARD_RATE), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_HBAR))) - .when( - cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_HUNDRED_HBARS), - cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), - sleepFor(INTER_PERIOD_SLEEP_MS)) - .then( - /* --- staking not active, so no child record for end of staking period are generated --- */ - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(TRX_TYPE), - getTxnRecord(TRX_TYPE) - .stakingFeeExempted() - .andAllChildRecords() - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), - - /* --- staking will be activated, so child record is generated at end of staking period. But - since rewardsSumHistory will be 0 for the first staking period after rewards are activated , - paid_rewards will be 0 --- */ - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, 9 * ONE_HBAR)), - sleepFor(INTER_PERIOD_SLEEP_MS), - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)).via(FIRST_TRANSFER), - getTxnRecord(FIRST_TRANSFER) - .andAllChildRecords() - .stakingFeeExempted() - .hasChildRecordCount(1) - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of()), - - /* --- staking is activated, so child record is generated at end of staking period. - Since rewardsSumHistory is updated during the previous staking period after rewards are - activated , - paid_rewards will be non-empty in this record --- */ - sleepFor(INTER_PERIOD_SLEEP_MS), - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) - .payingWith(BOB) - .via(SECOND_TRANSFER), - getTxnRecord(SECOND_TRANSFER) - .andAllChildRecords() - .hasChildRecordCount(1) - .hasStakingFeesPaid() - .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) - .hasPaidStakingRewards(List.of(Pair.of(ALICE, expectedPendingRewards))), - - /* Within the same period rewards are not awarded twice */ - cryptoTransfer(tinyBarsFromTo(BOB, ALICE, ONE_HBAR)) - .payingWith(BOB) - .via("expectNoReward"), - getTxnRecord("expectNoReward") - .andAllChildRecords() - .hasChildRecordCount(0) - .hasStakingFeesPaid() - .hasPaidStakingRewards(List.of())); + // .hasPaidStakingRewards(List.of(Pair.of(PAYABLE_CONTRACT, + // 500000000L))) + ); } + @HapiTest + @Order(6) final HapiSpec endOfStakingPeriodRecTest() { return defaultHapiSpec("EndOfStakingPeriodRecTest") .given( - cryptoCreate("a1").balance(24000 * ONE_MILLION_HBARS).stakedNodeId(0), - cryptoCreate("a2").balance(2000 * ONE_MILLION_HBARS).stakedNodeId(0), + cryptoCreate("a1").balance(ONE_MILLION_HBARS).stakedNodeId(0), + cryptoCreate("a2").balance(ONE_MILLION_HBARS).stakedNodeId(0), cryptoTransfer( tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS)) // will trigger staking ) - .when(sleepFor(INTER_PERIOD_SLEEP_MS)) + .when(waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("trigger"), getTxnRecord("trigger") .logged() + .countStakingRecords() .hasChildRecordCount(1) .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("transfer"), getTxnRecord("transfer") + .countStakingRecords() .hasChildRecordCount(1) .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .logged(), cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("noEndOfStakingPeriodRecord"), getTxnRecord("noEndOfStakingPeriodRecord") + .countStakingRecords() .hasChildRecordCount(0) .logged(), - sleepFor(INTER_PERIOD_SLEEP_MS), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), cryptoTransfer(tinyBarsFromTo("a1", "a2", ONE_HBAR)).via("transfer1"), getTxnRecord("transfer1") + .countStakingRecords() .hasChildRecordCount(1) .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) .logged()); } @HapiTest + @Order(7) final HapiSpec rewardsOfDeletedAreRedirectedToBeneficiary() { final var bob = "bob"; final var deletion = "deletion"; return defaultHapiSpec("RewardsOfDeletedAreRedirectedToBeneficiary") - .given( - overriding(STAKING_START_THRESHOLD, "" + 10 * ONE_HBAR), - cryptoTransfer(tinyBarsFromTo(GENESIS, STAKING_REWARD, ONE_MILLION_HBARS))) + .given() .when( - cryptoCreate(ALICE).stakedNodeId(0).balance(33_000 * ONE_MILLION_HBARS), + cryptoCreate(ALICE).stakedNodeId(0).balance(ONE_MILLION_HBARS), cryptoCreate(bob).balance(0L), - sleepFor(150_000)) + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS), + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1L)), + waitUntilStartOfNextStakingPeriod(STAKING_PERIOD_MINS)) .then( cryptoDelete(ALICE).transfer(bob).via(deletion), - getTxnRecord(deletion).andAllChildRecords().logged()); + getTxnRecord(deletion) + .andAllChildRecords() + .countStakingRecords() + .hasChildRecordCount(1) + .hasChildRecords(recordWith().memo(END_OF_STAKING_PERIOD_CALCULATIONS_MEMO)) + .hasPaidStakingRewards(List.of(Pair.of(bob, 3333333000000L))) + .logged()); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 6cb50bc55f12..0a81d57c1048 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -74,13 +74,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.reduceFeeFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ALLOW_SKIPPED_ENTITY_IDS; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.hapi.ContractCreateSuite.EMPTY_CONSTRUCTOR_CONTRACT; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.CRYPTO_TRANSFER_RECEIVER; import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.FALSE; @@ -1005,7 +1002,8 @@ final HapiSpec lazyCreateViaEthereumCryptoTransfer() { return propertyPreservingHapiSpec("lazyCreateViaEthereumCryptoTransfer") .preserving(CHAIN_ID_PROP, LAZY_CREATE_PROPERTY_NAME, CONTRACTS_EVM_VERSION_PROP) .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, ALLOW_SKIPPED_ENTITY_IDS), + // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, + // ALLOW_SKIPPED_ENTITY_IDS), overridingThree( CHAIN_ID_PROP, "298", From 2ee5ce0739a2cf6c7da8fb6b176b7b6a4de72825 Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Thu, 28 Dec 2023 13:08:16 -0600 Subject: [PATCH 56/80] chore(ci): add support for docker image determinism checking (#10662) Signed-off-by: Nathan Klick --- .../workflows/flow-artifact-determinism.yaml | 13 +- .../node-flow-build-application.yaml | 2 +- .../node-flow-pull-request-checks.yaml | 19 +- .../generate-docker-artifact-baseline.sh | 175 ++++++++ .../zxc-verify-docker-build-determinism.yaml | 387 ++++++++++++++++++ README.md | 11 +- 6 files changed, 600 insertions(+), 7 deletions(-) create mode 100755 .github/workflows/support/scripts/generate-docker-artifact-baseline.sh create mode 100644 .github/workflows/zxc-verify-docker-build-determinism.yaml diff --git a/.github/workflows/flow-artifact-determinism.yaml b/.github/workflows/flow-artifact-determinism.yaml index 4edfc21317d6..54457467a12a 100644 --- a/.github/workflows/flow-artifact-determinism.yaml +++ b/.github/workflows/flow-artifact-determinism.yaml @@ -49,7 +49,7 @@ permissions: contents: read jobs: - check: + check-gradle: name: Gradle uses: ./.github/workflows/zxc-verify-gradle-build-determinism.yaml with: @@ -59,3 +59,14 @@ jobs: secrets: gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} + + check-docker: + name: Docker + uses: ./.github/workflows/zxc-verify-docker-build-determinism.yaml + with: + ref: ${{ github.event.inputs.ref || '' }} + java-distribution: ${{ inputs.java-distribution || 'temurin' }} + java-version: ${{ inputs.java-version || '21.0.1' }} + secrets: + gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} + gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} diff --git a/.github/workflows/node-flow-build-application.yaml b/.github/workflows/node-flow-build-application.yaml index 354b2e12a067..059955cb6ea6 100644 --- a/.github/workflows/node-flow-build-application.yaml +++ b/.github/workflows/node-flow-build-application.yaml @@ -81,7 +81,7 @@ jobs: java-version: ${{ github.event.inputs.java-version || '21.0.1' }} java-distribution: ${{ github.event.inputs.java-distribution || 'temurin' }} enable-unit-tests: ${{ github.event_name == 'push' || github.event.inputs.enable-unit-tests == 'true' }} - enable-sonar-analysis: ${{ github.event_name == 'push' || github.event.inputs.enable-sonar-analysis == 'true' }} + enable-sonar-analysis: false enable-integration-tests: ${{ github.event_name == 'push' || github.event.inputs.enable-integration-tests == 'true' }} enable-hapi-tests-misc: ${{ github.event_name == 'push' || github.event.inputs.enable-hapi-tests == 'true' }} enable-hapi-tests-crypto: ${{ github.event_name == 'push' || github.event.inputs.enable-hapi-tests == 'true' }} diff --git a/.github/workflows/node-flow-pull-request-checks.yaml b/.github/workflows/node-flow-pull-request-checks.yaml index d08534a57b3a..a953134d7b51 100644 --- a/.github/workflows/node-flow-pull-request-checks.yaml +++ b/.github/workflows/node-flow-pull-request-checks.yaml @@ -273,8 +273,8 @@ jobs: gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} snyk-token: ${{ secrets.SNYK_TOKEN }} - artifact-determinism: - name: Artifact Determinism + gradle-determinism: + name: Gradle Determinism uses: ./.github/workflows/zxc-verify-gradle-build-determinism.yaml needs: - dependency-check @@ -287,3 +287,18 @@ jobs: secrets: gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} + + docker-determinism: + name: Docker Determinism + uses: ./.github/workflows/zxc-verify-docker-build-determinism.yaml + needs: + - dependency-check + - spotless + if: ${{ github.actor != 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository }} + with: + ref: ${{ github.event.inputs.ref || '' }} + java-distribution: temurin + java-version: 21.0.1 + secrets: + gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} + gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} diff --git a/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh b/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh new file mode 100755 index 000000000000..381026f1f0fd --- /dev/null +++ b/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash +set -o pipefail +set +e + +readonly RELEASE_LIB_PATH="hedera-node/data/lib" +readonly RELEASE_APPS_PATH="hedera-node/data/apps" +readonly DOCKER_IMAGE_NAME="main-network-node" + +GROUP_ACTIVE="false" + +function fail { + printf '%s\n' "$1" >&2 ## Send message to stderr. Exclude >&2 if you don't want it that way. + if [[ "${GROUP_ACTIVE}" == "true" ]]; then + end_group + fi + exit "${2-1}" ## Return a code specified by $2 or 1 by default. +} + +function start_group { + if [[ "${GROUP_ACTIVE}" == "true" ]]; then + end_group + fi + + GROUP_ACTIVE="true" + printf "::group::%s\n" "${1}" +} + +function end_group { + GROUP_ACTIVE="false" + printf "::endgroup::\n" +} + +function log { + local message="${1}" + shift + # shellcheck disable=SC2059 + printf "${message}" "${@}" +} + +function log_line { + local message="${1}" + shift + # shellcheck disable=SC2059 + printf "${message}\n" "${@}" +} + +function start_task { + local message="${1}" + shift + # shellcheck disable=SC2059 + printf "${message} .....\t" "${@}" +} + +function end_task { + printf "%s\n" "${1:-DONE}" +} + +start_group "Configuring Environment" + # Access workflow environment variables + export GITHUB_WORKSPACE GITHUB_SHA GITHUB_OUTPUT MANIFEST_PATH DOCKER_REGISTRY DOCKER_TAG + + start_task "Initializing Temporary Directory" + TEMP_DIR="$(mktemp -d)" || fail "ERROR (Exit Code: ${?})" "${?}" + trap 'rm -rf "${TEMP_DIR}"' EXIT + end_task "DONE (Path: ${TEMP_DIR})" + + start_task "Resolving the GITHUB_WORKSPACE path" + # Ensure GITHUB_WORKSPACE is provided or default to the repository root + if [[ -z "${GITHUB_WORKSPACE}" || ! -d "${GITHUB_WORKSPACE}" ]]; then + GITHUB_WORKSPACE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../" && pwd)" + fi + end_task "DONE (Path: ${GITHUB_WORKSPACE})" + + start_task "Resolving the GITHUB_OUTPUT path" + # Ensure GITHUB_OUTPUT is provided or default to the repository root + if [[ -z "${GITHUB_OUTPUT}" ]]; then + GITHUB_OUTPUT="${TEMP_DIR}/workflow-output.txt" + fi + end_task "DONE (Path: ${GITHUB_OUTPUT})" + + start_task "Resolving the GITHUB_SHA hash" + if [[ -z "${GITHUB_SHA}" ]]; then + GITHUB_SHA="$(git rev-parse HEAD | tr -d '[:space:]')" || fail "ERROR (Exit Code: ${?})" "${?}" + fi + end_task "DONE (Commit: ${GITHUB_SHA})" + + start_task "Resolving the MANIFEST_PATH variable" + if [[ -z "${MANIFEST_PATH}" ]]; then + MANIFEST_PATH="${GITHUB_WORKSPACE}/.manifests/gradle" + fi + end_task "DONE (Path: ${MANIFEST_PATH})" + + start_task "Ensuring the MANIFEST_PATH location is present" + if [[ ! -d "${MANIFEST_PATH}" ]]; then + mkdir -p "${MANIFEST_PATH}" || fail "ERROR (Exit Code: ${?})" "${?}" + fi + end_task + + start_task "Checking for the skopeo command" + if command -v skopeo >/dev/null 2>&1; then + SKOPEO="$(command -v skopeo)" || fail "ERROR (Exit Code: ${?})" "${?}" + export SKOPEO + else + fail "ERROR (Exit Code: ${?})" "${?}" + fi + end_task "DONE (Found: ${SKOPEO})" + + start_task "Checking for the JQ command" + if command -v jq >/dev/null 2>&1; then + JQ="$(command -v jq)" || fail "ERROR (Exit Code: ${?})" "${?}" + export JQ + else + fail "ERROR (Exit Code: ${?})" "${?}" + fi + end_task "DONE (Found: ${JQ})" + + start_task "Checking for prebuilt libraries" + ls -al "${GITHUB_WORKSPACE}/${RELEASE_LIB_PATH}"/*.jar >/dev/null 2>&1 || fail "ERROR (Exit Code: ${?})" "${?}" + end_task "FOUND (Path: ${GITHUB_WORKSPACE}/${RELEASE_LIB_PATH}/*.jar)" + + start_task "Checking for prebuilt applications" + ls -al "${GITHUB_WORKSPACE}/${RELEASE_APPS_PATH}"/*.jar >/dev/null 2>&1 || fail "ERROR (Exit Code: ${?})" "${?}" + end_task "FOUND (Path: ${GITHUB_WORKSPACE}/${RELEASE_APPS_PATH}/*.jar)" +end_group + +start_group "Prepare the Docker Image Information" + start_task "Resolving the DOCKER_REGISTRY variable" + if [[ -z "${DOCKER_REGISTRY}" ]]; then + DOCKER_REGISTRY="localhost:5000" + fi + end_task "DONE (Registry: ${DOCKER_REGISTRY})" + + start_task "Resolving the DOCKER_TAG variable" + if [[ -z "${DOCKER_TAG}" ]]; then + DOCKER_TAG="$(echo "${GITHUB_SHA}" | tr -d '[:space:]' | cut -c1-8)" + fi + end_task "DONE (Tag: ${DOCKER_TAG})" + + start_task "Resolving the Fully Qualified Image Name" + FQ_IMAGE_NAME="${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}:${DOCKER_TAG}" + end_task "DONE (Image: ${FQ_IMAGE_NAME})" +end_group + +start_group "Generate Docker Image Manifest (linux/amd64)" + ${SKOPEO} --override-os linux --override-arch amd64 inspect --tls-verify=false "docker://${FQ_IMAGE_NAME}" | tee "${TEMP_DIR}/linux-amd64.manifest.json" || fail "SKOPEO ERROR (Exit Code: ${?})" "${?}" + ${JQ} -r '.Layers[]' "${TEMP_DIR}/linux-amd64.manifest.json" | tee "${TEMP_DIR}/linux-amd64.layers.json" >/dev/null 2>&1 || fail "JQ LAYER ERROR (Exit Code: ${?})" "${?}" + ${JQ} -r 'del(.RepoTags) | del(.LayersData) | del(.Digest)' "${TEMP_DIR}/linux-amd64.manifest.json" | tee "${TEMP_DIR}/linux-amd64.comparable.json" >/dev/null 2>&1 || fail "JQ COMP ERROR (Exit Code: ${?})" "${?}" +end_group + +start_group "Generate Docker Image Manifest (linux/arm64)" + ${SKOPEO} --override-os linux --override-arch arm64 inspect --tls-verify=false "docker://${FQ_IMAGE_NAME}" | tee "${TEMP_DIR}/linux-arm64.manifest.json" || fail "SKOPEO ERROR (Exit Code: ${?})" "${?}" + ${JQ} -r '.Layers[]' "${TEMP_DIR}/linux-arm64.manifest.json" | tee "${TEMP_DIR}/linux-arm64.layers.json" >/dev/null 2>&1 || fail "JQ LAYER ERROR (Exit Code: ${?})" "${?}" + ${JQ} -r 'del(.RepoTags) | del(.LayersData) | del(.Digest)' "${TEMP_DIR}/linux-arm64.manifest.json" | tee "${TEMP_DIR}/linux-arm64.comparable.json" >/dev/null 2>&1 || fail "JQ COMP ERROR (Exit Code: ${?})" "${?}" +end_group + +start_group "Generating Final Release Manifests" + + start_task "Generating the manifest archive" + tar -czf "${TEMP_DIR}/manifest.tar.gz" -C "${TEMP_DIR}" linux-amd64.manifest.json linux-amd64.layers.json linux-amd64.comparable.json linux-arm64.manifest.json linux-arm64.layers.json linux-arm64.comparable.json >/dev/null 2>&1 || fail "TAR ERROR (Exit Code: ${?})" "${?}" + end_task + + start_task "Copying the manifest files" + cp "${TEMP_DIR}/manifest.tar.gz" "${MANIFEST_PATH}/${GITHUB_SHA}.tar.gz" || fail "COPY ERROR (Exit Code: ${?})" "${?}" + cp "${TEMP_DIR}"/*.json "${MANIFEST_PATH}/" || fail "COPY ERROR (Exit Code: ${?})" "${?}" + end_task "DONE (Path: ${MANIFEST_PATH}/${GITHUB_SHA}.tar.gz)" + + start_task "Setting Step Outputs" + { + printf "path=%s\n" "${MANIFEST_PATH}" + printf "file=%s\n" "${MANIFEST_PATH}/${GITHUB_SHA}.tar.gz" + printf "name=%s\n" "${GITHUB_SHA}.tar.gz" + } >> "${GITHUB_OUTPUT}" + end_task +end_group + diff --git a/.github/workflows/zxc-verify-docker-build-determinism.yaml b/.github/workflows/zxc-verify-docker-build-determinism.yaml new file mode 100644 index 000000000000..3d85827e6fe0 --- /dev/null +++ b/.github/workflows/zxc-verify-docker-build-determinism.yaml @@ -0,0 +1,387 @@ +## +# Copyright (C) 2023 Hedera Hashgraph, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +## + +name: "ZXC: Verify Docker Build Determinism" +on: + workflow_call: + inputs: + ref: + description: "The branch, tag, or commit to checkout:" + type: string + required: false + default: "" + java-distribution: + description: "Java JDK Distribution:" + type: string + required: false + default: "temurin" + java-version: + description: "Java JDK Version:" + type: string + required: false + default: "21.0.1" + + secrets: + gradle-cache-username: + description: "The username used to authenticate with the Gradle Build Cache Node." + required: true + gradle-cache-password: + description: "The password used to authenticate with the Gradle Build Cache Node." + required: true + +defaults: + run: + shell: bash + +permissions: + id-token: write + contents: read + +env: + GRADLE_CACHE_USERNAME: ${{ secrets.gradle-cache-username }} + GRADLE_CACHE_PASSWORD: ${{ secrets.gradle-cache-password }} + DOCKER_MANIFEST_GENERATOR: .github/workflows/support/scripts/generate-docker-artifact-baseline.sh + DOCKER_MANIFEST_PATH: ${{ github.workspace }}/.manifests/docker + DOCKER_REGISTRY: localhost:5000 + DOCKER_IMAGE_NAME: main-network-node + DOCKER_CONTEXT_PATH: hedera-node/infrastructure/docker/containers/production-next/main-network-node + +jobs: + generate-baseline: + name: Generate Baseline + runs-on: [self-hosted, Linux, medium, ephemeral] + outputs: + sha: ${{ steps.commit.outputs.sha }} + sha-abbrev: ${{ steps.commit.outputs.sha-abbrev }} + source-date: ${{ steps.commit.outputs.source-date }} + path: ${{ steps.baseline.outputs.path }} + file: ${{ steps.baseline.outputs.file }} + name: ${{ steps.baseline.outputs.name }} + steps: + - name: Checkout Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ inputs.ref }} + + - name: Setup Java + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + with: + distribution: ${{ inputs.java-distribution }} + java-version: ${{ inputs.java-version }} + + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0 + with: + cache-disabled: true + + - name: Authenticate to Google Cloud + id: google-auth + uses: google-github-actions/auth@f105ef0cdb3b102a020be1767fcc8a974898b7c6 # v1.2.0 + with: + workload_identity_provider: "projects/235822363393/locations/global/workloadIdentityPools/hedera-builds-pool/providers/hedera-builds-gh-actions" + service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com" + + - name: Setup Google Cloud SDK + uses: google-github-actions/setup-gcloud@e30db14379863a8c79331b04a9969f4c1e225e0b # v1.1.1 + + - name: Retrieve Commit Hash + id: commit + run: | + echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}" + echo "sha-abbrev=$(git rev-parse HEAD | tr -d '[:space:]' | cut -c1-8)" >> "${GITHUB_OUTPUT}" + echo "source-date=$(git log -1 --pretty=%ct)" >> "${GITHUB_OUTPUT}" + + - name: Baseline Existence Check + id: baseline + run: | + BASELINE_NAME="${{ steps.commit.outputs.sha }}.tar.gz" + BASELINE_PATH="gs://hedera-ci-ephemeral-artifacts/${{ github.repository }}/docker/baselines" + BASELINE_FILE="${BASELINE_PATH}/${BASELINE_NAME}" + BASELINE_EXISTS="false" + + if gsutil ls "${BASELINE_FILE}" >/dev/null 2>&1; then + BASELINE_EXISTS="true" + fi + + echo "exists=${BASELINE_EXISTS}" >> "${GITHUB_OUTPUT}" + echo "path=${BASELINE_PATH}" >> "${GITHUB_OUTPUT}" + echo "name=${BASELINE_NAME}" >> "${GITHUB_OUTPUT}" + echo "file=${BASELINE_FILE}" >> "${GITHUB_OUTPUT}" + + - name: Install Skopeo and JQ + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + run: | + sudo apt-get update + sudo apt-get install --yes --no-install-recommends skopeo jq + + - name: Build Gradle Artifacts + id: gradle-build + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + run: ./gradlew assemble --scan + + - name: Setup QEmu Support + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + + - name: Setup Docker Buildx Support + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + with: + version: v0.12.0 + driver-opts: network=host + + - name: Setup Local Docker Registry + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + run: docker run -d -p 5000:5000 --restart=always --name registry registry:latest + + - name: Show Docker Version + run: docker version + + - name: Show Docker Info + run: docker info + + - name: Prepare for Docker Build + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + run: | + mkdir -p "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data" + + echo "::group::Copying Library Artifacts" + cp -Rvf "${{ github.workspace }}/hedera-node/data/lib" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/" + echo "::endgroup::" + + echo "::group::Copying Application Artifacts" + cp -Rvf "${{ github.workspace }}/hedera-node/data/apps" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/" + echo "::endgroup::" + + - name: Build Docker Image + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + env: + SOURCE_DATE_EPOCH: ${{ steps.commit.outputs.source-date }} + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + with: + push: true + no-cache: true + platforms: linux/amd64,linux/arm64 + build-args: | + SOURCE_DATE_EPOCH=${{ steps.commit.outputs.source-date }} + context: ${{ env.DOCKER_CONTEXT_PATH }} + tags: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.commit.outputs.sha-abbrev }} + + - name: Generate Manifest + id: manifest + env: + MANIFEST_PATH: ${{ env.DOCKER_MANIFEST_PATH }} + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + run: ${{ env.DOCKER_MANIFEST_GENERATOR }} + + - name: Upload Baseline + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + run: gsutil cp "${{ steps.manifest.outputs.file }}" "${{ steps.baseline.outputs.file }}" + + verify-artifacts: + name: "Verify Artifacts (${{ join(matrix.os, ', ') }})" + runs-on: ${{ matrix.os }} + needs: + - generate-baseline + strategy: + fail-fast: false + matrix: + # Windows is disabled due to GitHub not supporting Docker Desktop/Podman Desktop and Docker CE on Windows not + # supporting BuildKit and the Buildx plugin. + # The GitHub hosted ubuntu-22.04 runners are disabled temporarily due to the Azure VM causing docker container + # builds to be non-deterministic. Self-hosted runners using Ubuntu 22.04 are unaffected. + # The Self Hosted Large instance is temporarily disabled because it is running no longer supported version of + # Ubuntu 18.04. The Large instance will be upgraded to Ubuntu 22.04 in the near future. + os: + #- ubuntu-22.04 + - ubuntu-20.04 + - macos-12 + - macos-11 + #- windows-2022 + #- windows-2019 + - [self-hosted, Linux, medium, ephemeral] + #- [self-hosted, Linux, large, ephemeral] + steps: + - name: Standardize Git Line Endings + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - name: Checkout Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ inputs.ref }} + + - name: Setup Python + uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa # v4.8.0 + with: + python-version: 3.9 + + - name: Setup Java + uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 + with: + distribution: ${{ inputs.java-distribution }} + java-version: ${{ inputs.java-version }} + + - name: Setup Gradle + uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0 + with: + cache-disabled: true + + - name: Install Skopeo and JQ (Linux) + if: ${{ runner.os == 'Linux' }} + run: | + sudo apt-get update + sudo apt-get install --yes --no-install-recommends skopeo jq + + - name: Install Skopeo and JQ (macOS) + if: ${{ runner.os == 'macOS' }} + run: brew install skopeo jq + + - name: Install Skopeo and JQ (Windows) + if: ${{ runner.os == 'Windows' }} + run: | + curl -L -o /usr/bin/jq.exe https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe + curl -L -o /usr/bin/skopeo.exe https://github.com/passcod/winskopeo/releases/latest/download/skopeo.exe + + - name: Setup CoreUtils (macOS) + if: ${{ runner.os == 'macOS' }} + run: brew install coreutils + + - name: Authenticate to Google Cloud + id: google-auth + uses: google-github-actions/auth@f105ef0cdb3b102a020be1767fcc8a974898b7c6 # v1.2.0 + with: + workload_identity_provider: "projects/235822363393/locations/global/workloadIdentityPools/hedera-builds-pool/providers/hedera-builds-gh-actions" + service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com" + + - name: Setup Google Cloud SDK + uses: google-github-actions/setup-gcloud@e30db14379863a8c79331b04a9969f4c1e225e0b # v1.1.1 + env: + CLOUDSDK_PYTHON: ${{ format('{0}{1}', env.pythonLocation, runner.os == 'Windows' && '\python.exe' || '/bin/python3') }} + + - name: Download Baseline + env: + CLOUDSDK_PYTHON: ${{ format('{0}{1}', env.pythonLocation, runner.os == 'Windows' && '\python.exe' || '/bin/python3') }} + run: | + mkdir -p "${DOCKER_MANIFEST_PATH}" + cd "${DOCKER_MANIFEST_PATH}" + gsutil cp "${{ needs.generate-baseline.outputs.file }}" . + tar -xzf "${{ needs.generate-baseline.outputs.name }}" + + - name: Build Artifacts + id: gradle-build + run: ./gradlew assemble --scan --no-build-cache + + - name: Set up Docker + uses: crazy-max/ghaction-setup-docker@d9be6cade441568ba10037bce5221b8f564981f1 # v3.0.0 + if: ${{ !contains(matrix.os, 'self-hosted') }} + with: + version: v24.0.7 + + - name: Setup QEmu Support + if: ${{ runner.os != 'Windows' }} + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + + - name: Setup Docker Buildx Support + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + if: ${{ runner.os != 'Windows' }} + with: + version: v0.12.0 + driver-opts: network=host + + - name: Setup Local Docker Registry + run: docker run -d -p 5000:5000 --restart=always --name registry registry:latest + + - name: Show Docker Version + run: docker version + + - name: Show Docker Info + run: docker info + + - name: Prepare for Docker Build + run: | + mkdir -p "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data" + + echo "::group::Copying Library Artifacts" + cp -Rvf "${{ github.workspace }}/hedera-node/data/lib" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/" + echo "::endgroup::" + + echo "::group::Copying Application Artifacts" + cp -Rvf "${{ github.workspace }}/hedera-node/data/apps" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/" + echo "::endgroup::" + + - name: Build Docker Image + uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + env: + SOURCE_DATE_EPOCH: ${{ needs.generate-baseline.outputs.source-date }} + with: + push: true + no-cache: true + platforms: linux/amd64,linux/arm64 + build-args: | + SOURCE_DATE_EPOCH=${{ needs.generate-baseline.outputs.source-date }} + context: ${{ env.DOCKER_CONTEXT_PATH }} + tags: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ needs.generate-baseline.outputs.sha-abbrev }} + + - name: Regenerate Manifest + id: regen-manifest + env: + MANIFEST_PATH: ${{ env.DOCKER_MANIFEST_PATH }}/regenerated + run: ${{ env.DOCKER_MANIFEST_GENERATOR }} + + - name: Validate Layers (linux/amd64) + run: | + if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.layers.json" >/dev/null 2>&1; then + echo "::group::Layer Differences" + diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.layers.json" + echo "::endgroup::" + exit 1 + fi + + - name: Validate Layers (linux/arm64) + run: | + if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.layers.json" >/dev/null 2>&1; then + echo "::group::Layer Differences" + diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.layers.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.layers.json" + echo "::endgroup::" + exit 1 + fi + + - name: Validate Full Manifest (linux/amd64) + run: | + if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.comparable.json" >/dev/null 2>&1; then + echo "::group::Layer Differences" + diff -u "${DOCKER_MANIFEST_PATH}/linux-amd64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-amd64.comparable.json" + echo "::endgroup::" + exit 1 + fi + + - name: Validate Full Manifest (linux/arm64) + run: | + if ! diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.comparable.json" >/dev/null 2>&1; then + echo "::group::Layer Differences" + diff -u "${DOCKER_MANIFEST_PATH}/linux-arm64.comparable.json" "${{ steps.regen-manifest.outputs.path }}/linux-arm64.comparable.json" + echo "::endgroup::" + exit 1 + fi + + - name: Publish Manifests + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + if: ${{ steps.regen-manifest.conclusion == 'success' && failure() && !cancelled() }} + with: + name: Docker Manifests [${{ join(matrix.os, ', ') }}] + path: ${{ env.DOCKER_MANIFEST_PATH }}/** diff --git a/README.md b/README.md index c90a4cec05e8..5f4289dcdd05 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ -[![Build Application](https://github.com/hashgraph/hedera-services/actions/workflows/node-flow-build-application.yaml/badge.svg?branch=develop)](https://github.com/hashgraph/hedera-services/actions/workflows/flow-build-application.yaml) -[![codecov](https://codecov.io/github/hashgraph/hedera-services/coverage.svg?branch=master&token=ZPMV8C93DV)](README.md) +[![Node: Build Application](https://github.com/hashgraph/hedera-services/actions/workflows/node-flow-build-application.yaml/badge.svg)](https://github.com/hashgraph/hedera-services/actions/workflows/node-flow-build-application.yaml) +[![Artifact Determinism](https://github.com/hashgraph/hedera-services/actions/workflows/flow-artifact-determinism.yaml/badge.svg)](https://github.com/hashgraph/hedera-services/actions/workflows/flow-artifact-determinism.yaml) +[![Node: Performance Tests](https://github.com/hashgraph/hedera-services/actions/workflows/flow-node-performance-tests.yaml/badge.svg)](https://github.com/hashgraph/hedera-services/actions/workflows/flow-node-performance-tests.yaml) + +[![codecov](https://codecov.io/gh/hashgraph/hedera-services/graph/badge.svg?token=ZPMV8C93DV)](https://codecov.io/gh/hashgraph/hedera-services) [![Latest Version](https://img.shields.io/github/v/tag/hashgraph/hedera-services?sort=semver&label=version)](README.md) [![Made With](https://img.shields.io/badge/made_with-java-blue)](https://github.com/hashgraph/hedera-services/) [![Development Branch](https://img.shields.io/badge/docs-quickstart-green.svg)](hedera-node/docs/gradle-quickstart.md) @@ -15,7 +18,9 @@ nodes in the [Hedera public network](https://hedera.com). * _hedera-node/_ - implementation of Hedera services on the Platform. ## JVM -JDK 17 is required. The Temurin builds of [Eclipse Adoptium](https://adoptium.net/) are strongly recommended. +An [Eclipse Adoptium](https://adoptium.net/) build of the Java 21 JDK is required. If an Adoptium JDK is not installed, +the Gradle build will download an appropriate Adoptium JDK. The JDK version used to execute Gradle must be Java 21+ in +order for the `checkAllModuleInfo` task to succeed. ## Solidity Hedera Contracts support `pragma solidity <=0.8.9`. From dc495c5b19ab4b5ccff234d87ad813edcecdfefc Mon Sep 17 00:00:00 2001 From: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Date: Fri, 29 Dec 2023 08:47:30 -0600 Subject: [PATCH 57/80] chore: replacing `minGenNonAncient` data flow with `NonAncientEventWindow` (#10597) Signed-off-by: Edward Wertz Signed-off-by: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Co-authored-by: Cody Littley <56973212+cody-littley@users.noreply.github.com> --- .../ConsistencyTestingToolRoundTests.java | 8 +- .../com/swirlds/platform/ConsensusImpl.java | 3 + .../com/swirlds/platform/SwirldsPlatform.java | 26 +++- .../platform/components/EventIntake.java | 12 +- .../components/LinkedEventIntake.java | 14 +- .../consensus/NonAncientEventWindow.java | 141 ++++++++++++++++++ .../creation/AsyncEventCreationManager.java | 26 ++-- .../event/creation/EventCreationManager.java | 9 +- .../creation/EventCreationManagerFactory.java | 4 +- .../platform/event/creation/EventCreator.java | 7 +- .../creation/SyncEventCreationManager.java | 9 +- .../tipset/ChildlessEventTracker.java | 7 +- .../creation/tipset/TipsetEventCreator.java | 20 +-- .../event/creation/tipset/TipsetTracker.java | 29 ++-- .../tipset/TipsetWeightCalculator.java | 2 +- .../deduplication/EventDeduplicator.java | 17 ++- .../platform/event/linking/InOrderLinker.java | 20 +-- .../platform/event/orphan/OrphanBuffer.java | 23 +-- .../validation/EventSignatureValidator.java | 15 +- .../shadowgraph/LatestEventTipsetTracker.java | 9 +- .../platform/internal/ConsensusRound.java | 23 ++- .../system/events/EventConstants.java | 2 + .../wiring/EventDeduplicatorWiring.java | 19 +-- .../wiring/EventSignatureValidatorWiring.java | 19 +-- .../platform/wiring/InOrderLinkerWiring.java | 19 +-- .../wiring/LinkedEventIntakeWiring.java | 19 +-- .../platform/wiring/OrphanBufferWiring.java | 11 +- .../platform/wiring/PlatformWiring.java | 45 +++--- .../EventCreationManagerWiring.java | 15 +- .../swirlds/platform/ConsensusRoundTests.java | 6 +- .../event/EventDeduplicatorTests.java | 8 +- .../event/linking/InOrderLinkerTests.java | 18 ++- .../event/orphan/OrphanBufferTests.java | 9 +- .../EventSignatureValidatorTests.java | 5 +- .../swirlds/platform/uptime/UptimeTests.java | 8 +- .../test/chatter/ChatterNotifierTest.java | 2 + .../test/components/EventIntakeTest.java | 2 + .../EventObserverDispatcherTests.java | 2 + .../TransactionHandlingTestUtils.java | 2 + .../tipset/ChildlessEventTrackerTests.java | 4 +- .../event/tipset/TipsetEventCreatorTests.java | 3 +- .../test/event/tipset/TipsetTrackerTests.java | 8 +- .../tipset/TipsetWeightCalculatorTests.java | 7 +- 43 files changed, 466 insertions(+), 191 deletions(-) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/NonAncientEventWindow.java diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/test/java/com/swirlds/demo/consistency/ConsistencyTestingToolRoundTests.java b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/test/java/com/swirlds/demo/consistency/ConsistencyTestingToolRoundTests.java index e1d925ebb68d..632857d957dd 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/test/java/com/swirlds/demo/consistency/ConsistencyTestingToolRoundTests.java +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/src/test/java/com/swirlds/demo/consistency/ConsistencyTestingToolRoundTests.java @@ -24,6 +24,7 @@ import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.GraphGenerations; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; @@ -82,7 +83,12 @@ private static Round buildMockRound(final List> eventContents, final Mockito.when(mockSnapshot.round()).thenReturn(roundReceived); return new ConsensusRound( - mock(AddressBook.class), mockEvents, mock(EventImpl.class), mock(GraphGenerations.class), mockSnapshot); + mock(AddressBook.class), + mockEvents, + mock(EventImpl.class), + mock(GraphGenerations.class), + mock(NonAncientEventWindow.class), + mockSnapshot); } @Test diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java index cead95025e11..b8a00deaa817 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ConsensusImpl.java @@ -32,6 +32,7 @@ import com.swirlds.platform.consensus.ConsensusUtils; import com.swirlds.platform.consensus.CountingVote; import com.swirlds.platform.consensus.InitJudges; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.consensus.RoundElections; import com.swirlds.platform.consensus.SequentialRingBuffer; import com.swirlds.platform.consensus.ThreadSafeConsensusInfo; @@ -655,6 +656,8 @@ private List getStronglySeenInPreviousRound(final EventImpl event) { consensusEvents, recentEvents.get(recentEvents.size() - 1), new Generations(this), + NonAncientEventWindow.createUsingRoundsNonAncient( + decidedRoundNumber, getMinGenerationNonAncient(), config.roundsNonAncient()), new ConsensusSnapshot( decidedRoundNumber, ConsensusUtils.getHashes(judges), diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 4f37234401b8..31534c6facb3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -74,6 +74,7 @@ import com.swirlds.platform.components.transaction.system.PreconsensusSystemTransactionManager; import com.swirlds.platform.config.ThreadConfig; import com.swirlds.platform.consensus.ConsensusConfig; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.crypto.PlatformSigner; @@ -904,7 +905,13 @@ public class SwirldsPlatform implements Platform { if (eventConfig.useLegacyIntake()) { eventLinker.loadFromSignedState(initialState); } else { - platformWiring.updateMinimumGenerationNonAncient(initialMinimumGenerationNonAncient); + platformWiring.updateNonAncientEventWindow(NonAncientEventWindow.createUsingRoundsNonAncient( + initialState.getRound(), + initialMinimumGenerationNonAncient, + platformContext + .getConfiguration() + .getConfigData(ConsensusConfig.class) + .roundsNonAncient())); } // We don't want to invoke these callbacks until after we are starting up. @@ -1054,8 +1061,13 @@ private void loadStateIntoEventCreator(@NonNull final SignedState signedState) { } try { - eventCreator.setMinimumGenerationNonAncient( - signedState.getState().getPlatformState().getMinimumGenerationNonAncient()); + eventCreator.setNonAncientEventWindow(NonAncientEventWindow.createUsingRoundsNonAncient( + signedState.getRound(), + signedState.getState().getPlatformState().getMinimumGenerationNonAncient(), + platformContext + .getConfiguration() + .getConfigData(ConsensusConfig.class) + .roundsNonAncient())); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("interrupted while loading state into event creator", e); @@ -1152,7 +1164,13 @@ private void loadReconnectState(final SignedState signedState) { .inject(new AddressBookUpdate( signedState.getState().getPlatformState().getPreviousAddressBook(), signedState.getState().getPlatformState().getAddressBook())); - platformWiring.updateMinimumGenerationNonAncient(signedState.getMinRoundGeneration()); + platformWiring.updateNonAncientEventWindow(NonAncientEventWindow.createUsingRoundsNonAncient( + signedState.getRound(), + signedState.getMinRoundGeneration(), + platformContext + .getConfiguration() + .getConfigData(ConsensusConfig.class) + .roundsNonAncient())); } } finally { intakeQueue.resume(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java index 69e9b633362d..42197fc3674d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java @@ -25,6 +25,8 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.threading.manager.ThreadManager; import com.swirlds.platform.Consensus; +import com.swirlds.platform.consensus.ConsensusConfig; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.EventLinker; import com.swirlds.platform.event.validation.StaticValidators; @@ -79,6 +81,7 @@ public class EventIntake { private final EventIntakeMetrics metrics; private final Time time; + private final Long roundsNonAncient; /** * Measures the time spent in each phase of event intake @@ -137,6 +140,10 @@ public EventIntake( this.intakeEventCounter = Objects.requireNonNull(intakeEventCounter); final EventConfig eventConfig = platformContext.getConfiguration().getConfigData(EventConfig.class); + this.roundsNonAncient = (long) platformContext + .getConfiguration() + .getConfigData(ConsensusConfig.class) + .roundsNonAncient(); final BlockingQueue prehandlePoolQueue = new LinkedBlockingQueue<>(); prehandlePool = new ThreadPoolExecutor( @@ -214,7 +221,10 @@ public void addEvent(final EventImpl event) { phaseTimer.activatePhase(EventIntakePhase.HANDLING_STALE_EVENTS); handleStale(minGenNonAncientBeforeAdding); if (latestEventTipsetTracker != null) { - latestEventTipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + // FUTURE WORK: When this class is refactored, it should not be constructing the + // NonAncientEventWindow, but receiving it through the PlatformWiring instead. + latestEventTipsetTracker.setNonAncientEventWindow(NonAncientEventWindow.createUsingRoundsNonAncient( + consensus().getLastRoundDecided(), minimumGenerationNonAncient, roundsNonAncient)); } } } finally { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java index 8a5e75832c95..adbca1264bf7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/LinkedEventIntake.java @@ -19,6 +19,8 @@ import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; import com.swirlds.platform.Consensus; +import com.swirlds.platform.consensus.ConsensusConfig; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.eventhandling.ConsensusRoundHandler; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; @@ -61,6 +63,7 @@ public class LinkedEventIntake { private final EventIntakeMetrics metrics; private final Time time; + private final Long roundsNonAncient; /** * Tracks the number of events from each peer have been received, but aren't yet through the intake pipeline @@ -104,6 +107,10 @@ public LinkedEventIntake( this.paused = false; metrics = new EventIntakeMetrics(platformContext, () -> -1); + this.roundsNonAncient = (long) platformContext + .getConfiguration() + .getConfigData(ConsensusConfig.class) + .roundsNonAncient(); } /** @@ -150,7 +157,12 @@ public List addEvent(@NonNull final EventImpl event) { // with no consensus events, so we check the diff in generations to look for stale events handleStale(minimumGenerationNonAncientBeforeAdding); if (latestEventTipsetTracker != null) { - latestEventTipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + // FUTURE WORK: When this class is refactored, it should not be constructing the + // NonAncientEventWindow, but receiving it through the PlatformWiring instead. + latestEventTipsetTracker.setNonAncientEventWindow(NonAncientEventWindow.createUsingRoundsNonAncient( + consensusSupplier.get().getLastRoundDecided(), + minimumGenerationNonAncient, + roundsNonAncient)); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/NonAncientEventWindow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/NonAncientEventWindow.java new file mode 100644 index 000000000000..9481cf6d5c5f --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/consensus/NonAncientEventWindow.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.swirlds.platform.consensus; + +import com.swirlds.base.utility.ToStringBuilder; +import com.swirlds.platform.event.GossipEvent; +import com.swirlds.platform.system.events.EventConstants; +import com.swirlds.platform.system.events.EventDescriptor; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Determines the non-ancient lower bound (inclusive) on events and communicates the window of rounds between the + * pendingConsensusRound and the minimumRoundNonAncient (inclusive). + */ +public class NonAncientEventWindow { + + /** + * The initial NonAncientEventWindow. This constant is used to initialize NonAncientEventWindow variables before + * receiving an updated value. + */ + public static NonAncientEventWindow INITIAL_EVENT_WINDOW = new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, ConsensusConstants.ROUND_FIRST, EventConstants.FIRST_GENERATION); + + private final long latestConsensusRound; + private final long minRoundNonAncient; + private final long minGenNonAncient; + + /** + * Create a new NonAncientEventWindow with the given bounds. The latestConsensusRound must be greater than or equal + * to the first round of consensus. If the minimum round non-ancient is set to a number lower than the first round + * of consensus, the first round of consensus is used instead. The minGenNonAncient value must be greater than or + * equal to the first generation for events. + * + * @param latestConsensusRound the latest round that has come to consensus + * @param minRoundNonAncient the minimum round that is non-ancient + * @param minGenNonAncient the minimum generation that is non-ancient + * @throws IllegalArgumentException if the latestConsensusRound is less than the first round of consensus or if the + * minGenNonAncient value is less than the first generation for events. + */ + public NonAncientEventWindow( + final long latestConsensusRound, final long minRoundNonAncient, final long minGenNonAncient) { + if (latestConsensusRound < ConsensusConstants.ROUND_FIRST) { + throw new IllegalArgumentException( + "The latest consensus round cannot be less than the first round of consensus."); + } + if (minGenNonAncient < EventConstants.FIRST_GENERATION) { + throw new IllegalArgumentException( + "the minimum generation non-ancient cannot be lower than the first generation for events."); + } + this.latestConsensusRound = latestConsensusRound; + this.minRoundNonAncient = Math.max(minRoundNonAncient, ConsensusConstants.ROUND_FIRST); + this.minGenNonAncient = minGenNonAncient; + } + + /** + * @return the pending round coming to consensus, i.e. 1 + the latestConsensusRound + */ + public long pendingConsensusRound() { + return latestConsensusRound + 1; + } + + /** + * @return the lower bound of the non-ancient event window + */ + public long getLowerBound() { + // FUTURE WORK: return minRoundNonAncient once we switch from minGenNonAncient. + return minGenNonAncient; + } + + /** + * Determines if the given event is ancient. + * + * @param event the event to check for being ancient. + * @return true if the event is ancient, false otherwise. + */ + public boolean isAncient(@NonNull final GossipEvent event) { + // FUTURE WORK: use generation until we throw the switch to using round + return event.getGeneration() < minGenNonAncient; + } + + /** + * Determines if the given event is ancient. + * + * @param event the event to check for being ancient. + * @return true if the event is ancient, false otherwise. + */ + public boolean isAncient(@NonNull final EventDescriptor event) { + // FUTURE WORK: use generation until we throw the switch to using round + return event.getGeneration() < minGenNonAncient; + } + + /** + * Determines if the given long value is ancient. + * + * @param testValue the value to check for being ancient. + * @return true if the value is ancient, false otherwise. + */ + public boolean isAncient(final long testValue) { + // FUTURE WORK: use generation until we throw the switch to using round + return testValue < minGenNonAncient; + } + + /** + * Create a NonAncientEventWindow by calculating the minRoundNonAncient value from the latestConsensusRound and + * roundsNonAncient. + * + * @param latestConsensusRound the latest round that has come to consensus + * @param minGenNonAncient the minimum generation that is non-ancient + * @param roundsNonAncient the number of rounds that are non-ancient + * @return the new NonAncientEventWindow + */ + @NonNull + public static NonAncientEventWindow createUsingRoundsNonAncient( + final long latestConsensusRound, final long minGenNonAncient, final long roundsNonAncient) { + return new NonAncientEventWindow( + latestConsensusRound, latestConsensusRound - roundsNonAncient + 1, minGenNonAncient); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .append("latestConsensusRound", latestConsensusRound) + .append("minRoundNonAncient", minRoundNonAncient) + .append("minGenNonAncient", minGenNonAncient) + .toString(); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/AsyncEventCreationManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/AsyncEventCreationManager.java index c91179edb852..adb50a136a5c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/AsyncEventCreationManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/AsyncEventCreationManager.java @@ -29,6 +29,7 @@ import com.swirlds.common.threading.framework.config.QueueThreadMetricsConfiguration; import com.swirlds.common.threading.futures.StandardFuture; import com.swirlds.common.threading.manager.ThreadManager; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.internal.EventImpl; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Objects; @@ -60,9 +61,9 @@ public class AsyncEventCreationManager implements Lifecycle { private final BlockingQueueInserter eventInserter; /** - * The object used to enqueue updates to the minimum generation non-ancient onto the work queue. + * The object used to enqueue updates to the non-ancient event window onto the work queue. */ - private final BlockingQueueInserter minimumGenerationNonAncientInserter; + private final BlockingQueueInserter nonAncientEventWindowInserter; /** * Used to signal a desired pause. @@ -102,7 +103,7 @@ public AsyncEventCreationManager( .setCapacity(eventCreationConfig.creationQueueSize()) .setMaxBufferSize(eventCreationConfig.creationQueueBufferSize()) .addHandler(EventImpl.class, this::handleEvent) - .addHandler(Long.class, this::handleMinimumGenerationNonAncient) + .addHandler(NonAncientEventWindow.class, this::handleNonAncientEventWindow) .addHandler(PauseRequest.class, this::handlePauseStatusChange) .setIdleCallback(eventCreator::maybeCreateEvent) .setBatchHandledCallback(eventCreator::maybeCreateEvent) @@ -113,7 +114,7 @@ public AsyncEventCreationManager( .build(); eventInserter = workQueue.getInserter(EventImpl.class); - minimumGenerationNonAncientInserter = workQueue.getInserter(Long.class); + nonAncientEventWindowInserter = workQueue.getInserter(NonAncientEventWindow.class); setPauseStatusInserter = workQueue.getInserter(PauseRequest.class); } @@ -128,12 +129,13 @@ public void registerEvent(@NonNull final EventImpl event) throws InterruptedExce } /** - * Update the minimum generation non-ancient + * Update the non-ancient event window * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the new minimum generation non-ancient */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) throws InterruptedException { - minimumGenerationNonAncientInserter.put(minimumGenerationNonAncient); + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) + throws InterruptedException { + nonAncientEventWindowInserter.put(nonAncientEventWindow); } /** @@ -146,12 +148,12 @@ private void handleEvent(@NonNull final EventImpl event) { } /** - * Pass a new minimum generation non-ancient into the event creator. + * Pass a new non-ancient event window into the event creator. * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the new non-ancient event window */ - private void handleMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - eventCreator.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + private void handleNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + eventCreator.setNonAncientEventWindow(nonAncientEventWindow); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManager.java index 6e4f23b954a1..bd0ad11dfb66 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManager.java @@ -25,6 +25,7 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.metrics.extensions.PhaseTimer; import com.swirlds.common.metrics.extensions.PhaseTimerBuilder; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.rules.EventCreationRule; import edu.umd.cs.findbugs.annotations.NonNull; @@ -134,11 +135,11 @@ public void registerEvent(@NonNull final GossipEvent event) { } /** - * Update the minimum generation non-ancient. + * Update the non-ancient event window, defining the minimum threshold for an event to be non-ancient. * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the non-ancient event window */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - creator.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + creator.setNonAncientEventWindow(nonAncientEventWindow); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java index 29300f7a40b4..2241ed7ec30f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreationManagerFactory.java @@ -123,8 +123,8 @@ public static AsyncEventCreationManager buildLegacyEventCreationManager( "Interrupted while attempting to register event with tipset event creator")); eventObserverDispatcher.addObserver((ConsensusRoundObserver) round -> abortAndThrowIfInterrupted( - manager::setMinimumGenerationNonAncient, - round.getGenerations().getMinGenerationNonAncient(), + manager::setNonAncientEventWindow, + round.getNonAncientEventWindow(), "Interrupted while attempting to register minimum generation " + "non-ancient with tipset event creator")); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreator.java index 37f839c33ec3..11fa530790bd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/EventCreator.java @@ -16,6 +16,7 @@ package com.swirlds.platform.event.creation; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -33,11 +34,11 @@ public interface EventCreator { void registerEvent(@NonNull GossipEvent event); /** - * Update the minimum generation non-ancient. + * Update the non-ancient event window. * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the new non-ancient event window */ - void setMinimumGenerationNonAncient(long minimumGenerationNonAncient); + void setNonAncientEventWindow(@NonNull NonAncientEventWindow nonAncientEventWindow); /** * Create a new event if it is legal to do so. The only time this should not create an event is if there are no diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/SyncEventCreationManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/SyncEventCreationManager.java index b12bf008fec5..a66d082c796a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/SyncEventCreationManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/SyncEventCreationManager.java @@ -26,6 +26,7 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.metrics.extensions.PhaseTimer; import com.swirlds.common.metrics.extensions.PhaseTimerBuilder; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.rules.EventCreationRule; import edu.umd.cs.findbugs.annotations.NonNull; @@ -179,11 +180,11 @@ public void registerEvent(@NonNull final GossipEvent event) { } /** - * Update the minimum generation non-ancient. + * Update the non-ancient event window * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the new non-ancient event window */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - creator.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + creator.setNonAncientEventWindow(nonAncientEventWindow); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/ChildlessEventTracker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/ChildlessEventTracker.java index be71ac170dca..1831b3f18780 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/ChildlessEventTracker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/ChildlessEventTracker.java @@ -17,6 +17,7 @@ package com.swirlds.platform.event.creation.tipset; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.system.events.EventDescriptor; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; @@ -80,11 +81,11 @@ public void registerSelfEventParents(@NonNull final List parent /** * Remove ancient events. * - * @param minimumGenerationNonAncient the minimum generation of non-ancient events + * @param nonAncientEventWindow the non-ancient event window */ - public void pruneOldEvents(final long minimumGenerationNonAncient) { + public void pruneOldEvents(@NonNull final NonAncientEventWindow nonAncientEventWindow) { for (final EventDescriptor event : getChildlessEvents()) { - if (event.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(event)) { removeEvent(event); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetEventCreator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetEventCreator.java index 6847c3180cd9..20f9b668d9a5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetEventCreator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetEventCreator.java @@ -17,7 +17,6 @@ package com.swirlds.platform.event.creation.tipset; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; -import static com.swirlds.platform.consensus.GraphGenerations.FIRST_GENERATION; import static com.swirlds.platform.event.creation.tipset.TipsetAdvancementWeight.ZERO_ADVANCEMENT_WEIGHT; import static com.swirlds.platform.event.creation.tipset.TipsetUtils.getParentDescriptors; import static com.swirlds.platform.system.events.EventConstants.CREATOR_ID_UNDEFINED; @@ -31,6 +30,7 @@ import com.swirlds.common.stream.Signer; import com.swirlds.common.utility.throttle.RateLimitedLogger; import com.swirlds.platform.components.transaction.TransactionSupplier; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.EventUtils; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.EventCreationConfig; @@ -69,7 +69,7 @@ public class TipsetEventCreator implements EventCreator { private final ChildlessEventTracker childlessOtherEventTracker; private final TransactionSupplier transactionSupplier; private final SoftwareVersion softwareVersion; - private long minimumGenerationNonAncient = FIRST_GENERATION; + private NonAncientEventWindow nonAncientEventWindow = NonAncientEventWindow.INITIAL_EVENT_WINDOW; /** * The address book for the current network. @@ -156,7 +156,7 @@ public TipsetEventCreator( */ @Override public void registerEvent(@NonNull final GossipEvent event) { - if (event.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(event)) { return; } @@ -197,10 +197,10 @@ public void registerEvent(@NonNull final GossipEvent event) { * {@inheritDoc} */ @Override - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - this.minimumGenerationNonAncient = minimumGenerationNonAncient; - tipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); - childlessOtherEventTracker.pruneOldEvents(minimumGenerationNonAncient); + public void setNonAncientEventWindow(@NonNull NonAncientEventWindow nonAncientEventWindow) { + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); + tipsetTracker.setNonAncientEventWindow(nonAncientEventWindow); + childlessOtherEventTracker.pruneOldEvents(nonAncientEventWindow); } /** @@ -274,8 +274,8 @@ private GossipEvent createEventByOptimizingAdvancementWeight() { if (bestOtherParent == null) { // If there are no available other parents, it is only legal to create a new event if we are // creating a genesis event. In order to create a genesis event, we must have never created - // an event before and the current minimum generation non ancient must have never been advanced. - if (minimumGenerationNonAncient > GENERATION_UNDEFINED + 1 || lastSelfEvent != null) { + // an event before and the current non-ancient event window must have never been advanced. + if (nonAncientEventWindow != NonAncientEventWindow.INITIAL_EVENT_WINDOW || lastSelfEvent != null) { // event creation isn't legal return null; } @@ -474,7 +474,7 @@ private static NodeId getCreator(@Nullable final EventDescriptor descriptor) { public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Minimum generation non-ancient: ") - .append(tipsetTracker.getMinimumGenerationNonAncient()) + .append(tipsetTracker.getNonAncientEventWindow()) .append("\n"); sb.append("Latest self event: ").append(lastSelfEvent).append("\n"); sb.append(tipsetWeightCalculator); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetTracker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetTracker.java index ff4251beebb2..98a4ec76376f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetTracker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetTracker.java @@ -24,6 +24,7 @@ import com.swirlds.common.sequence.map.SequenceMap; import com.swirlds.common.sequence.map.StandardSequenceMap; import com.swirlds.common.utility.throttle.RateLimitedLogger; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.EventDescriptor; import edu.umd.cs.findbugs.annotations.NonNull; @@ -58,7 +59,7 @@ public class TipsetTracker { private final AddressBook addressBook; - private long minimumGenerationNonAncient; + private NonAncientEventWindow nonAncientEventWindow = NonAncientEventWindow.INITIAL_EVENT_WINDOW; private final RateLimitedLogger ancientEventLogger; @@ -80,22 +81,23 @@ public TipsetTracker(@NonNull final Time time, @NonNull final AddressBook addres } /** - * Set the minimum generation that is not considered ancient. + * Set the non-ancient event window. * - * @param minimumGenerationNonAncient the minimum non-ancient generation, all lower generations are ancient + * @param nonAncientEventWindow the minimum non-ancient generation, all lower generations are ancient */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - tipsets.shiftWindow(minimumGenerationNonAncient); - this.minimumGenerationNonAncient = minimumGenerationNonAncient; + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); + tipsets.shiftWindow(nonAncientEventWindow.getLowerBound()); } /** - * Get the minimum generation that is not considered ancient (from this class's perspective). + * Get the current non-ancient event window (from this class's perspective). * - * @return the minimum non-ancient generation, all lower generations are ancient + * @return the non-ancient event window */ - public long getMinimumGenerationNonAncient() { - return minimumGenerationNonAncient; + @NonNull + public NonAncientEventWindow getNonAncientEventWindow() { + return nonAncientEventWindow; } /** @@ -109,17 +111,16 @@ public long getMinimumGenerationNonAncient() { public Tipset addEvent( @NonNull final EventDescriptor eventDescriptor, @NonNull final List parents) { - if (eventDescriptor.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(eventDescriptor)) { // Note: although we don't immediately return from this method, the tipsets.put() // will not update the data structure for an ancient event. We should never // enter this bock of code. This log is here as a canary to alert us if we somehow do. ancientEventLogger.error( EXCEPTION.getMarker(), - "Rejecting ancient event from {} with generation {}. " - + "Current minimum generation non-ancient is {}", + "Rejecting ancient event from {} with generation {}. Current non-ancient event window is {}", eventDescriptor.getCreator(), eventDescriptor.getGeneration(), - minimumGenerationNonAncient); + nonAncientEventWindow); } final List parentTipsets = new ArrayList<>(parents.size()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetWeightCalculator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetWeightCalculator.java index ab0e7011d929..e5f914b269dd 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetWeightCalculator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/TipsetWeightCalculator.java @@ -236,7 +236,7 @@ public TipsetAdvancementWeight getTheoreticalAdvancementWeight(@NonNull final Li + "Parent ID = {}, parent generation = {}, minimum generation non-ancient = {}", parent.getCreator(), parent.getGeneration(), - tipsetTracker.getMinimumGenerationNonAncient()); + tipsetTracker.getNonAncientEventWindow()); } continue; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/EventDeduplicator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/EventDeduplicator.java index 94f7c062128e..6f7f73eb2c91 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/EventDeduplicator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/deduplication/EventDeduplicator.java @@ -22,6 +22,7 @@ import com.swirlds.common.metrics.LongAccumulator; import com.swirlds.common.sequence.map.SequenceMap; import com.swirlds.common.sequence.map.StandardSequenceMap; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.metrics.EventIntakeMetrics; @@ -53,9 +54,9 @@ public class EventDeduplicator { private static final int INITIAL_CAPACITY = 1024; /** - * The current minimum generation required for an event to be non-ancient. + * The current non-ancient event window. */ - private long minimumGenerationNonAncient = 0; + private NonAncientEventWindow nonAncientEventWindow = NonAncientEventWindow.INITIAL_EVENT_WINDOW; /** * Keeps track of the number of events in the intake pipeline from each peer @@ -111,7 +112,7 @@ public EventDeduplicator( */ @Nullable public GossipEvent handleEvent(@NonNull final GossipEvent event) { - if (event.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(event)) { // Ancient events can be safely ignored. intakeEventCounter.eventExitedIntakePipeline(event.getSenderId()); return null; @@ -137,14 +138,14 @@ public GossipEvent handleEvent(@NonNull final GossipEvent event) { } /** - * Set the minimum generation required for an event to be non-ancient. + * Set the NonAncientEventWindow, defines the minimum threshold for an event to be non-ancient. * - * @param minimumGenerationNonAncient the minimum generation required for an event to be non-ancient + * @param nonAncientEventWindow the non-ancient event window */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - this.minimumGenerationNonAncient = minimumGenerationNonAncient; + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); - observedEvents.shiftWindow(minimumGenerationNonAncient); + observedEvents.shiftWindow(nonAncientEventWindow.getLowerBound()); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java index 4457f843655b..9806b572f8d0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/linking/InOrderLinker.java @@ -27,6 +27,7 @@ import com.swirlds.common.sequence.map.StandardSequenceMap; import com.swirlds.common.utility.throttle.RateLimitedLogger; import com.swirlds.platform.EventStrings; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.internal.EventImpl; @@ -94,9 +95,9 @@ public class InOrderLinker { private final Map parentHashMap = new HashMap<>(INITIAL_CAPACITY); /** - * The current minimum generation required for an event to be non-ancient. + * The current non-ancient event window. */ - private long minimumGenerationNonAncient = 0; + private NonAncientEventWindow nonAncientEventWindow = NonAncientEventWindow.INITIAL_EVENT_WINDOW; /** * Keeps track of the number of events in the intake pipeline from each peer @@ -158,7 +159,7 @@ public InOrderLinker( private EventImpl getParentToLink( @NonNull final GossipEvent child, @NonNull final Hash parentHash, final long claimedParentGeneration) { - if (claimedParentGeneration < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(claimedParentGeneration)) { // ancient parents don't need to be linked return null; } @@ -220,7 +221,7 @@ private EventImpl getParentToLink( */ @Nullable public EventImpl linkEvent(@NonNull final GossipEvent event) { - if (event.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(event)) { // This event is ancient, so we don't need to link it. this.intakeEventCounter.eventExitedIntakePipeline(event.getSenderId()); return null; @@ -242,15 +243,16 @@ public EventImpl linkEvent(@NonNull final GossipEvent event) { } /** - * Set the minimum generation required for an event to be non-ancient. + * Set the non-ancient event window, defining the minimum non-ancient threshold. * - * @param minimumGenerationNonAncient the minimum generation required for an event to be non-ancient + * @param nonAncientEventWindow the non-ancient event window */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - this.minimumGenerationNonAncient = minimumGenerationNonAncient; + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); parentDescriptorMap.shiftWindow( - minimumGenerationNonAncient, (descriptor, event) -> parentHashMap.remove(descriptor.getHash())); + nonAncientEventWindow.getLowerBound(), + (descriptor, event) -> parentHashMap.remove(descriptor.getHash())); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/orphan/OrphanBuffer.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/orphan/OrphanBuffer.java index faca16a18a6e..607c53d67261 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/orphan/OrphanBuffer.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/orphan/OrphanBuffer.java @@ -24,6 +24,7 @@ import com.swirlds.common.sequence.map.StandardSequenceMap; import com.swirlds.common.sequence.set.SequenceSet; import com.swirlds.common.sequence.set.StandardSequenceSet; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.system.events.EventDescriptor; @@ -53,9 +54,9 @@ public class OrphanBuffer { private static final Function> EMPTY_LIST = ignored -> new ArrayList<>(); /** - * The current minimum generation required for an event to be non-ancient. + * The current non-ancient event window. */ - private long minimumGenerationNonAncient = 0; + private NonAncientEventWindow nonAncientEventWindow = NonAncientEventWindow.INITIAL_EVENT_WINDOW; /** * The number of orphans currently in the buffer. @@ -112,7 +113,7 @@ public OrphanBuffer( */ @NonNull public List handleEvent(@NonNull final GossipEvent event) { - if (event.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(event)) { // Ancient events can be safely ignored. intakeEventCounter.eventExitedIntakePipeline(event.getSenderId()); return List.of(); @@ -134,23 +135,23 @@ public List handleEvent(@NonNull final GossipEvent event) { } /** - * Set the minimum generation of non-ancient events to keep in the buffer. + * Sets the non-ancient event window that defines when an event is considered ancient. * - * @param minimumGenerationNonAncient the minimum generation of non-ancient events to keep in the buffer + * @param nonAncientEventWindow the non-ancient event window * @return the list of events that are no longer orphans as a result of this change */ @NonNull - public List setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - this.minimumGenerationNonAncient = minimumGenerationNonAncient; + public List setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); - eventsWithParents.shiftWindow(minimumGenerationNonAncient); + eventsWithParents.shiftWindow(nonAncientEventWindow.getLowerBound()); // As the map is cleared out, we need to gather the ancient parents and their orphans. We can't // modify the data structure as the window is being shifted, so we collect that data and act on // it once the window has finished shifting. final List ancientParents = new ArrayList<>(); missingParentMap.shiftWindow( - minimumGenerationNonAncient, + nonAncientEventWindow.getLowerBound(), (parent, orphans) -> ancientParents.add(new ParentAndOrphans(parent, orphans))); final List unorphanedEvents = new ArrayList<>(); @@ -198,7 +199,7 @@ private List getMissingParents(@NonNull final GossipEvent event final Iterator parentIterator = new ParentIterator(event); while (parentIterator.hasNext()) { final EventDescriptor parent = parentIterator.next(); - if (!eventsWithParents.contains(parent) && parent.getGeneration() >= minimumGenerationNonAncient) { + if (!eventsWithParents.contains(parent) && !nonAncientEventWindow.isAncient(parent)) { missingParents.add(parent); } } @@ -230,7 +231,7 @@ private List eventIsNotAnOrphan(@NonNull final GossipEvent event) { final GossipEvent nonOrphan = nonOrphanStack.pop(); final EventDescriptor nonOrphanDescriptor = nonOrphan.getDescriptor(); - if (nonOrphan.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(nonOrphan)) { // Although it doesn't cause harm to pass along ancient events, it is unnecessary to do so. intakeEventCounter.eventExitedIntakePipeline(event.getSenderId()); continue; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/EventSignatureValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/EventSignatureValidator.java index 8de1f411512f..adf2e0ea1801 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/EventSignatureValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/validation/EventSignatureValidator.java @@ -25,6 +25,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.CommonUtils; import com.swirlds.common.utility.throttle.RateLimitedLogger; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.crypto.SignatureVerifier; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; @@ -73,9 +74,9 @@ public class EventSignatureValidator { private final SoftwareVersion currentSoftwareVersion; /** - * The current minimum generation required for an event to be non-ancient. + * The current non-ancient event window. */ - private long minimumGenerationNonAncient = 0; + private NonAncientEventWindow nonAncientEventWindow = NonAncientEventWindow.INITIAL_EVENT_WINDOW; /** * Keeps track of the number of events in the intake pipeline from each peer @@ -221,7 +222,7 @@ private boolean isSignatureValid(@NonNull final GossipEvent event) { */ @Nullable public GossipEvent validateSignature(@NonNull final GossipEvent event) { - if (event.getGeneration() < minimumGenerationNonAncient) { + if (nonAncientEventWindow.isAncient(event)) { // ancient events can be safely ignored intakeEventCounter.eventExitedIntakePipeline(event.getSenderId()); return null; @@ -238,12 +239,12 @@ public GossipEvent validateSignature(@NonNull final GossipEvent event) { } /** - * Set the minimum generation required for an event to be non-ancient. + * Set the non-ancient event window that defines the minimum threshold required for an event to be non-ancient * - * @param minimumGenerationNonAncient the minimum generation required for an event to be non-ancient + * @param nonAncientEventWindow the non-ancient event window */ - public void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - this.minimumGenerationNonAncient = minimumGenerationNonAncient; + public void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java index 4e3d8876e9ed..16279ecb8b57 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/LatestEventTipsetTracker.java @@ -18,6 +18,7 @@ import com.swirlds.base.time.Time; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.creation.tipset.Tipset; import com.swirlds.platform.event.creation.tipset.TipsetTracker; import com.swirlds.platform.internal.EventImpl; @@ -58,12 +59,12 @@ public LatestEventTipsetTracker( } /** - * Update the minimum generation non-ancient. + * Update the non-ancient event window * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the non-ancient event window */ - public synchronized void setMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - tipsetTracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + public synchronized void setNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + tipsetTracker.setNonAncientEventWindow(nonAncientEventWindow); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/ConsensusRound.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/ConsensusRound.java index 353ead7271b6..dc67f10b1d18 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/ConsensusRound.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/internal/ConsensusRound.java @@ -19,6 +19,7 @@ import com.swirlds.base.utility.ToStringBuilder; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.GraphGenerations; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.EventUtils; import com.swirlds.platform.system.Round; import com.swirlds.platform.system.address.AddressBook; @@ -37,6 +38,8 @@ public class ConsensusRound implements Round { private final List consensusEvents; /** the consensus generations when this round reached consensus */ private final GraphGenerations generations; + /** the non-ancient event window for this round */ + private final NonAncientEventWindow nonAncientEventWindow; /** The number of application transactions in this round */ private int numAppTransactions = 0; /** A snapshot of consensus at this consensus round */ @@ -52,23 +55,26 @@ public class ConsensusRound implements Round { /** * Create a new instance with the provided consensus events. * - * @param consensusRoster the consensus roster for this round - * @param consensusEvents the events in the round, in consensus order - * @param keystoneEvent the event that, when added to the hashgraph, caused this round to reach consensus - * @param generations the consensus generations for this round - * @param snapshot snapshot of consensus at this round + * @param consensusRoster the consensus roster for this round + * @param consensusEvents the events in the round, in consensus order + * @param keystoneEvent the event that, when added to the hashgraph, caused this round to reach consensus + * @param generations the consensus generations for this round + * @param nonAncientEventWindow the non-ancient event window for this round + * @param snapshot snapshot of consensus at this round */ public ConsensusRound( @NonNull final AddressBook consensusRoster, @NonNull final List consensusEvents, @NonNull final EventImpl keystoneEvent, @NonNull final GraphGenerations generations, + @NonNull final NonAncientEventWindow nonAncientEventWindow, @NonNull final ConsensusSnapshot snapshot) { this.consensusRoster = Objects.requireNonNull(consensusRoster); this.consensusEvents = Collections.unmodifiableList(Objects.requireNonNull(consensusEvents)); this.keystoneEvent = Objects.requireNonNull(keystoneEvent); this.generations = Objects.requireNonNull(generations); + this.nonAncientEventWindow = Objects.requireNonNull(nonAncientEventWindow); this.snapshot = Objects.requireNonNull(snapshot); for (final EventImpl e : consensusEvents) { @@ -101,6 +107,13 @@ public int getNumAppTransactions() { return generations; } + /** + * @return the non-ancient event window for this round + */ + public @NonNull NonAncientEventWindow getNonAncientEventWindow() { + return nonAncientEventWindow; + } + /** * @return a snapshot of consensus at this consensus round */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java index c5bd3aab9593..c97fa992f911 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/events/EventConstants.java @@ -37,4 +37,6 @@ private EventConstants() {} public static final long MINIMUM_ROUND_CREATED = 1; /** the round number to represent that the birth round is undefined */ public static final long BIRTH_ROUND_UNDEFINED = -1; + /** the minimum generation value an event can have. */ + public static final long FIRST_GENERATION = 0; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventDeduplicatorWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventDeduplicatorWiring.java index 816cb8f0191d..9b2214e8fe09 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventDeduplicatorWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventDeduplicatorWiring.java @@ -20,6 +20,7 @@ import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.deduplication.EventDeduplicator; import edu.umd.cs.findbugs.annotations.NonNull; @@ -27,15 +28,15 @@ /** * Wiring for the {@link EventDeduplicator}. * - * @param eventInput the input wire for events to be deduplicated - * @param minimumGenerationNonAncientInput the input wire for the minimum generation non-ancient - * @param clearInput the input wire to clear the internal state of the deduplicator - * @param eventOutput the output wire for deduplicated events - * @param flushRunnable the runnable to flush the deduplicator + * @param eventInput the input wire for events to be deduplicated + * @param nonAncientEventWindowInput the input wire for the minimum non-ancient threshold + * @param clearInput the input wire to clear the internal state of the deduplicator + * @param eventOutput the output wire for deduplicated events + * @param flushRunnable the runnable to flush the deduplicator */ public record EventDeduplicatorWiring( @NonNull InputWire eventInput, - @NonNull InputWire minimumGenerationNonAncientInput, + @NonNull InputWire nonAncientEventWindowInput, @NonNull InputWire clearInput, @NonNull OutputWire eventOutput, @NonNull Runnable flushRunnable) { @@ -49,7 +50,7 @@ public record EventDeduplicatorWiring( public static EventDeduplicatorWiring create(@NonNull final TaskScheduler taskScheduler) { return new EventDeduplicatorWiring( taskScheduler.buildInputWire("non-deduplicated events"), - taskScheduler.buildInputWire("minimum generation non ancient"), + taskScheduler.buildInputWire("non-ancient event window"), taskScheduler.buildInputWire("clear"), taskScheduler.getOutputWire(), taskScheduler::flush); @@ -62,8 +63,8 @@ public static EventDeduplicatorWiring create(@NonNull final TaskScheduler) eventInput).bind(deduplicator::handleEvent); - ((BindableInputWire) minimumGenerationNonAncientInput) - .bind(deduplicator::setMinimumGenerationNonAncient); + ((BindableInputWire) nonAncientEventWindowInput) + .bind(deduplicator::setNonAncientEventWindow); ((BindableInputWire) clearInput).bind(deduplicator::clear); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventSignatureValidatorWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventSignatureValidatorWiring.java index fe5af6282fc5..2edb50c4f63a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventSignatureValidatorWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/EventSignatureValidatorWiring.java @@ -20,6 +20,7 @@ import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.validation.AddressBookUpdate; import com.swirlds.platform.event.validation.EventSignatureValidator; @@ -28,15 +29,15 @@ /** * Wiring for the {@link EventSignatureValidator}. * - * @param eventInput the input wire for events with unvalidated signatures - * @param minimumGenerationNonAncientInput the input wire for the minimum generation non-ancient - * @param addressBookUpdateInput the input wire for address book updates - * @param eventOutput the output wire for events with validated signatures - * @param flushRunnable the runnable to flush the validator + * @param eventInput the input wire for events with unvalidated signatures + * @param nonAncientEventWindowInput the input wire for the minimum non-ancient threshold + * @param addressBookUpdateInput the input wire for address book updates + * @param eventOutput the output wire for events with validated signatures + * @param flushRunnable the runnable to flush the validator */ public record EventSignatureValidatorWiring( @NonNull InputWire eventInput, - @NonNull InputWire minimumGenerationNonAncientInput, + @NonNull InputWire nonAncientEventWindowInput, @NonNull InputWire addressBookUpdateInput, @NonNull OutputWire eventOutput, @NonNull Runnable flushRunnable) { @@ -50,7 +51,7 @@ public record EventSignatureValidatorWiring( public static EventSignatureValidatorWiring create(@NonNull final TaskScheduler taskScheduler) { return new EventSignatureValidatorWiring( taskScheduler.buildInputWire("events with unvalidated signatures"), - taskScheduler.buildInputWire("minimum generation non ancient"), + taskScheduler.buildInputWire("non-ancient event window"), taskScheduler.buildInputWire("address book update"), taskScheduler.getOutputWire(), taskScheduler::flush); @@ -63,8 +64,8 @@ public static EventSignatureValidatorWiring create(@NonNull final TaskScheduler< */ public void bind(@NonNull final EventSignatureValidator eventSignatureValidator) { ((BindableInputWire) eventInput).bind(eventSignatureValidator::validateSignature); - ((BindableInputWire) minimumGenerationNonAncientInput) - .bind(eventSignatureValidator::setMinimumGenerationNonAncient); + ((BindableInputWire) nonAncientEventWindowInput) + .bind(eventSignatureValidator::setNonAncientEventWindow); ((BindableInputWire) addressBookUpdateInput) .bind(eventSignatureValidator::updateAddressBooks); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/InOrderLinkerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/InOrderLinkerWiring.java index cd565d08e176..a6bbe8f99fba 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/InOrderLinkerWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/InOrderLinkerWiring.java @@ -20,6 +20,7 @@ import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.InOrderLinker; import com.swirlds.platform.internal.EventImpl; @@ -28,15 +29,15 @@ /** * Wiring for the {@link InOrderLinker}. * - * @param eventInput the input wire for events to be linked - * @param minimumGenerationNonAncientInput the input wire for the minimum generation non-ancient - * @param clearInput the input wire to clear the internal state of the linker - * @param eventOutput the output wire for linked events - * @param flushRunnable the runnable to flush the linker + * @param eventInput the input wire for events to be linked + * @param nonAncientEventWindowInput the input wire for the minimum generation non-ancient + * @param clearInput the input wire to clear the internal state of the linker + * @param eventOutput the output wire for linked events + * @param flushRunnable the runnable to flush the linker */ public record InOrderLinkerWiring( @NonNull InputWire eventInput, - @NonNull InputWire minimumGenerationNonAncientInput, + @NonNull InputWire nonAncientEventWindowInput, @NonNull InputWire clearInput, @NonNull OutputWire eventOutput, @NonNull Runnable flushRunnable) { @@ -50,7 +51,7 @@ public record InOrderLinkerWiring( public static InOrderLinkerWiring create(@NonNull final TaskScheduler taskScheduler) { return new InOrderLinkerWiring( taskScheduler.buildInputWire("unlinked events"), - taskScheduler.buildInputWire("minimum generation non ancient"), + taskScheduler.buildInputWire("non-ancient event window"), taskScheduler.buildInputWire("clear"), taskScheduler.getOutputWire(), taskScheduler::flush); @@ -63,8 +64,8 @@ public static InOrderLinkerWiring create(@NonNull final TaskScheduler */ public void bind(@NonNull final InOrderLinker inOrderLinker) { ((BindableInputWire) eventInput).bind(inOrderLinker::linkEvent); - ((BindableInputWire) minimumGenerationNonAncientInput) - .bind(inOrderLinker::setMinimumGenerationNonAncient); + ((BindableInputWire) nonAncientEventWindowInput) + .bind(inOrderLinker::setNonAncientEventWindow); ((BindableInputWire) clearInput).bind(inOrderLinker::clear); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java index 7623a57f42a3..0258ffccfbf0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/LinkedEventIntakeWiring.java @@ -21,6 +21,7 @@ import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; import com.swirlds.platform.components.LinkedEventIntake; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; @@ -30,18 +31,18 @@ /** * Wiring for the {@link LinkedEventIntake}. * - * @param eventInput the input wire for events to be added to the hashgraph - * @param pauseInput the input wire for pausing the linked event intake - * @param consensusRoundOutput the output wire for consensus rounds - * @param minimumGenerationNonAncientOutput the output wire for the minimum generation non-ancient. This output is - * transformed from the consensus round output - * @param flushRunnable the runnable to flush the intake + * @param eventInput the input wire for events to be added to the hashgraph + * @param pauseInput the input wire for pausing the linked event intake + * @param consensusRoundOutput the output wire for consensus rounds + * @param nonAncientEventWindowOutput the output wire for the {@link NonAncientEventWindow}. This output is transformed + * from the consensus round output + * @param flushRunnable the runnable to flush the intake */ public record LinkedEventIntakeWiring( @NonNull InputWire eventInput, @NonNull InputWire pauseInput, @NonNull OutputWire consensusRoundOutput, - @NonNull OutputWire minimumGenerationNonAncientOutput, + @NonNull OutputWire nonAncientEventWindowOutput, @NonNull Runnable flushRunnable) { /** @@ -59,9 +60,9 @@ public static LinkedEventIntakeWiring create(@NonNull final TaskScheduler consensusRound.getGenerations().getMinGenerationNonAncient()), + consensusRound -> consensusRound.getNonAncientEventWindow()), taskScheduler::flush); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java index 1551ee212284..91de7b212c6b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/OrphanBufferWiring.java @@ -20,6 +20,7 @@ import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.orphan.OrphanBuffer; import edu.umd.cs.findbugs.annotations.NonNull; @@ -29,14 +30,14 @@ * Wiring for the {@link OrphanBuffer}. * * @param eventInput the input wire for unordered events - * @param minimumGenerationNonAncientInput the input wire for the minimum generation non-ancient + * @param nonAncientEventWindowInput the input wire for the non-ancient event window * @param clearInput the input wire to clear the internal state of the orphan buffer * @param eventOutput the output wire for topologically ordered events * @param flushRunnable the runnable to flush the buffer */ public record OrphanBufferWiring( @NonNull InputWire eventInput, - @NonNull InputWire minimumGenerationNonAncientInput, + @NonNull InputWire nonAncientEventWindowInput, @NonNull InputWire clearInput, @NonNull OutputWire eventOutput, @NonNull Runnable flushRunnable) { @@ -50,7 +51,7 @@ public record OrphanBufferWiring( public static OrphanBufferWiring create(@NonNull final TaskScheduler> taskScheduler) { return new OrphanBufferWiring( taskScheduler.buildInputWire("unordered events"), - taskScheduler.buildInputWire("minimum generation non ancient"), + taskScheduler.buildInputWire("non-ancient event window"), taskScheduler.buildInputWire("clear"), taskScheduler.getOutputWire().buildSplitter("orphanBufferSplitter", "event lists"), taskScheduler::flush); @@ -63,8 +64,8 @@ public static OrphanBufferWiring create(@NonNull final TaskScheduler>) eventInput).bind(orphanBuffer::handleEvent); - ((BindableInputWire>) minimumGenerationNonAncientInput) - .bind(orphanBuffer::setMinimumGenerationNonAncient); + ((BindableInputWire>) nonAncientEventWindowInput) + .bind(orphanBuffer::setNonAncientEventWindow); ((BindableInputWire>) clearInput).bind(orphanBuffer::clear); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java index e560340e85c2..6e97f307f513 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java @@ -29,6 +29,7 @@ import com.swirlds.platform.StateSigner; import com.swirlds.platform.components.LinkedEventIntake; import com.swirlds.platform.components.appcomm.AppCommunicationComponent; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.EventCreationManager; import com.swirlds.platform.event.deduplication.EventDeduplicator; @@ -145,19 +146,17 @@ public WiringModel getModel() { } /** - * Solder the minimum generation non-ancient output to all components that need it. + * Solder the NonAncientEventWindow output to all components that need it. */ - private void solderMinimumGenerationNonAncient() { - final OutputWire minimumGenerationNonAncientOutput = - linkedEventIntakeWiring.minimumGenerationNonAncientOutput(); - - minimumGenerationNonAncientOutput.solderTo(eventDeduplicatorWiring.minimumGenerationNonAncientInput(), INJECT); - minimumGenerationNonAncientOutput.solderTo( - eventSignatureValidatorWiring.minimumGenerationNonAncientInput(), INJECT); - minimumGenerationNonAncientOutput.solderTo(orphanBufferWiring.minimumGenerationNonAncientInput(), INJECT); - minimumGenerationNonAncientOutput.solderTo(inOrderLinkerWiring.minimumGenerationNonAncientInput(), INJECT); - minimumGenerationNonAncientOutput.solderTo( - eventCreationManagerWiring.minimumGenerationNonAncientInput(), INJECT); + private void solderNonAncientEventWindow() { + final OutputWire nonAncientEventWindowOutputWire = + linkedEventIntakeWiring.nonAncientEventWindowOutput(); + + nonAncientEventWindowOutputWire.solderTo(eventDeduplicatorWiring.nonAncientEventWindowInput(), INJECT); + nonAncientEventWindowOutputWire.solderTo(eventSignatureValidatorWiring.nonAncientEventWindowInput(), INJECT); + nonAncientEventWindowOutputWire.solderTo(orphanBufferWiring.nonAncientEventWindowInput(), INJECT); + nonAncientEventWindowOutputWire.solderTo(inOrderLinkerWiring.nonAncientEventWindowInput(), INJECT); + nonAncientEventWindowOutputWire.solderTo(eventCreationManagerWiring.nonAncientEventWindowInput(), INJECT); } /** @@ -177,7 +176,7 @@ private void wire() { .solderTo(applicationTransactionPrehandlerWiring.appTransactionsToPrehandleInput()); orphanBufferWiring.eventOutput().solderTo(stateSignatureCollectorWiring.preconsensusEventInput()); - solderMinimumGenerationNonAncient(); + solderNonAncientEventWindow(); } } @@ -329,19 +328,19 @@ public InputWire getSignStateInput() { } /** - * Inject a new minimum generation non-ancient on all components that need it. + * Inject a new non-ancient event window into all components that need it. *

    - * Future work: this is a temporary hook to allow the components to get the minimum generation non-ancient during - * startup. This method will be removed once the components are wired together. + * Future work: this is a temporary hook to allow the components to get the non-ancient event window during startup. + * This method will be removed once the components are wired together. * - * @param minimumGenerationNonAncient the new minimum generation non-ancient + * @param nonAncientEventWindow the new non-ancient event window */ - public void updateMinimumGenerationNonAncient(final long minimumGenerationNonAncient) { - eventDeduplicatorWiring.minimumGenerationNonAncientInput().inject(minimumGenerationNonAncient); - eventSignatureValidatorWiring.minimumGenerationNonAncientInput().inject(minimumGenerationNonAncient); - orphanBufferWiring.minimumGenerationNonAncientInput().inject(minimumGenerationNonAncient); - inOrderLinkerWiring.minimumGenerationNonAncientInput().inject(minimumGenerationNonAncient); - eventCreationManagerWiring.minimumGenerationNonAncientInput().inject(minimumGenerationNonAncient); + public void updateNonAncientEventWindow(@NonNull final NonAncientEventWindow nonAncientEventWindow) { + eventDeduplicatorWiring.nonAncientEventWindowInput().inject(nonAncientEventWindow); + eventSignatureValidatorWiring.nonAncientEventWindowInput().inject(nonAncientEventWindow); + orphanBufferWiring.nonAncientEventWindowInput().inject(nonAncientEventWindow); + inOrderLinkerWiring.nonAncientEventWindowInput().inject(nonAncientEventWindow); + eventCreationManagerWiring.nonAncientEventWindowInput().inject(nonAncientEventWindow); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventCreationManagerWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventCreationManagerWiring.java index 28d8b2b3e41f..0eddeda0d8f0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventCreationManagerWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/EventCreationManagerWiring.java @@ -22,6 +22,7 @@ import com.swirlds.common.wiring.wires.input.BindableInputWire; import com.swirlds.common.wiring.wires.input.InputWire; import com.swirlds.common.wiring.wires.output.OutputWire; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.EventCreationConfig; import com.swirlds.platform.event.creation.EventCreationManager; @@ -36,7 +37,7 @@ public class EventCreationManagerWiring { private final TaskScheduler taskScheduler; private final BindableInputWire eventInput; - private final BindableInputWire minimumGenerationNonAncientInput; + private final BindableInputWire nonAncientEventWindowInput; private final BindableInputWire pauseInput; private final Bindable heartbeatBindable; private final OutputWire newEventOutput; @@ -66,7 +67,7 @@ private EventCreationManagerWiring( this.taskScheduler = taskScheduler; eventInput = taskScheduler.buildInputWire("possible parents"); - minimumGenerationNonAncientInput = taskScheduler.buildInputWire("minimum generation non ancient"); + nonAncientEventWindowInput = taskScheduler.buildInputWire("non-ancient event window"); pauseInput = taskScheduler.buildInputWire("pause"); newEventOutput = taskScheduler.getOutputWire(); @@ -84,7 +85,7 @@ private EventCreationManagerWiring( */ public void bind(@NonNull final EventCreationManager eventCreationManager) { eventInput.bind(eventCreationManager::registerEvent); - minimumGenerationNonAncientInput.bind(eventCreationManager::setMinimumGenerationNonAncient); + nonAncientEventWindowInput.bind(eventCreationManager::setNonAncientEventWindow); pauseInput.bind(eventCreationManager::setPauseStatus); heartbeatBindable.bind(now -> { return eventCreationManager.maybeCreateEvent(); @@ -102,13 +103,13 @@ public InputWire eventInput() { } /** - * Get the input wire for the minimum generation non-ancient. + * Get the input wire for the non-ancient event window. * - * @return the input wire for the minimum generation non-ancient + * @return the input wire for the non-ancient event window */ @NonNull - public InputWire minimumGenerationNonAncientInput() { - return minimumGenerationNonAncientInput; + public InputWire nonAncientEventWindowInput() { + return nonAncientEventWindowInput; } /** diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/ConsensusRoundTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/ConsensusRoundTests.java index 086586dc41d7..a10e8f691612 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/ConsensusRoundTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/ConsensusRoundTests.java @@ -24,6 +24,7 @@ import com.swirlds.common.test.fixtures.RandomUtils; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.GraphGenerations; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.address.AddressBook; @@ -48,8 +49,8 @@ void testConstructor() { final List events = List.of(e1, e2, e3); - final ConsensusRound round = - new ConsensusRound(mock(AddressBook.class), events, mock(EventImpl.class), g, snapshot); + final ConsensusRound round = new ConsensusRound( + mock(AddressBook.class), events, mock(EventImpl.class), g, mock(NonAncientEventWindow.class), snapshot); assertEquals(events, round.getConsensusEvents(), "consensus event list does not match the provided list."); assertEquals(events.size(), round.getNumEvents(), "numEvents does not match the events provided."); @@ -76,6 +77,7 @@ void testNumApplicationTransactions() { events, mock(EventImpl.class), mock(GraphGenerations.class), + mock(NonAncientEventWindow.class), mock(ConsensusSnapshot.class)); assertEquals( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java index b8311e0421e5..ee3f3a729809 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/EventDeduplicatorTests.java @@ -29,6 +29,8 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.consensus.ConsensusConstants; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.deduplication.EventDeduplicator; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.metrics.EventIntakeMetrics; @@ -181,7 +183,11 @@ void standardOperation() { if (random.nextBoolean()) { minimumGenerationNonAncient++; - deduplicator.setMinimumGenerationNonAncient(minimumGenerationNonAncient); + // FUTURE WORK: change from minGenNonAncient to minRoundNonAncient + deduplicator.setNonAncientEventWindow(new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, + ConsensusConstants.ROUND_NEGATIVE_INFINITY, + minimumGenerationNonAncient)); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/linking/InOrderLinkerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/linking/InOrderLinkerTests.java index 27e17969506e..f151e189023c 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/linking/InOrderLinkerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/linking/InOrderLinkerTests.java @@ -29,6 +29,8 @@ import com.swirlds.base.test.fixtures.time.FakeTime; import com.swirlds.common.crypto.Hash; +import com.swirlds.platform.consensus.ConsensusConstants; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.internal.EventImpl; @@ -161,7 +163,9 @@ void standardOperation() { assertEquals(0, exitedIntakePipelineCount.get()); time.tick(Duration.ofSeconds(1)); - inOrderLinker.setMinimumGenerationNonAncient(1); + // FUTURE WORK: change from minGenNonAncient to minRoundNonAncient + inOrderLinker.setNonAncientEventWindow(new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, ConsensusConstants.ROUND_NEGATIVE_INFINITY, 1)); final Hash child2Hash = randomHash(random); final long child2Generation = 2; @@ -181,7 +185,9 @@ void standardOperation() { assertEquals(0, exitedIntakePipelineCount.get()); time.tick(Duration.ofSeconds(1)); - inOrderLinker.setMinimumGenerationNonAncient(2); + // FUTURE WORK: change from minGenNonAncient to minRoundNonAncient + inOrderLinker.setNonAncientEventWindow(new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, ConsensusConstants.ROUND_NEGATIVE_INFINITY, 2)); final Hash child3Hash = randomHash(random); final long child3Generation = 3; @@ -195,7 +201,9 @@ void standardOperation() { assertEquals(0, exitedIntakePipelineCount.get()); time.tick(Duration.ofSeconds(1)); - inOrderLinker.setMinimumGenerationNonAncient(4); + // FUTURE WORK: change from minGenNonAncient to minRoundNonAncient + inOrderLinker.setNonAncientEventWindow(new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, ConsensusConstants.ROUND_NEGATIVE_INFINITY, 4)); final Hash child4Hash = randomHash(random); final long child4Generation = 4; @@ -252,7 +260,9 @@ void missingOtherParent() { @Test @DisplayName("Ancient events should immediately exit the intake pipeline") void ancientEvent() { - inOrderLinker.setMinimumGenerationNonAncient(3); + // FUTURE WORK: change from minGenNonAncient to minRoundNonAncient + inOrderLinker.setNonAncientEventWindow(new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, ConsensusConstants.ROUND_NEGATIVE_INFINITY, 3)); final GossipEvent child1 = generateMockEvent( randomHash(random), diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/orphan/OrphanBufferTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/orphan/OrphanBufferTests.java index 8c580371a83b..acfc17331cb9 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/orphan/OrphanBufferTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/orphan/OrphanBufferTests.java @@ -27,6 +27,8 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.consensus.ConsensusConstants; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; import com.swirlds.platform.system.events.BaseEventHashedData; @@ -281,7 +283,12 @@ void standardOperation() { final int stepRandomness = Math.round(random.nextFloat() * MAX_GENERATION_STEP); if (random.nextFloat() < averageGenerationAdvancement / stepRandomness) { minimumGenerationNonAncient += stepRandomness; - unorphanedEvents.addAll(orphanBuffer.setMinimumGenerationNonAncient(minimumGenerationNonAncient)); + // FUTURE WORK: change from minGenNonAncient to minRoundNonAncient + final NonAncientEventWindow nonAncientEventWindow = new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, + ConsensusConstants.ROUND_NEGATIVE_INFINITY, + minimumGenerationNonAncient); + unorphanedEvents.addAll(orphanBuffer.setNonAncientEventWindow(nonAncientEventWindow)); } for (final GossipEvent unorphanedEvent : unorphanedEvents) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java index 611b0f598735..644e36e36281 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/event/validation/EventSignatureValidatorTests.java @@ -31,6 +31,8 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.SerializablePublicKey; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.consensus.ConsensusConstants; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.crypto.SignatureVerifier; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.IntakeEventCounter; @@ -252,7 +254,8 @@ void ancientEvent() { assertNotEquals(null, validatorWithTrueVerifier.validateSignature(event)); assertEquals(0, exitedIntakePipelineCount.get()); - validatorWithTrueVerifier.setMinimumGenerationNonAncient(100L); + validatorWithTrueVerifier.setNonAncientEventWindow(new NonAncientEventWindow( + ConsensusConstants.ROUND_FIRST, ConsensusConstants.ROUND_NEGATIVE_INFINITY, 100L)); assertNull(validatorWithTrueVerifier.validateSignature(event)); assertEquals(1, exitedIntakePipelineCount.get()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java index 3955a5d51d4f..273c8291e289 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/uptime/UptimeTests.java @@ -33,6 +33,7 @@ import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.GraphGenerations; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.address.Address; @@ -103,7 +104,12 @@ private static List generateEvents( private static ConsensusRound mockRound(@NonNull final List events) { final ConsensusSnapshot snapshot = mock(ConsensusSnapshot.class); final ConsensusRound round = new ConsensusRound( - mock(AddressBook.class), events, mock(EventImpl.class), mock(GraphGenerations.class), snapshot); + mock(AddressBook.class), + events, + mock(EventImpl.class), + mock(GraphGenerations.class), + mock(NonAncientEventWindow.class), + snapshot); final Instant consensusTimestamp = events.get(events.size() - 1).getConsensusTimestamp(); when(snapshot.consensusTimestamp()).thenReturn(consensusTimestamp); return round; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/chatter/ChatterNotifierTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/chatter/ChatterNotifierTest.java index 5123d04e606b..0e2559e1e01a 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/chatter/ChatterNotifierTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/chatter/ChatterNotifierTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify; import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.gossip.chatter.ChatterNotifier; import com.swirlds.platform.gossip.chatter.protocol.ChatterCore; @@ -73,6 +74,7 @@ void testPurge() { List.of(event), mock(EventImpl.class), new Generations(1, 2, 3), + mock(NonAncientEventWindow.class), mock(ConsensusSnapshot.class))); verify(chatterCore).shiftWindow(1); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java index 9f571f3f5366..2afec83d0442 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventIntakeTest.java @@ -33,6 +33,7 @@ import com.swirlds.platform.Consensus; import com.swirlds.platform.components.EventIntake; import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.EventLinker; import com.swirlds.platform.gossip.IntakeEventCounter; @@ -110,6 +111,7 @@ void test() { List.of(consEvent1, consEvent2), added, generations, + mock(NonAncientEventWindow.class), mock(ConsensusSnapshot.class))); }); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventObserverDispatcherTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventObserverDispatcherTests.java index 427eea17d859..70e6e3f1f410 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventObserverDispatcherTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/EventObserverDispatcherTests.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import com.swirlds.platform.consensus.ConsensusSnapshot; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.gossip.shadowgraph.Generations; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; @@ -81,6 +82,7 @@ void test() { List.of(e1, e2), mock(EventImpl.class), Generations.GENESIS_GENERATIONS, + mock(NonAncientEventWindow.class), mock(ConsensusSnapshot.class))); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/TransactionHandlingTestUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/TransactionHandlingTestUtils.java index d6763fe37133..30c4f5f8da6a 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/TransactionHandlingTestUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/components/TransactionHandlingTestUtils.java @@ -23,6 +23,7 @@ import com.swirlds.common.test.fixtures.DummySystemTransaction; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.GraphGenerations; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; import com.swirlds.platform.system.BasicSoftwareVersion; @@ -89,6 +90,7 @@ public static ConsensusRound newDummyRound(final List roundContents) { events, mock(EventImpl.class), mock(GraphGenerations.class), + mock(NonAncientEventWindow.class), mock(ConsensusSnapshot.class)); } } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/ChildlessEventTrackerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/ChildlessEventTrackerTests.java index 256393d844b6..9e029ef0e2ec 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/ChildlessEventTrackerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/ChildlessEventTrackerTests.java @@ -22,6 +22,7 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.creation.tipset.ChildlessEventTracker; import com.swirlds.platform.system.events.EventDescriptor; import edu.umd.cs.findbugs.annotations.NonNull; @@ -100,7 +101,8 @@ void basicBehaviorTest() { assertEquals(removeBranches(expectedEvents), new HashSet<>(tracker.getChildlessEvents())); // Increase the minimum generation non-ancient to 1, all events from batch1 should be removed - tracker.pruneOldEvents(1); + // FUTURE WORK: Change the test to use round instead of generation for ancient. + tracker.pruneOldEvents(new NonAncientEventWindow(1, 0, 1)); assertEquals(removeBranches(batch2), new HashSet<>(tracker.getChildlessEvents())); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java index 982f81c8369e..f56d65c6b382 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetEventCreatorTests.java @@ -38,6 +38,7 @@ import com.swirlds.common.stream.Signer; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; import com.swirlds.platform.components.transaction.TransactionSupplier; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.creation.EventCreator; import com.swirlds.platform.event.creation.tipset.ChildlessEventTracker; @@ -889,7 +890,7 @@ void noStaleEventsAtCreationTimeTest() { final EventCreator eventCreator = buildEventCreator(random, time, addressBook, nodeA, () -> new ConsensusTransactionImpl[0]); - eventCreator.setMinimumGenerationNonAncient(100); + eventCreator.setNonAncientEventWindow(new NonAncientEventWindow(1, 0, 100)); // Since there are no other parents available, the next event created would have a generation of 0 // (if event creation were permitted). Since the current minimum generation non ancient is 100, diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java index d7ad97240157..e8a487cc57b7 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetTrackerTests.java @@ -26,6 +26,7 @@ import com.swirlds.base.time.Time; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.creation.tipset.Tipset; import com.swirlds.platform.event.creation.tipset.TipsetTracker; import com.swirlds.platform.system.address.Address; @@ -141,8 +142,11 @@ void basicBehaviorTest() { long minimumGenerationNonAncient = 0; while (tracker.size() > 0) { minimumGenerationNonAncient += random.nextInt(1, 5); - tracker.setMinimumGenerationNonAncient(minimumGenerationNonAncient); - assertEquals(minimumGenerationNonAncient, tracker.getMinimumGenerationNonAncient()); + // FUTURE WORK: change test to use round number instead of minimum generation non-ancient. + final NonAncientEventWindow nonAncientEventWindow = + new NonAncientEventWindow(1, 0, minimumGenerationNonAncient); + tracker.setNonAncientEventWindow(nonAncientEventWindow); + assertEquals(nonAncientEventWindow, tracker.getNonAncientEventWindow()); for (final EventDescriptor fingerprint : expectedTipsets.keySet()) { if (fingerprint.getGeneration() < minimumGenerationNonAncient) { assertNull(tracker.getTipset(fingerprint)); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java index 985cb4b7dcd2..503b78a85aa2 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/tipset/TipsetWeightCalculatorTests.java @@ -34,6 +34,7 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator; import com.swirlds.common.test.fixtures.RandomAddressBookGenerator.WeightDistributionStrategy; +import com.swirlds.platform.consensus.NonAncientEventWindow; import com.swirlds.platform.event.creation.tipset.ChildlessEventTracker; import com.swirlds.platform.event.creation.tipset.Tipset; import com.swirlds.platform.event.creation.tipset.TipsetAdvancementWeight; @@ -533,9 +534,11 @@ void ancientParentTest() { final EventDescriptor eventD2 = newEventDescriptor(randomHash(random), nodeD, 2); builder.addEvent(eventD2, List.of(eventA1, eventB1, eventC1, eventD1)); + // FUTURE WORK: Change the test to use round instead of generation for ancient. // Mark generation 1 as ancient. - builder.setMinimumGenerationNonAncient(2); - childlessEventTracker.pruneOldEvents(2); + final NonAncientEventWindow nonAncientEventWindow = new NonAncientEventWindow(1, 0, 2); + builder.setNonAncientEventWindow(nonAncientEventWindow); + childlessEventTracker.pruneOldEvents(nonAncientEventWindow); // We shouldn't be able to find tipsets for ancient events. assertNull(builder.getTipset(eventA1)); From 0596b1c1d80a70eceb73743511b00fc7a42f2f01 Mon Sep 17 00:00:00 2001 From: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Date: Fri, 29 Dec 2023 08:48:44 -0600 Subject: [PATCH 58/80] fix: JTR tool compatible with Java 21 (#10672) Signed-off-by: Edward Wertz --- .../platform/testreader/JrsTestReportGenerator.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/JrsTestReportGenerator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/JrsTestReportGenerator.java index 1b11bb3533d1..7b00ba5724cf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/JrsTestReportGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/JrsTestReportGenerator.java @@ -699,9 +699,10 @@ private static void generateOverview( data.reportTime().atZone(ZoneId.systemDefault()).toLocalTime(); final ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate, localTime, ZoneId.systemDefault()); - final String date = - DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).format(zonedDateTime); - final String[] dateParts = date.split(" at "); + final String date = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(zonedDateTime); + final String time = DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL) + .format(zonedDateTime) + .replace("\u202f", " "); final int percentPassing; if (testCount.uniqueTests() == 0) { @@ -762,8 +763,8 @@ private static void generateOverview( owner, hidden ? "none" : "block", formattedDirectory, - dateParts[0], - dateParts[1], + date, + time, data.reportSpan(), percentPassing == -1 ? "--" : percentPassing, testCount.uniqueTests(), From f5784be68f155df831569e46030fdea71fe3d69a Mon Sep 17 00:00:00 2001 From: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Date: Fri, 29 Dec 2023 08:49:01 -0600 Subject: [PATCH 59/80] chore: update JTR test metadata file (#10674) Signed-off-by: Edward Wertz --- .../com/swirlds/platform/testreader/testMetadata.csv | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/testMetadata.csv b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/testMetadata.csv index 03bd1f87a1d5..221b2649d690 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/testMetadata.csv +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/testreader/testMetadata.csv @@ -54,6 +54,12 @@ ISS, ISS-emergency-recovery-diff-hash-1k-10m, platform, https://www.notion.so/sw ISS, ISS-emergency-recovery-reconnect-1k-10m, platform, https://www.notion.so/swirldslabs/ISS-ISS-emergency-recovery-reconnect-1k-10m-75cb5dd79efa4234bd6d783704511900 ISS, ISS-recoverable-1k-5m, platform, https://www.notion.so/swirldslabs/ISS-ISS-recoverable-1k-5m-f442196451c54068b3320c4728f8ddcd +LegacyIntakePipeline, FCM-Migration-LegacyIntake-5-10m, platform, https://www.notion.so/swirldslabs/LegacyIntakePipeline-FCM-Migration-LegacyIntake-5-10m-bcf18b8750814ec0a0a279ac2cf52203 +LegacyIntakePipeline, FCM-Restart-LegacyIntake-2.5k-20m, platform, https://www.notion.so/swirldslabs/LegacyIntakePipeline-FCM-Restart-LegacyIntake-2-5k-20m-21b674cbb9fa412ab1e197c9de29acab +LegacyIntakePipeline, ISS-recoverable-LegacyIntake-1k-5m, platform, https://www.notion.so/swirldslabs/LegacyIntakePipeline-ISS-Recoverable-LegacyIntake-1k-5m-1ca5a2aead594238ab0a2eb61eae39a7 +LegacyIntakePipeline, VMerkleDual-NDReconnect-LegacyIntake-500-60m, platform, https://www.notion.so/swirldslabs/LegacyIntakePipeline-VMerkleDual-NDReconnect-LegacyIntake-500-60m-03f4a781ea11471b8c6ab96edb66bb23 +LegacyIntakePipeline, Stress-Basic-LegacyIntake-50k-10m, platform, https://www.notion.so/swirldslabs/LegacyIntakePipeline-Stress-Basic-LegacyIntake-50k-10m-66be90c73fd64557b0d1db3a5b36cd4b + Migration, Crypto-Restart-Testnet-200-18m, services, https://www.notion.so/swirldslabs/Migration-Crypto-Restart-Testnet-200-18m-7ea1a0336f174dabb99f68d7831ab42c Migration, FCM-Migration-NewNodesFromGenesis-2.5k-8m, platform, https://www.notion.so/swirldslabs/Migration-FCM-Migration-NewNodesFromGenesis-2-5k-8m-a1aa980d64b54c8ea32f37527e8aa872 Migration, FCM-Migration-NewNodesFromSavedState-2.5k-10m, platform, https://www.notion.so/swirldslabs/Migration-FCM-Migration-NewNodesFromSavedState-2-5k-10m-e1a18d4b2bd84086a8926d7367485918 @@ -72,6 +78,7 @@ Misc, FCM-Throttle-2.5k-15m, platform, https://www.notion.so/swirldslabs/Misc-FC Misc, FCMFCQ-HotspotExpirationStress-2.5k-13m, platform, https://www.notion.so/swirldslabs/Misc-FCMFCQ-HotspotExpirationStress-2-5k-13m-2bbbd9c8cb184015bfdf010c0413b44c MultiSBReconnect, FCM-MultiSBReconnect-3R-1k-20m, platform, https://www.notion.so/swirldslabs/MultiSBReconnect-FCM-MultiSBReconnect-3R-1k-20m-f65da4179e2c430393d3f59899b47d2e +MultiSBReconnect, SBReconnect-LegacyIntake-20m, platform, https://www.notion.so/swirldslabs/MultiSBReconnect-SBReconnect-LegacyIntake-20m-204390c6fac64234a336889e40900ea4 NDReconnectCorrectness, AllProtectedFilesUpdate-NDReconnect-1-16m, services, https://www.notion.so/swirldslabs/NDReconnectCorrectness-AllProtectedFilesUpdate-NDReconnect-1-16m-ba54bf45373f4a8fa4f72f624459556a NDReconnectCorrectness, CongestionPricing-NDReconnect-1-16m, services, https://www.notion.so/swirldslabs/NDReconnectCorrectness-CongestionPricing-NDReconnect-1-16m-7581cef87f534dfd8e285a89f182c3aa @@ -137,6 +144,10 @@ Ubuntu1804_Update, Crypto-Update-Jar-1.5k-25m, services, https://www.notion.so/s Ubuntu1804_Update, Crypto-Update-Setting-1.5k-15m, services, https://www.notion.so/swirldslabs/Ubuntu1804_Update-Crypto-Update-Setting-1-5k-15m-f9e4cf7f22dd407ba7324be9e6fe3710 Ubuntu1804_Update, Crypto-Update-Setting-Config-1.5k-25m, services, https://www.notion.so/swirldslabs/Ubuntu1804_Update-Crypto-Update-Setting-Config-1-5k-25m-c57ab146de9242a495a3662472891433 +Ubuntu2204_Update, Crypto-Update-Jar-1.5k-25m, services, https://www.notion.so/swirldslabs/Ubuntu2204_Update-Crypto-Update-Jar-1-5k-25m-e0d4d48d5b5a40c4951a01a174e2e8e7 +Ubuntu2204_Update, Crypto-Update-Setting-Config-1.5k-25m, services, https://www.notion.so/swirldslabs/Ubuntu2204_Update-Crypto-Update-Setting-Config-1-5k-25m-f63a92c227a74a70a13e734f0a31ebf1 +Ubuntu2204_Update, Crypto-Update-Setting-1.5k-15m, services, https://www.notion.so/swirldslabs/Ubuntu2204_Update-Crypto-Update-Setting-1-5k-15m-7ee7988ef354401ca1ace966da9eb3bd + UnevenStake, Crypto-NIReconnect-Stake-1k-10m, services, https://www.notion.so/swirldslabs/UnevenStake-Crypto-NIReconnect-Stake-1k-10m-9c68e5923c7549dc8fc491880dc20456 UnevenStake, Crypto-Restart-Stake-1k-10m, services, https://www.notion.so/swirldslabs/UnevenStake-Crypto-Restart-Stake-1k-10m-6b39ab0dfd764dd0b0717d34c8238726 UnevenStake, Crypto-Restart-Stake-2.5k-10m, services, https://www.notion.so/swirldslabs/UnevenStake-Crypto-Restart-Stake-2-5k-10m-6365c7e0989b4c33b0eb7745afa663aa From 84112c9b6a29e894c4bec96b8dee8352eed29ca6 Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Fri, 29 Dec 2023 10:12:43 -0600 Subject: [PATCH 60/80] chore(ci): reduce macOS runner usage footprint (#10676) Signed-off-by: Nathan Klick --- .../generate-docker-artifact-baseline.sh | 18 +++------ .../zxc-verify-docker-build-determinism.yaml | 40 ++++++------------- .../zxc-verify-gradle-build-determinism.yaml | 5 ++- 3 files changed, 20 insertions(+), 43 deletions(-) diff --git a/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh b/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh index 381026f1f0fd..01efb9820a10 100755 --- a/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh +++ b/.github/workflows/support/scripts/generate-docker-artifact-baseline.sh @@ -2,8 +2,6 @@ set -o pipefail set +e -readonly RELEASE_LIB_PATH="hedera-node/data/lib" -readonly RELEASE_APPS_PATH="hedera-node/data/apps" readonly DOCKER_IMAGE_NAME="main-network-node" GROUP_ACTIVE="false" @@ -113,14 +111,6 @@ start_group "Configuring Environment" fail "ERROR (Exit Code: ${?})" "${?}" fi end_task "DONE (Found: ${JQ})" - - start_task "Checking for prebuilt libraries" - ls -al "${GITHUB_WORKSPACE}/${RELEASE_LIB_PATH}"/*.jar >/dev/null 2>&1 || fail "ERROR (Exit Code: ${?})" "${?}" - end_task "FOUND (Path: ${GITHUB_WORKSPACE}/${RELEASE_LIB_PATH}/*.jar)" - - start_task "Checking for prebuilt applications" - ls -al "${GITHUB_WORKSPACE}/${RELEASE_APPS_PATH}"/*.jar >/dev/null 2>&1 || fail "ERROR (Exit Code: ${?})" "${?}" - end_task "FOUND (Path: ${GITHUB_WORKSPACE}/${RELEASE_APPS_PATH}/*.jar)" end_group start_group "Prepare the Docker Image Information" @@ -156,12 +146,14 @@ end_group start_group "Generating Final Release Manifests" start_task "Generating the manifest archive" - tar -czf "${TEMP_DIR}/manifest.tar.gz" -C "${TEMP_DIR}" linux-amd64.manifest.json linux-amd64.layers.json linux-amd64.comparable.json linux-arm64.manifest.json linux-arm64.layers.json linux-arm64.comparable.json >/dev/null 2>&1 || fail "TAR ERROR (Exit Code: ${?})" "${?}" + MANIFEST_FILES=("linux-amd64.manifest.json" "linux-amd64.layers.json" "linux-amd64.comparable.json") + MANIFEST_FILES+=("linux-arm64.manifest.json" "linux-arm64.layers.json" "linux-arm64.comparable.json") + tar -czf "${TEMP_DIR}/manifest.tar.gz" -C "${TEMP_DIR}" "${MANIFEST_FILES[@]}" >/dev/null 2>&1 || fail "TAR ERROR (Exit Code: ${?})" "${?}" end_task start_task "Copying the manifest files" - cp "${TEMP_DIR}/manifest.tar.gz" "${MANIFEST_PATH}/${GITHUB_SHA}.tar.gz" || fail "COPY ERROR (Exit Code: ${?})" "${?}" - cp "${TEMP_DIR}"/*.json "${MANIFEST_PATH}/" || fail "COPY ERROR (Exit Code: ${?})" "${?}" + cp "${TEMP_DIR}/manifest.tar.gz" "${MANIFEST_PATH}/${GITHUB_SHA}.tar.gz" || fail "COPY ERROR (Exit Code: ${?})" "${?}" + cp "${TEMP_DIR}"/*.json "${MANIFEST_PATH}/" || fail "COPY ERROR (Exit Code: ${?})" "${?}" end_task "DONE (Path: ${MANIFEST_PATH}/${GITHUB_SHA}.tar.gz)" start_task "Setting Step Outputs" diff --git a/.github/workflows/zxc-verify-docker-build-determinism.yaml b/.github/workflows/zxc-verify-docker-build-determinism.yaml index 3d85827e6fe0..7fed26d317f8 100644 --- a/.github/workflows/zxc-verify-docker-build-determinism.yaml +++ b/.github/workflows/zxc-verify-docker-build-determinism.yaml @@ -187,6 +187,15 @@ jobs: if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} run: ${{ env.DOCKER_MANIFEST_GENERATOR }} + - name: Amend Manifest with Gradle Artifacts + if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} + working-directory: ${{ env.DOCKER_MANIFEST_PATH }} + run: | + EXTRACTED_FILE_NAME="${{ steps.commit.outputs.sha }}.tar" + gunzip "${{ steps.manifest.outputs.name }}" + tar -rvf "${EXTRACTED_FILE_NAME}" -C "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}" sdk + gzip "${EXTRACTED_FILE_NAME}" + - name: Upload Baseline if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} run: gsutil cp "${{ steps.manifest.outputs.file }}" "${{ steps.baseline.outputs.file }}" @@ -205,11 +214,12 @@ jobs: # builds to be non-deterministic. Self-hosted runners using Ubuntu 22.04 are unaffected. # The Self Hosted Large instance is temporarily disabled because it is running no longer supported version of # Ubuntu 18.04. The Large instance will be upgraded to Ubuntu 22.04 in the near future. + # Disabled macos-11 runner to minimize the number of concurrent macOS builds due to concurrency issues. os: #- ubuntu-22.04 - ubuntu-20.04 - macos-12 - - macos-11 + #- macos-11 #- windows-2022 #- windows-2019 - [self-hosted, Linux, medium, ephemeral] @@ -230,17 +240,6 @@ jobs: with: python-version: 3.9 - - name: Setup Java - uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 - with: - distribution: ${{ inputs.java-distribution }} - java-version: ${{ inputs.java-version }} - - - name: Setup Gradle - uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0 - with: - cache-disabled: true - - name: Install Skopeo and JQ (Linux) if: ${{ runner.os == 'Linux' }} run: | @@ -281,10 +280,7 @@ jobs: cd "${DOCKER_MANIFEST_PATH}" gsutil cp "${{ needs.generate-baseline.outputs.file }}" . tar -xzf "${{ needs.generate-baseline.outputs.name }}" - - - name: Build Artifacts - id: gradle-build - run: ./gradlew assemble --scan --no-build-cache + mv "sdk" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/" - name: Set up Docker uses: crazy-max/ghaction-setup-docker@d9be6cade441568ba10037bce5221b8f564981f1 # v3.0.0 @@ -312,18 +308,6 @@ jobs: - name: Show Docker Info run: docker info - - name: Prepare for Docker Build - run: | - mkdir -p "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data" - - echo "::group::Copying Library Artifacts" - cp -Rvf "${{ github.workspace }}/hedera-node/data/lib" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/" - echo "::endgroup::" - - echo "::group::Copying Application Artifacts" - cp -Rvf "${{ github.workspace }}/hedera-node/data/apps" "${{ github.workspace }}/${{ env.DOCKER_CONTEXT_PATH }}/sdk/data/" - echo "::endgroup::" - - name: Build Docker Image uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 env: diff --git a/.github/workflows/zxc-verify-gradle-build-determinism.yaml b/.github/workflows/zxc-verify-gradle-build-determinism.yaml index d1f30f51be2e..97b967f2aa0a 100644 --- a/.github/workflows/zxc-verify-gradle-build-determinism.yaml +++ b/.github/workflows/zxc-verify-gradle-build-determinism.yaml @@ -137,11 +137,12 @@ jobs: strategy: fail-fast: false matrix: + # Disabled MacOS runner testing due to concurrency issues with the Github hosted runners. os: - ubuntu-22.04 - ubuntu-20.04 - - macos-12 - - macos-11 + #- macos-12 + #- macos-11 - windows-2022 - windows-2019 - [self-hosted, Linux, medium, ephemeral] From bf5f821b54f9357b2735f9b8b5014b805588065d Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Fri, 29 Dec 2023 14:25:10 -0600 Subject: [PATCH 61/80] feat: add time to the platform context (#10679) Signed-off-by: Cody Littley --- .../cli/signedstate/SignedStateHolder.java | 3 +- .../app/service/mono/ServicesStateTest.java | 6 +- .../context/DefaultPlatformContext.java | 57 +++++++++++++------ .../common/context/PlatformContext.java | 19 ++++++- .../internal/DefaultPlatformContextTest.java | 8 ++- .../com/swirlds/platform/PlatformBuilder.java | 7 ++- .../platform/cli/CompareStatesCommand.java | 5 +- .../swirlds/platform/cli/DiagramCommand.java | 4 +- .../cli/EventStreamRecoverCommand.java | 5 +- .../cli/GenesisPlatformStateCommand.java | 5 +- .../recovery/internal/RecoveryPlatform.java | 3 +- .../platform/state/editor/StateEditor.java | 4 +- .../state/editor/StateEditorSave.java | 5 +- .../appcomm/AppCommComponentTests.java | 6 +- .../context/TestPlatformContextBuilder.java | 25 ++++++++ .../src/main/java/module-info.java | 2 +- .../AsyncPreconsensusEventWriterTests.java | 2 +- .../event/preconsensus/PcesWriterTests.java | 3 +- .../PreconsensusEventFileManagerTests.java | 2 +- .../SyncPreconsensusEventWriterTests.java | 2 +- 20 files changed, 130 insertions(+), 43 deletions(-) diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java index 2d04a209bd11..e104d2d62193 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java @@ -30,6 +30,7 @@ import com.hedera.node.app.service.mono.state.virtual.VirtualBlobKey.Type; import com.hedera.node.app.service.mono.state.virtual.VirtualBlobValue; import com.hedera.node.app.service.mono.utils.EntityNum; +import com.swirlds.base.time.Time; import com.swirlds.common.AutoCloseableNonThrowing; import com.swirlds.common.config.ConfigUtils; import com.swirlds.common.config.singleton.ConfigurationHolder; @@ -303,7 +304,7 @@ private Pair dehydrate(@NonNull final List

    - * The basic architecture approach of the {@link PlatformContext} defines the context as a single instance per - * Platform. When a platform is created the context will be passed to the platform and - * can be used internally in the platform to access all basic services. + * The basic architecture approach of the {@link PlatformContext} defines the context as a single instance per Platform. + * When a platform is created the context will be passed to the platform and can be used internally in the platform to + * access all basic services. */ public interface PlatformContext { @@ -35,6 +37,7 @@ public interface PlatformContext { * * @return the {@link Configuration} instance */ + @NonNull Configuration getConfiguration(); /** @@ -42,6 +45,7 @@ public interface PlatformContext { * * @return the {@link Cryptography} instance */ + @NonNull Cryptography getCryptography(); /** @@ -49,5 +53,14 @@ public interface PlatformContext { * * @return the {@link Metrics} instance */ + @NonNull Metrics getMetrics(); + + /** + * Returns the {@link Time} instance for the platform + * + * @return the {@link Time} instance + */ + @NonNull + Time getTime(); } diff --git a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java index afabb2e4a318..b4a810169a47 100644 --- a/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java +++ b/platform-sdk/swirlds-common/src/test/java/com/swirlds/common/context/internal/DefaultPlatformContextTest.java @@ -18,8 +18,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; +import com.swirlds.base.time.Time; import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.metrics.PlatformMetricsProvider; import com.swirlds.common.metrics.platform.DefaultMetricsProvider; import com.swirlds.common.platform.NodeId; @@ -38,7 +40,11 @@ void testNoNullServices() { metricsProvider.createGlobalMetrics(); // when - final PlatformContext context = new DefaultPlatformContext(nodeId, metricsProvider, configuration); + final PlatformContext context = new DefaultPlatformContext( + configuration, + metricsProvider.createPlatformMetrics(nodeId), + CryptographyHolder.get(), + Time.getCurrent()); // then assertNotNull(context.getConfiguration(), "Configuration must not be null"); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/PlatformBuilder.java index c3ce468414cb..8d429b81250a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/PlatformBuilder.java @@ -34,6 +34,7 @@ import com.swirlds.common.config.StateConfig; import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.CryptographyHolder; import com.swirlds.common.io.utility.RecycleBinImpl; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; @@ -203,7 +204,11 @@ public Platform build() { checkNodesToRun(List.of(selfId)); final Map keysAndCerts = initNodeSecurity(configAddressBook, configuration); - final PlatformContext platformContext = new DefaultPlatformContext(selfId, getMetricsProvider(), configuration); + final PlatformContext platformContext = new DefaultPlatformContext( + configuration, + getMetricsProvider().createPlatformMetrics(selfId), + CryptographyHolder.get(), + Time.getCurrent()); // the AddressBook is not changed after this point, so we calculate the hash now platformContext.getCryptography().digestSync(configAddressBook); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java index 9f2e9148c5c2..0f9ecd8d2dc6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/CompareStatesCommand.java @@ -18,6 +18,7 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; +import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; @@ -162,8 +163,8 @@ public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(getAbsolutePath("settings.txt"), configurationPaths); - final PlatformContext platformContext = - new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get()); + final PlatformContext platformContext = new DefaultPlatformContext( + configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); try (final ReservedSignedState stateA = loadAndHashState(platformContext, stateAPath)) { try (final ReservedSignedState stateB = loadAndHashState(platformContext, stateBPath)) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java index eceef7ce325b..85fa08a5a6ec 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/DiagramCommand.java @@ -88,8 +88,8 @@ private void setManualLinks(@NonNull final List manualLinks) { @Override public Integer call() throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(); - final PlatformContext platformContext = - new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get()); + final PlatformContext platformContext = new DefaultPlatformContext( + configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); final PlatformWiring platformWiring = new PlatformWiring(platformContext, Time.getCurrent()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java index 6ea63f7074df..c5b59e5edbe6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/EventStreamRecoverCommand.java @@ -19,6 +19,7 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.platform.recovery.EventRecoveryWorkflow.recoverState; +import com.swirlds.base.time.Time; import com.swirlds.cli.commands.EventStreamCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; @@ -126,8 +127,8 @@ private void setLoadSigningKeys(final boolean loadSigningKeys) { public Integer call() throws Exception { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(getAbsolutePath("settings.txt"), configurationPaths); - final PlatformContext platformContext = - new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get()); + final PlatformContext platformContext = new DefaultPlatformContext( + configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); recoverState( platformContext, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index 3faa7da6a915..965f3c2d51a4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -19,6 +19,7 @@ import static com.swirlds.platform.state.signed.SavedStateMetadata.NO_NODE_ID; import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateFilesToDirectory; +import com.swirlds.base.time.Time; import com.swirlds.cli.commands.StateCommand; import com.swirlds.cli.utility.AbstractCommand; import com.swirlds.cli.utility.SubcommandOf; @@ -70,8 +71,8 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(); BootstrapUtils.setupConstructableRegistry(); - final PlatformContext platformContext = - new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get()); + final PlatformContext platformContext = new DefaultPlatformContext( + configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); System.out.printf("Reading from %s %n", statePath.toAbsolutePath()); final DeserializedSignedState deserializedSignedState = diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java index c40b1101068c..ee1bd8de3892 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java @@ -19,6 +19,7 @@ import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.platform.crypto.CryptoStatic.initNodeSecurity; +import com.swirlds.base.time.Time; import com.swirlds.common.AutoCloseableNonThrowing; import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; @@ -87,7 +88,7 @@ public RecoveryPlatform( notificationEngine = NotificationEngine.buildEngine(getStaticThreadManager()); - context = new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get()); + context = new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); setLatestState(initialState); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java index d75eb9bd34cb..9b6a55638733 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditor.java @@ -19,6 +19,7 @@ import static com.swirlds.platform.state.editor.StateEditorUtils.formatNodeType; import static com.swirlds.platform.state.editor.StateEditorUtils.formatRoute; +import com.swirlds.base.time.Time; import com.swirlds.cli.utility.CommandBuilder; import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; @@ -64,7 +65,8 @@ public StateEditor(final Path statePath) throws IOException { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(); - platformContext = new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get()); + platformContext = new DefaultPlatformContext( + configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); final DeserializedSignedState deserializedSignedState = SignedStateFileReader.readStateFile(platformContext, statePath); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java index e4eb4cbae559..87f3fa1a3117 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/editor/StateEditorSave.java @@ -21,6 +21,7 @@ import static com.swirlds.platform.state.signed.SavedStateMetadata.NO_NODE_ID; import static com.swirlds.platform.state.signed.SignedStateFileWriter.writeSignedStateFilesToDirectory; +import com.swirlds.base.time.Time; import com.swirlds.cli.utility.SubcommandOf; import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; @@ -74,8 +75,8 @@ public void run() { final Configuration configuration = DefaultConfiguration.buildBasicConfiguration(); - final PlatformContext platformContext = - new DefaultPlatformContext(configuration, new NoOpMetrics(), CryptographyHolder.get()); + final PlatformContext platformContext = new DefaultPlatformContext( + configuration, new NoOpMetrics(), CryptographyHolder.get(), Time.getCurrent()); try (final ReservedSignedState signedState = getStateEditor().getSignedStateCopy()) { writeSignedStateFilesToDirectory(platformContext, NO_NODE_ID, directory, signedState.get()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/appcomm/AppCommComponentTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/appcomm/AppCommComponentTests.java index 5e8279d997b8..fb45cd982fb8 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/appcomm/AppCommComponentTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/components/appcomm/AppCommComponentTests.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import com.swirlds.base.time.Time; import com.swirlds.common.config.singleton.ConfigurationHolder; import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; @@ -54,7 +55,10 @@ public class AppCommComponentTests { public AppCommComponentTests() { context = new DefaultPlatformContext( - ConfigurationHolder.getInstance().get(), new NoOpMetrics(), CryptographyHolder.get()); + ConfigurationHolder.getInstance().get(), + new NoOpMetrics(), + CryptographyHolder.get(), + Time.getCurrent()); } @Test diff --git a/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/com/swirlds/test/framework/context/TestPlatformContextBuilder.java b/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/com/swirlds/test/framework/context/TestPlatformContextBuilder.java index a673d0ea59de..6ed94c4b8e44 100644 --- a/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/com/swirlds/test/framework/context/TestPlatformContextBuilder.java +++ b/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/com/swirlds/test/framework/context/TestPlatformContextBuilder.java @@ -18,6 +18,7 @@ import static com.swirlds.common.config.ConfigUtils.scanAndRegisterAllConfigTypes; +import com.swirlds.base.time.Time; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Cryptography; import com.swirlds.common.crypto.CryptographyHolder; @@ -25,7 +26,9 @@ import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Objects; /** * A simple builder to create a {@link PlatformContext} for unit tests. @@ -40,6 +43,7 @@ public final class TestPlatformContextBuilder { private Configuration configuration; private Metrics metrics; private Cryptography cryptography; + private Time time = Time.getCurrent(); private TestPlatformContextBuilder() {} @@ -48,6 +52,7 @@ private TestPlatformContextBuilder() {} * * @return a new instance */ + @NonNull public static TestPlatformContextBuilder create() { return new TestPlatformContextBuilder(); } @@ -58,6 +63,7 @@ public static TestPlatformContextBuilder create() { * @param configuration the configuration to use * @return the builder instance */ + @NonNull public TestPlatformContextBuilder withConfiguration(@Nullable final Configuration configuration) { this.configuration = configuration; return this; @@ -68,6 +74,7 @@ public TestPlatformContextBuilder withConfiguration(@Nullable final Configuratio * * @param metrics the metrics to use */ + @NonNull public TestPlatformContextBuilder withMetrics(@Nullable final Metrics metrics) { this.metrics = metrics; return this; @@ -78,11 +85,23 @@ public TestPlatformContextBuilder withMetrics(@Nullable final Metrics metrics) { * * @param cryptography the cryptography to use */ + @NonNull public TestPlatformContextBuilder withCryptography(@Nullable final Cryptography cryptography) { this.cryptography = cryptography; return this; } + /** + * Set the {@link Time} to use. + * + * @param time the time to use + */ + @NonNull + public TestPlatformContextBuilder withTime(@NonNull final Time time) { + this.time = Objects.requireNonNull(time); + return this; + } + /** * Returns a new {@link PlatformContext} based on this builder * @@ -114,6 +133,12 @@ public Cryptography getCryptography() { public Metrics getMetrics() { return metrics; } + + @NonNull + @Override + public Time getTime() { + return time; + } }; } } diff --git a/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/module-info.java b/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/module-info.java index 53355a346193..74679dc783d9 100644 --- a/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/module-info.java +++ b/platform-sdk/swirlds-unit-tests/common/swirlds-test-framework/src/main/java/module-info.java @@ -3,7 +3,7 @@ exports com.swirlds.test.framework.context; exports com.swirlds.test.framework.config; - requires com.swirlds.base; + requires transitive com.swirlds.base; requires transitive com.swirlds.common; requires transitive com.swirlds.config.api; requires com.swirlds.config.extensions; diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/AsyncPreconsensusEventWriterTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/AsyncPreconsensusEventWriterTests.java index a5a1fc9b0783..a868ff705d08 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/AsyncPreconsensusEventWriterTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/AsyncPreconsensusEventWriterTests.java @@ -241,7 +241,7 @@ private PlatformContext buildContext() { final Metrics metrics = new NoOpMetrics(); - return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get()); + return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); } /** diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java index fbe6f141df75..745dfdf16308 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesWriterTests.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.swirlds.base.test.fixtures.time.FakeTime; +import com.swirlds.base.time.Time; import com.swirlds.common.config.TransactionConfig_; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; @@ -269,7 +270,7 @@ private PlatformContext buildContext() { final Metrics metrics = new NoOpMetrics(); - return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get()); + return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); } /** diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PreconsensusEventFileManagerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PreconsensusEventFileManagerTests.java index e791d08c4eb3..5a0197e13c82 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PreconsensusEventFileManagerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PreconsensusEventFileManagerTests.java @@ -106,7 +106,7 @@ private PlatformContext buildContext(final boolean permitGaps) { final Metrics metrics = new NoOpMetrics(); - return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get()); + return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); } /** diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/SyncPreconsensusEventWriterTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/SyncPreconsensusEventWriterTests.java index fe54254ab15a..4f03a1cafc24 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/SyncPreconsensusEventWriterTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/SyncPreconsensusEventWriterTests.java @@ -98,7 +98,7 @@ private PlatformContext buildContext() { final Metrics metrics = new NoOpMetrics(); - return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get()); + return new DefaultPlatformContext(configuration, metrics, CryptographyHolder.get(), Time.getCurrent()); } @Test From 577dad783d4f8c2af97d24fb8bd50a72636ef62f Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:46:57 -0600 Subject: [PATCH 62/80] fix: bug when node is removed (#10687) Signed-off-by: Cody Littley --- .../event/creation/tipset/Tipset.java | 22 ++++++--- .../gossip/shadowgraph/SyncUtils.java | 48 ++++++++++++------- .../platform/system/address/AddressBook.java | 10 ++-- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/Tipset.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/Tipset.java index e96024c1540c..abfae00963b0 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/Tipset.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/event/creation/tipset/Tipset.java @@ -36,6 +36,12 @@ public class Tipset { */ private final long[] tips; + /** + * The value used to represent an undefined tip generation, either because the node ID is not in the address book or + * because there is no known event for the node ID in this event's ancestry. + */ + public static final long UNDEFINED = -1L; + /** * Create an empty tipset. * @@ -46,11 +52,11 @@ public Tipset(@NonNull final AddressBook addressBook) { tips = new long[addressBook.getSize()]; // Necessary because we currently start at generation 0, not generation 1. - Arrays.fill(tips, -1); + Arrays.fill(tips, UNDEFINED); } /** - * Build an empty tipset (i.e. where all generations are -1) using another tipset as a template. + * Build an empty tipset (i.e. where all generations are {@link #UNDEFINED}) using another tipset as a template. * * @param tipset the tipset to use as a template * @return a new empty tipset @@ -81,7 +87,7 @@ public Tipset(@NonNull final AddressBook addressBook) { final Tipset newTipset = buildEmptyTipset(tipsets.get(0)); for (int index = 0; index < length; index++) { - long max = -1; + long max = UNDEFINED; for (final Tipset tipSet : tipsets) { max = Math.max(max, tipSet.tips[index]); } @@ -98,7 +104,11 @@ public Tipset(@NonNull final AddressBook addressBook) { * @return the tip generation for the node */ public long getTipGenerationForNode(@NonNull final NodeId nodeId) { - return tips[addressBook.getIndexOfNodeId(nodeId)]; + final int index = addressBook.getIndexOfNodeId(nodeId); + if (index == AddressBook.NOT_IN_ADDRESS_BOOK_INDEX) { + return UNDEFINED; + } + return tips[index]; } /** @@ -131,8 +141,8 @@ public int size() { * *

    * A tip advancement is defined as an increase in the tip generation for a node ID. The exception to this rule is - * that an increase in generation for the self ID is never counted as a tip advancement. The tip advancement - * weight is defined as the sum of all remaining tip advancements after being appropriately weighted. + * that an increase in generation for the self ID is never counted as a tip advancement. The tip advancement weight + * is defined as the sum of all remaining tip advancements after being appropriately weighted. *

    * *

    diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java index e70ff38a75e9..7fa1461587e2 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/shadowgraph/SyncUtils.java @@ -432,24 +432,36 @@ public static List filterLikelyDuplicates( continue; } - // FUTURE WORK: make sure this doesn't break when AB size changes - - // We want to answer the question: "Is this event an ancestor of my latest self event?" - // The latest self event's tipset makes this easy to answer. A tipset is basically just - // an array containing the latest generations of each event creator in our ancestry. - // If we compare the generation of the event we're looking at to the generation of the - // latest ancestor from the same creator, we can tell if this is an ancestor. If the event's - // generation is less than or equal to the latest ancestor's generation, then it is an ancestor. - // If it is greater than the latest ancestor's generation, then it is not an ancestor. - // - // Note: there is an edge case where this breaks down a little. If this event's creator is branching, - // then we may falsely conclude that this event is in our ancestry when it is not. But this is not - // harmful. The purpose of this method is to reduce the number of events we send to the peer, and so - // the worst that can happen is that we send a few extra events and get a slightly higher duplication rate. - - final boolean isAncestor = latestSelfEventTipset != null - && latestSelfEventTipset.getTipGenerationForNode(event.getCreatorId()) >= event.getGeneration(); - if (isAncestor) { + if (latestSelfEventTipset == null) { + // Special case: we have no self events yet. + filteredList.add(event); + continue; + } + + final long latestGenerationInAncestry = latestSelfEventTipset.getTipGenerationForNode(event.getCreatorId()); + + if (latestGenerationInAncestry == Tipset.UNDEFINED) { + // Special case: we don't have enough information to decide if this node is in our ancestry. + filteredList.add(event); + continue; + } + + if (latestGenerationInAncestry >= event.getGeneration()) { + // This event is an ancestor* of our latest self event. + // + // We want to answer the question: "Is this event an ancestor of my latest self event?" + // The latest self event's tipset makes this easy to answer. A tipset is basically just + // an array containing the latest generations of each event creator in our ancestry. + // If we compare the generation of the event we're looking at to the generation of the + // latest ancestor from the same creator, we can tell if this is an ancestor. If the event's + // generation is less than or equal to the latest ancestor's generation, then it is an ancestor. + // If it is greater than the latest ancestor's generation, then it is not an ancestor. + // + // *Note: there is an edge case where this breaks down a little. If this event's creator is branching, + // then we may falsely conclude that this event is in our ancestry when it is not. But this is not + // harmful. The purpose of this method is to reduce the number of events we send to the peer, and so + // the worst that can happen is that we send a few extra events and get a slightly higher duplication + // rate. filteredList.add(event); continue; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBook.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBook.java index 8241a3830e01..811b711fa1ab 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBook.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBook.java @@ -42,6 +42,11 @@ */ public class AddressBook extends PartialMerkleLeaf implements Iterable

    , MerkleLeaf { + /** + * The index of a node ID that does not exist in the address book. + */ + public static final int NOT_IN_ADDRESS_BOOK_INDEX = -1; + public static final long CLASS_ID = 0x4ee5498ef623fbe0L; private static class ClassVersion { @@ -269,10 +274,7 @@ public NodeId getNodeId(final int index) { */ public int getIndexOfNodeId(@NonNull final NodeId id) { Objects.requireNonNull(id, "nodeId is null"); - if (!addresses.containsKey(id)) { - throw new NoSuchElementException("no address with id " + id + " exists"); - } - return nodeIndices.getOrDefault(id, -1); + return nodeIndices.getOrDefault(id, NOT_IN_ADDRESS_BOOK_INDEX); } /** From b5574b1822372b2a44669c12932951b9f0398ec7 Mon Sep 17 00:00:00 2001 From: JivkoKelchev Date: Sat, 30 Dec 2023 19:43:26 +0200 Subject: [PATCH 63/80] fix: Fuzzy matching for CreateOperationSuite and Create2OperationSuite 09431 (#10185) Signed-off-by: Zhivko Kelchev Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../app/workflows/handle/HandleWorkflow.java | 60 ++++++-- .../SingleTransactionRecordBuilderImpl.java | 2 + .../handle/record/RecordListBuilderTest.java | 50 +++---- .../SingleTransactionRecordBuilderTest.java | 4 +- .../java/contract/AbstractContractXTest.java | 24 +--- .../contract/ClassicViewsXTestConstants.java | 4 +- .../java/contract/CreatesERC20XTest.java | 4 +- .../java/contract/CreatesERC721XTest.java | 4 +- .../java/contract/CreatesXTestConstants.java | 3 +- .../src/xtest/java/contract/FuseXTest.java | 2 +- .../src/xtest/java/contract/MintsXTest.java | 6 +- .../xtest/java/contract/UpdatesKeysXTest.java | 26 +--- .../xtest/java/contract/XTestConstants.java | 5 - .../operations/CustomCreate2Operation.java | 3 +- .../CustomContractCreationProcessor.java | 7 + .../exec/scope/HandleHederaOperations.java | 133 ++++++++++------- .../scope/HandleSystemContractOperations.java | 32 +++++ .../impl/exec/scope/HederaOperations.java | 3 +- .../exec/scope/QueryHederaOperations.java | 5 +- .../scope/QuerySystemContractOperations.java | 20 ++- .../exec/scope/SystemContractOperations.java | 23 +++ .../systemcontracts/HtsSystemContract.java | 21 +-- .../hts/DispatchForResponseCodeHtsCall.java | 50 +++---- .../associations/AssociationsTranslator.java | 4 +- .../hts/burn/BurnTranslator.java | 4 +- .../hts/create/ClassicCreatesCall.java | 122 +++++++++------- .../hts/create/CreateDecoder.java | 37 +++-- .../hts/create/CreateSyntheticTxnFactory.java | 4 + .../hts/create/CreateTranslator.java | 3 +- .../hts/delete/DeleteTranslator.java | 7 +- .../hts/freeze/FreezeUnfreezeTranslator.java | 4 +- .../GrantApprovalTranslator.java | 8 +- .../GrantRevokeKycTranslator.java | 4 +- .../hts/mint/MintTranslator.java | 4 +- .../hts/pauses/PausesTranslator.java | 4 +- .../hts/update/UpdateDecoder.java | 36 +++-- .../hts/update/UpdateExpiryTranslator.java | 9 +- .../hts/update/UpdateKeysTranslator.java | 4 +- .../hts/update/UpdateTranslator.java | 9 +- .../hts/wipe/WipeTranslator.java | 4 +- .../impl/exec/utils/KeyValueWrapper.java | 76 ++++------ .../impl/handlers/ContractDeleteHandler.java | 8 +- .../impl/handlers/ContractUpdateHandler.java | 19 +-- .../handlers/EthereumTransactionHandler.java | 43 ++++-- .../impl/hevm/HederaWorldUpdater.java | 5 +- .../impl/state/ProxyWorldUpdater.java | 18 ++- .../contract/impl/utils/ConversionUtils.java | 6 +- .../contract/impl/utils/SynthTxnUtils.java | 14 +- .../CustomCreate2OperationTest.java | 2 +- .../scope/HandleHederaOperationsTest.java | 135 ++++++++++++++++-- .../HandleSystemContractOperationsTest.java | 36 +++++ .../DispatchForResponseCodeHtsCallTest.java | 45 +++++- .../handlers/ContractHandlerTestBase.java | 7 + .../handlers/ContractUpdateHandlerTest.java | 35 +++++ .../test/state/ProxyWorldUpdaterTest.java | 6 +- .../impl/test/utils/SynthTxnUtilsTest.java | 3 + .../token/impl/WritableAccountStore.java | 5 +- .../token/impl/api/TokenServiceApiImpl.java | 18 +-- .../impl/handlers/CryptoDeleteHandler.java | 4 +- .../staking/EndOfStakingPeriodUpdater.java | 6 +- .../service/token/api/TokenServiceApi.java | 9 +- .../record-snapshots/Create2Operation.json | 1 + .../record-snapshots/CreateOperation.json | 1 + .../ops/hollow/RandomHollowAccount.java | 6 +- .../queries/crypto/HapiGetAccountRecords.java | 2 +- .../spec/queries/meta/HapiGetTxnRecord.java | 7 + .../utilops/records/SnapshotMatchMode.java | 10 +- .../spec/utilops/records/SnapshotModeOp.java | 15 +- .../opcodes/Create2OperationSuite.java | 91 ++++++++---- .../opcodes/CreateOperationSuite.java | 28 +++- .../opcodes/ExtCodeSizeOperationSuite.java | 10 +- .../suites/contract/records/LogsSuite.java | 31 ++-- .../crypto/AutoAccountCreationSuite.java | 53 ++++--- .../ethereum/HelloWorldEthereumSuite.java | 10 +- .../suites/leaky/LeakyContractTestsSuite.java | 4 +- .../suites/leaky/LeakyCryptoTestsSuite.java | 2 - 76 files changed, 1005 insertions(+), 524 deletions(-) create mode 100644 hedera-node/test-clients/record-snapshots/Create2Operation.json create mode 100644 hedera-node/test-clients/record-snapshots/CreateOperation.json diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index bbf2e48afe8e..98e9dbfec3ed 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -16,6 +16,7 @@ package com.hedera.node.app.workflows.handle; +import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.CONSENSUS_GAS_EXHAUSTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; @@ -24,6 +25,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.ContractServiceImpl.CONTRACT_SERVICE; import static com.hedera.node.app.spi.HapiUtils.isHollow; import static com.hedera.node.app.spi.key.KeyUtils.IMMUTABILITY_SENTINEL_KEY; import static com.hedera.node.app.state.HederaRecordCache.DuplicateCheckResult.NO_DUPLICATE; @@ -43,6 +45,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.SignatureMap; import com.hedera.hapi.node.base.Transaction; @@ -54,6 +57,7 @@ import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.records.BlockRecordManager; +import com.hedera.node.app.service.file.ReadableFileStore; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.records.ChildRecordFinalizer; @@ -62,12 +66,14 @@ import com.hedera.node.app.services.ServiceScopeLookup; import com.hedera.node.app.signature.DefaultKeyVerifier; import com.hedera.node.app.signature.KeyVerifier; +import com.hedera.node.app.signature.impl.SignatureVerificationImpl; import com.hedera.node.app.spi.authorization.Authorizer; import com.hedera.node.app.spi.authorization.SystemPrivilege; import com.hedera.node.app.spi.fees.FeeAccumulator; import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.info.NetworkInfo; import com.hedera.node.app.spi.info.NodeInfo; +import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory; import com.hedera.node.app.spi.workflows.HandleException; @@ -99,9 +105,10 @@ import com.swirlds.platform.system.events.ConsensusEvent; import com.swirlds.platform.system.transaction.ConsensusTransaction; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; import java.util.EnumSet; -import java.util.Objects; +import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; @@ -114,10 +121,8 @@ public class HandleWorkflow { private static final Logger logger = LogManager.getLogger(HandleWorkflow.class); - private static final Set DISPATCHING_CONTRACT_TRANSACTIONS = EnumSet.of( - HederaFunctionality.CONTRACT_CREATE, - HederaFunctionality.CONTRACT_CALL, - HederaFunctionality.ETHEREUM_TRANSACTION); + private static final Set DISPATCHING_CONTRACT_TRANSACTIONS = + EnumSet.of(HederaFunctionality.CONTRACT_CREATE, HederaFunctionality.CONTRACT_CALL, ETHEREUM_TRANSACTION); private final NetworkInfo networkInfo; private final PreHandleWorkflow preHandleWorkflow; @@ -424,7 +429,37 @@ private void handleUserTransaction( try { // Any hollow accounts that must sign to have all needed signatures, need to be finalized // as a result of transaction being handled. - finalizeHollowAccounts(context, configuration, preHandleResult.getHollowAccounts(), verifier); + Set hollowAccounts = preHandleResult.getHollowAccounts(); + SignatureVerification maybeEthTxVerification = null; + if (transactionInfo.functionality() == ETHEREUM_TRANSACTION) { + final var maybeEthTxSigs = CONTRACT_SERVICE + .handlers() + .ethereumTransactionHandler() + .maybeEthTxSigsFor( + transactionInfo.txBody().ethereumTransactionOrThrow(), + readableStoreFactory.getStore(ReadableFileStore.class), + configuration); + if (maybeEthTxSigs != null) { + final var alias = Bytes.wrap(maybeEthTxSigs.address()); + final var accountStore = readableStoreFactory.getStore(ReadableAccountStore.class); + final var maybeHollowAccountId = accountStore.getAccountIDByAlias(alias); + if (maybeHollowAccountId != null) { + final var maybeHollowAccount = + requireNonNull(accountStore.getAccountById(maybeHollowAccountId)); + if (isHollow(maybeHollowAccount)) { + hollowAccounts = new LinkedHashSet<>(preHandleResult.getHollowAccounts()); + hollowAccounts.add(maybeHollowAccount); + maybeEthTxVerification = new SignatureVerificationImpl( + Key.newBuilder() + .ecdsaSecp256k1(Bytes.wrap(maybeEthTxSigs.publicKey())) + .build(), + alias, + true); + } + } + } + } + finalizeHollowAccounts(context, configuration, hollowAccounts, verifier, maybeEthTxVerification); networkUtilizationManager.trackTxn(transactionInfo, consensusNow, stack); // If the payer is authorized to waive fees, then we don't charge them @@ -530,12 +565,14 @@ private void handleUserTransaction( * @param configuration the configuration * @param accounts the set of hollow accounts that need to be finalized * @param verifier the key verifier + * @param ethTxVerification */ private void finalizeHollowAccounts( @NonNull final HandleContext context, @NonNull final Configuration configuration, @NonNull final Set accounts, - @NonNull final DefaultKeyVerifier verifier) { + @NonNull final DefaultKeyVerifier verifier, + @Nullable SignatureVerification ethTxVerification) { final var consensusConfig = configuration.getConfigData(ConsensusConfig.class); final var precedingHollowAccountRecords = accounts.size(); final var maxRecords = consensusConfig.handleMaxPrecedingRecords(); @@ -546,9 +583,12 @@ private void finalizeHollowAccounts( } else { for (final var hollowAccount : accounts) { // get the verified key for this hollow account - final var verification = Objects.requireNonNull( - verifier.verificationFor(hollowAccount.alias()), - "Required hollow account verified signature did not exist"); + final var verification = + ethTxVerification != null && hollowAccount.alias().equals(ethTxVerification.evmAlias()) + ? ethTxVerification + : requireNonNull( + verifier.verificationFor(hollowAccount.alias()), + "Required hollow account verified signature did not exist"); if (verification.key() != null) { if (!IMMUTABILITY_SENTINEL_KEY.equals(hollowAccount.keyOrThrow())) { logger.error("Hollow account {} has a key other than the sentinel key", hollowAccount); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index b5df857487a1..66241b4b0067 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -805,6 +805,8 @@ public SingleTransactionRecordBuilderImpl fileID(@NonNull final FileID fileID) { @Override @NonNull public SingleTransactionRecordBuilderImpl contractID(@Nullable final ContractID contractID) { + // Ensure we don't externalize as an account creation too + transactionReceiptBuilder.accountID((AccountID) null); transactionReceiptBuilder.contractID(contractID); return this; } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/RecordListBuilderTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/RecordListBuilderTest.java index 1bea9c0f3524..e75d73c17640 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/RecordListBuilderTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/RecordListBuilderTest.java @@ -34,7 +34,6 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.record.RecordListCheckPoint; import com.hedera.node.app.state.SingleTransactionRecord; -import com.hedera.node.config.data.ConsensusConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; @@ -54,8 +53,6 @@ class RecordListBuilderTest extends AppTestBase { .withValue("consensus.handle.maxFollowingRecords", MAX_CHILDREN) .getOrCreateConfig(); private static final int EXPECTED_CHILD_NANO_INCREMENT = 0; - private static final int EXPECTED_CHILD_NANO_INCREMENT_SCHEDULED = - Math.toIntExact(CONFIGURATION.getConfigData(ConsensusConfig.class).handleMaxPrecedingRecords()); @SuppressWarnings("ConstantConditions") @Test @@ -498,23 +495,23 @@ void testAddPrecedingAndChildRecords() throws IOException { .nanosBefore(2, result.userTransactionRecord()) .hasNonce(2) .hasNoParent() - .hasTransaction(first); + .hasSignedTransaction(first); assertCreatedRecord(records.get(1)) .nanosBefore(1, result.userTransactionRecord()) .hasNonce(1) .hasNoParent() - .hasTransaction(second); + .hasSignedTransaction(second); assertCreatedRecord(records.get(2)).hasNonce(0).hasNoParent(); assertCreatedRecord(records.get(3)) .nanosAfter(1, result.userTransactionRecord()) .hasNonce(3) .hasParent(result.userTransactionRecord()) - .hasTransaction(fourth); + .hasSignedTransaction(fourth); assertCreatedRecord(records.get(4)) .nanosAfter(2, result.userTransactionRecord()) .hasNonce(4) .hasParent(result.userTransactionRecord()) - .hasTransaction(fifth); + .hasSignedTransaction(fifth); } @Test @@ -728,7 +725,7 @@ void testRevertSingleRemovableChild() { .nanosAfter(1, result.userTransactionRecord()) .hasNonce(1) .hasResponseCode(OK) - .hasTransaction(remainingTx) + .hasSignedTransaction(remainingTx) .hasParent(result.userTransactionRecord()); } @@ -767,13 +764,13 @@ void testRevertMultipleRemovableChildren() { .nanosAfter(1, result.userTransactionRecord()) .hasNonce(1) .hasResponseCode(OK) - .hasTransaction(child1Tx) + .hasSignedTransaction(child1Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(2)) .nanosAfter(2, result.userTransactionRecord()) .hasNonce(2) .hasResponseCode(OK) - .hasTransaction(remainingTx) + .hasSignedTransaction(remainingTx) .hasParent(result.userTransactionRecord()); } @@ -818,45 +815,45 @@ void testRevertMultipleMixedChildren() { .nanosAfter(1, result.userTransactionRecord()) // first child gets next consensus time .hasNonce(1) // first child .hasResponseCode(OK) // child3's children were reverted, first child comes before, so it is not affected - .hasTransaction(child1Tx) + .hasSignedTransaction(child1Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(2)) .nanosAfter(2, result.userTransactionRecord()) .hasNonce(2) // second child .hasResponseCode( OK) // child3's children were reverted, second child comes before, so it is not affected - .hasTransaction(child2Tx) + .hasSignedTransaction(child2Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(3)) .nanosAfter(3, result.userTransactionRecord()) .hasNonce(3) // third child. The children of this was were reverted .hasResponseCode(OK) // child3's children were reverted, third child is not affected - .hasTransaction(child3Tx) + .hasSignedTransaction(child3Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(4)) // child4 was removed, but for mono-service fidelity we "smooth" the gap in consensus times .nanosAfter(4, result.userTransactionRecord()) .hasNonce(4) // child5 gets the 4th nonce since child4 was removed .hasResponseCode(REVERTED_SUCCESS) - .hasTransaction(child5Tx) + .hasSignedTransaction(child5Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(5)) .nanosAfter(5, result.userTransactionRecord()) // immediately after child5 .hasNonce(5) // child6 gets the 5th nonce since child4 was removed .hasResponseCode(REVERTED_SUCCESS) - .hasTransaction(child6Tx) + .hasSignedTransaction(child6Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(6)) .nanosAfter(6, result.userTransactionRecord()) .hasNonce(6) .hasResponseCode(OK) - .hasTransaction(child8Tx) + .hasSignedTransaction(child8Tx) .hasParent(result.userTransactionRecord()); assertCreatedRecord(records.get(7)) .nanosAfter(7, result.userTransactionRecord()) .hasNonce(7) .hasResponseCode(OK) - .hasTransaction(child9Tx) + .hasSignedTransaction(child9Tx) .hasParent(result.userTransactionRecord()); } @@ -934,24 +931,24 @@ void revertChildrenFrom_Correctly_Revert_ChildrenBothWays() { .nanosBefore(2, result.userTransactionRecord()) .hasNonce(2) .hasNoParent() - .hasTransaction(first); + .hasSignedTransaction(first); assertCreatedRecord(records.get(1)) .nanosBefore(1, result.userTransactionRecord()) .hasNonce(1) .hasNoParent() - .hasTransaction(second); + .hasSignedTransaction(second); assertCreatedRecord(records.get(2)).hasNonce(0).hasNoParent(); assertCreatedRecord(records.get(3)) .nanosAfter(1, result.userTransactionRecord()) .hasNonce(3) .hasParent(result.userTransactionRecord()) - .hasTransaction(third); + .hasSignedTransaction(third); assertCreatedRecord(records.get(4)) .nanosAfter(2, result.userTransactionRecord()) .hasNonce(4) .hasResponseCode(REVERTED_SUCCESS) .hasParent(result.userTransactionRecord()) - .hasTransaction(fourth); + .hasSignedTransaction(fourth); } @Test @@ -1045,18 +1042,18 @@ void revertChildrenFrom_Correctly_Revert_RemovablePrecedingChild() { .hasNonce(2) .hasResponseCode(REVERTED_SUCCESS) .hasNoParent() - .hasTransaction(first); + .hasSignedTransaction(first); assertCreatedRecord(records.get(1)) .nanosBefore(1, result.userTransactionRecord()) .hasNonce(1) .hasNoParent() - .hasTransaction(second); + .hasSignedTransaction(second); assertCreatedRecord(records.get(2)).hasNonce(0).hasNoParent(); assertCreatedRecord(records.get(3)) .nanosAfter(1, result.userTransactionRecord()) .hasNonce(3) .hasParent(result.userTransactionRecord()) - .hasTransaction(third); + .hasSignedTransaction(third); } private SingleTransactionRecordBuilderImpl addUserTransaction(final RecordListBuilder builder) { @@ -1128,5 +1125,10 @@ public TransactionRecordAssertions hasTransaction(@NonNull final Transaction tx) assertThat(record.transaction()).isEqualTo(tx); return this; } + + public TransactionRecordAssertions hasSignedTransaction(@NonNull final Transaction tx) { + assertThat(record.transaction().signedTransactionBytes()).isEqualTo(tx.signedTransactionBytes()); + return this; + } } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderTest.java index dbea23653c06..c96f577c62bc 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderTest.java @@ -37,7 +37,6 @@ import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.hapi.node.transaction.AssessedCustomFee; import com.hedera.hapi.node.transaction.ExchangeRateSet; -import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.hapi.node.transaction.TransactionReceipt; import com.hedera.hapi.node.transaction.TransactionRecord; import com.hedera.hapi.streams.ContractActions; @@ -73,7 +72,6 @@ public class SingleTransactionRecordBuilderTest { public static final long NEW_TOTAL_SUPPLY = 34134546L; public static final String MEMO = "Yo Memo"; private @Mock Transaction transaction; - private @Mock TransactionBody transactionBody; private @Mock TransactionID transactionID; private final Bytes transactionBytes = Bytes.wrap("Hello Tester"); private @Mock ContractFunctionResult contractCallResult; @@ -234,7 +232,7 @@ void testBuilder(TransactionRecord.EntropyOneOfType entropyOneOfType) { private void assertTransactionReceiptProps(TransactionReceipt receipt, List serialNumbers) { assertEquals(status, receipt.status()); - assertEquals(accountID, receipt.accountID()); + assertNull(receipt.accountID()); assertEquals(fileID, receipt.fileID()); assertEquals(contractID, receipt.contractID()); assertNull(receipt.exchangeRate()); diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java index 059e264efccb..4605370f8bae 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java @@ -77,7 +77,6 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.ArrayDeque; import java.util.Deque; @@ -188,13 +187,6 @@ protected void runHtsCallAndExpectOnSuccess( runHtsCallAndExpectOnSuccess(false, sender, input, outputAssertions, context); } - protected void runDelegatedHtsCallAndExpectOnSuccess( - @NonNull final org.hyperledger.besu.datatypes.Address sender, - @NonNull final org.apache.tuweni.bytes.Bytes input, - @NonNull final Consumer outputAssertions) { - runHtsCallAndExpectOnSuccess(true, sender, input, outputAssertions, null); - } - private void runHtsCallAndExpectOnSuccess( final boolean requiresDelegatePermission, @NonNull final org.hyperledger.besu.datatypes.Address sender, @@ -253,19 +245,6 @@ private void internalRunHtsCallAndExpectRevert( })); } - private void runHtsCallAndExpectRevert( - final boolean requiresDelegatePermission, - @NonNull final org.hyperledger.besu.datatypes.Address sender, - @NonNull final org.apache.tuweni.bytes.Bytes input, - @NonNull final ResponseCodeEnum status) { - runHtsCallAndExpect(requiresDelegatePermission, sender, input, resultOnlyAssertion(result -> { - assertEquals(MessageFrame.State.REVERT, result.getState()); - final var impliedReason = - org.apache.tuweni.bytes.Bytes.wrap(status.protoName().getBytes(StandardCharsets.UTF_8)); - assertEquals(impliedReason, result.getOutput()); - })); - } - private void runHtsCallAndExpect( final boolean requiresDelegatePermission, @NonNull final org.hyperledger.besu.datatypes.Address sender, @@ -287,7 +266,8 @@ private void runHtsCallAndExpect( context, tinybarValues, systemContractGasCalculator, - component.config().getConfigData(HederaConfig.class)), + component.config().getConfigData(HederaConfig.class), + HederaFunctionality.CONTRACT_CALL), new HandleHederaNativeOperations(context), new HandleSystemContractOperations(context)); given(proxyUpdater.enhancement()).willReturn(enhancement); diff --git a/hedera-node/hedera-app/src/xtest/java/contract/ClassicViewsXTestConstants.java b/hedera-node/hedera-app/src/xtest/java/contract/ClassicViewsXTestConstants.java index d255619996b4..5c032a0fd64d 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/ClassicViewsXTestConstants.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/ClassicViewsXTestConstants.java @@ -27,7 +27,7 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator.TOKEN_INFO; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokenkey.TokenKeyTranslator.TOKEN_KEY; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.headlongAddressOf; -import static contract.MiscViewsXTestConstants.ERC_USER_ID; +import static contract.MiscViewsXTestConstants.ERC_USER_ADDRESS; import static contract.MiscViewsXTestConstants.OPERATOR_ID; import static contract.XTestConstants.ERC20_TOKEN_ID; @@ -244,7 +244,7 @@ static final ByteBuffer returnExpectedKey(@NonNull final Key key) { EXPECTED_ROYALTY_CUSTOM_FEES.toArray(new Tuple[0]), LEDGER_ID), 1L, - headlongAddressOf(ERC_USER_ID), + ERC_USER_ADDRESS, 0L, com.hedera.pbj.runtime.io.buffer.Bytes.wrap("https://example.com/721/1") .toByteArray(), diff --git a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java index 02c0039ad4a5..fa386dd27b10 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC20XTest.java @@ -40,6 +40,7 @@ import static contract.CreatesXTestConstants.TOKEN_KEY_TWO; import static contract.CreatesXTestConstants.hederaTokenFactory; import static contract.XTestConstants.AN_ED25519_KEY; +import static contract.XTestConstants.COINBASE_ID; import static contract.XTestConstants.ERC20_TOKEN_ID; import static contract.XTestConstants.INVALID_ACCOUNT_HEADLONG_ADDRESS; import static contract.XTestConstants.ONE_HBAR; @@ -804,13 +805,14 @@ protected Map initialTokenRelationships() { @Override protected Map initialAccounts() { final Map accounts = new HashMap<>(); + accounts.put(COINBASE_ID, Account.newBuilder().accountId(COINBASE_ID).build()); accounts.put( SENDER_ID, Account.newBuilder() .accountId(SENDER_ID) .alias(SENDER_ADDRESS) .key(AN_ED25519_KEY) - .tinybarBalance(100 * ONE_HBAR) + .tinybarBalance(10_000 * ONE_HBAR) .build()); accounts.put( OWNER_ID, diff --git a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java index 6fef4556da35..4fbf42be4870 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/CreatesERC721XTest.java @@ -37,6 +37,7 @@ import static contract.CreatesXTestConstants.TOKEN_SUPPLY_KEY; import static contract.CreatesXTestConstants.hederaTokenFactory; import static contract.XTestConstants.AN_ED25519_KEY; +import static contract.XTestConstants.COINBASE_ID; import static contract.XTestConstants.ERC20_TOKEN_ID; import static contract.XTestConstants.INVALID_ACCOUNT_HEADLONG_ADDRESS; import static contract.XTestConstants.ONE_HBAR; @@ -661,13 +662,14 @@ protected Map initialTokenRelationships() { @Override protected Map initialAccounts() { final Map accounts = new HashMap<>(); + accounts.put(COINBASE_ID, Account.newBuilder().accountId(COINBASE_ID).build()); accounts.put( SENDER_ID, Account.newBuilder() .accountId(SENDER_ID) .alias(SENDER_ADDRESS) .key(AN_ED25519_KEY) - .tinybarBalance(100 * ONE_HBAR) + .tinybarBalance(10_000 * ONE_HBAR) .build()); accounts.put( OWNER_ID, diff --git a/hedera-node/hedera-app/src/xtest/java/contract/CreatesXTestConstants.java b/hedera-node/hedera-app/src/xtest/java/contract/CreatesXTestConstants.java index ed6dad775174..d5a2fde2b5b2 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/CreatesXTestConstants.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/CreatesXTestConstants.java @@ -49,8 +49,7 @@ public class CreatesXTestConstants { BigInteger.valueOf(80), Tuple.of(true, asAddress(""), new byte[] {}, new byte[] {}, asAddress(""))); static final Tuple TOKEN_INVALID_ADMIN_KEY = Tuple.of( - BigInteger.valueOf(1), - Tuple.of(false, INVALID_ACCOUNT_HEADLONG_ADDRESS, new byte[] {}, new byte[] {}, asAddress(""))); + BigInteger.valueOf(1), Tuple.of(false, asAddress(""), new byte[] {}, new byte[] {}, asAddress(""))); static final Tuple TOKEN_INVALID_SUPPLY_KEY = Tuple.of( BigInteger.valueOf(16), Tuple.of(false, INVALID_ACCOUNT_HEADLONG_ADDRESS, new byte[] {}, new byte[] {}, asAddress(""))); diff --git a/hedera-node/hedera-app/src/xtest/java/contract/FuseXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/FuseXTest.java index c6cde3bf229a..364959cce1c3 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/FuseXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/FuseXTest.java @@ -36,7 +36,7 @@ import java.util.Map; public class FuseXTest extends AbstractContractXTest { - static final long GAS = 300_000L; + static final long GAS = 600_000L; static final long NEXT_ENTITY_NUM = 1234L; private static final FileID FUSE_INITCODE_ID = FileID.newBuilder().fileNum(1002L).build(); diff --git a/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java index 315cb03e744c..9531f49f1c20 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/MintsXTest.java @@ -38,11 +38,11 @@ import static contract.XTestConstants.ERC20_TOKEN_ID; import static contract.XTestConstants.ERC721_TOKEN_ADDRESS; import static contract.XTestConstants.ERC721_TOKEN_ID; -import static contract.XTestConstants.INVALID_CONTRACT_ID_KEY; import static contract.XTestConstants.INVALID_TOKEN_ADDRESS; import static contract.XTestConstants.OWNER_ADDRESS; import static contract.XTestConstants.OWNER_BESU_ADDRESS; import static contract.XTestConstants.OWNER_ID; +import static contract.XTestConstants.SENDER_CONTRACT_ID_KEY; import static contract.XTestConstants.SN_1234; import static contract.XTestConstants.SN_1234_METADATA; import static contract.XTestConstants.addErc20Relation; @@ -347,7 +347,7 @@ protected Map initialTokens() { .tokenId(C_TOKEN_ID) .treasuryAccountId(UNAUTHORIZED_SPENDER_ID) .tokenType(TokenType.FUNGIBLE_COMMON) - .supplyKey(INVALID_CONTRACT_ID_KEY) + .supplyKey(SENDER_CONTRACT_ID_KEY) .build()); tokens.put( D_TOKEN_ID, @@ -355,7 +355,7 @@ protected Map initialTokens() { .tokenId(D_TOKEN_ID) .treasuryAccountId(UNAUTHORIZED_SPENDER_ID) .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) - .supplyKey(INVALID_CONTRACT_ID_KEY) + .supplyKey(SENDER_CONTRACT_ID_KEY) .build()); return tokens; } diff --git a/hedera-node/hedera-app/src/xtest/java/contract/UpdatesKeysXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/UpdatesKeysXTest.java index a4be63c01caa..12d031b6ddf2 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/UpdatesKeysXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/UpdatesKeysXTest.java @@ -26,8 +26,6 @@ import static contract.XTestConstants.ERC20_TOKEN_ADDRESS; import static contract.XTestConstants.ERC20_TOKEN_ID; import static contract.XTestConstants.INVALID_ACCOUNT_ADDRESS; -import static contract.XTestConstants.INVALID_ACCOUNT_HEADLONG_ADDRESS; -import static contract.XTestConstants.INVALID_CONTRACT_ID_KEY; import static contract.XTestConstants.INVALID_ID; import static contract.XTestConstants.INVALID_TOKEN_ADDRESS; import static contract.XTestConstants.SENDER_ADDRESS; @@ -75,7 +73,8 @@ public class UpdatesKeysXTest extends AbstractContractXTest { private final Tuple[] INVALID_TOKEN_KEY = new Tuple[] { Tuple.of( BigInteger.valueOf(1), - Tuple.of(false, asAddress(""), new byte[] {}, new byte[] {}, INVALID_ACCOUNT_HEADLONG_ADDRESS)) + // Ensure this key is invalid by leaving all key options unset + Tuple.of(false, asAddress(""), new byte[] {}, new byte[] {}, asAddress(""))) }; @Override @@ -87,7 +86,7 @@ protected void doScenarioOperations() { .encodeCallWithArgs(ERC20_TOKEN_ADDRESS, TOKEN_KEY) .array()), assertSuccess()); - // Should throw `INVALID_SIGNATURE` as we are passing an invalid admin key + // Should throw `INVALID_SIGNATURE` as the sender is not authorized via a contractID runHtsCallAndExpectOnSuccess( SENDER_BESU_ADDRESS, Bytes.wrap(UpdateKeysTranslator.TOKEN_UPDATE_KEYS_FUNCTION @@ -101,10 +100,8 @@ protected void doScenarioOperations() { Bytes.wrap(UpdateKeysTranslator.TOKEN_UPDATE_KEYS_FUNCTION .encodeCallWithArgs(ERC20_TOKEN_ADDRESS, INVALID_TOKEN_KEY) .array()), - output -> { - assertEquals( - Bytes.wrap(ReturnTypes.encodedRc(INVALID_ADMIN_KEY).array()), output); - }); + output -> assertEquals( + Bytes.wrap(ReturnTypes.encodedRc(INVALID_ADMIN_KEY).array()), output, "Invalid admin key")); // Should throw `INVALID_TOKEN_ID` as we are passing an invalid token address runHtsCallAndExpectOnSuccess( SENDER_BESU_ADDRESS, @@ -146,17 +143,6 @@ protected Map initialTokens() { .adminKey(SENDER_CONTRACT_ID_KEY) .autoRenewAccountId(SENDER_ID) .build()); - - tokens.put( - ERC20_TOKEN_ID, - Token.newBuilder() - .tokenId(ERC20_TOKEN_ID) - .treasuryAccountId(INVALID_ID) - .tokenType(TokenType.FUNGIBLE_COMMON) - .supplyKey(AN_ED25519_KEY) - .adminKey(INVALID_CONTRACT_ID_KEY) - .autoRenewAccountId(INVALID_ID) - .build()); tokens.put( A_TOKEN_ID, Token.newBuilder() @@ -185,7 +171,7 @@ protected Map initialAccounts() { Account.newBuilder() .accountId(INVALID_ID) .alias(INVALID_ACCOUNT_ADDRESS) - .key(INVALID_CONTRACT_ID_KEY) + .key(SENDER_CONTRACT_ID_KEY) .smartContract(true) .build()); return accounts; diff --git a/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java b/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java index e0ec69745af8..1c20f3644ac7 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/XTestConstants.java @@ -76,11 +76,6 @@ public class XTestConstants { AccountID.newBuilder().accountNum(987654321L).build(); public static final AccountID INVALID_ID = AccountID.newBuilder().accountNum(Long.MAX_VALUE).build(); - public static final Key INVALID_CONTRACT_ID_KEY = Key.newBuilder() - .contractID(ContractID.newBuilder() - .contractNum(SENDER_ID.accountNumOrThrow()) - .build()) - .build(); public static final com.esaulpaugh.headlong.abi.Address RECEIVER_HEADLONG_ADDRESS = asHeadlongAddress(asEvmAddress(RECEIVER_ID.accountNumOrThrow())); public static final com.esaulpaugh.headlong.abi.Address LAZY_CREATE_TARGET_1_HEADLONG_ADDRESS = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java index 4fa97dd02e8b..eda60faadfcd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java @@ -69,7 +69,8 @@ protected long cost(@NonNull final MessageFrame frame) { protected void onSuccess(@NonNull final MessageFrame frame, @NonNull final Address creation) { final var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); if (updater.isHollowAccount(creation)) { - updater.finalizeHollowAccount(creation); + // Pass along the address of the "parent" finalizing the hollow account as well + updater.finalizeHollowAccount(creation, frame.getRecipientAddress()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java index 4294fb96b829..8190a05e935c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java @@ -106,4 +106,11 @@ private void halt( private boolean alreadyCreated(final MutableAccount account) { return account.getNonce() > 0 || account.getCode().size() > 0; } + + @Override + protected void revert(final MessageFrame frame) { + super.revert(frame); + // Clear the childRecords from the record builder checkpoint in ProxyWorldUpdater, when revert() is called + ((HederaWorldUpdater) frame.getWorldUpdater()).revertChildRecords(); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java index 1f09325ff1ee..7087c2f8a14c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java @@ -24,6 +24,7 @@ import static com.hedera.node.app.service.mono.txns.crypto.AbstractAutoCreationLogic.THREE_MONTHS_IN_SECONDS; import static com.hedera.node.app.spi.key.KeyUtils.IMMUTABILITY_SENTINEL_KEY; import static com.hedera.node.app.spi.workflows.record.ExternalizedRecordCustomizer.SUPPRESSING_EXTERNALIZED_RECORD_CUSTOMIZER; +import static com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder.transactionWith; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.*; @@ -38,7 +39,6 @@ import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.records.ContractCreateRecordBuilder; -import com.hedera.node.app.service.contract.impl.records.ContractDeleteRecordBuilder; import com.hedera.node.app.service.contract.impl.state.ContractStateStore; import com.hedera.node.app.service.contract.impl.state.WritableContractStateStore; import com.hedera.node.app.service.token.ReadableAccountStore; @@ -86,8 +86,8 @@ public class HandleHederaOperations implements HederaOperations { private final ContractsConfig contractsConfig; private final HederaConfig hederaConfig; private final SystemContractGasCalculator gasCalculator; - private final HandleContext context; + private final HederaFunctionality functionality; @Inject public HandleHederaOperations( @@ -96,13 +96,15 @@ public HandleHederaOperations( @NonNull final HandleContext context, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator gasCalculator, - @NonNull final HederaConfig hederaConfig) { + @NonNull final HederaConfig hederaConfig, + @NonNull final HederaFunctionality functionality) { this.ledgerConfig = requireNonNull(ledgerConfig); this.contractsConfig = requireNonNull(contractsConfig); this.context = requireNonNull(context); this.tinybarValues = requireNonNull(tinybarValues); this.hederaConfig = requireNonNull(hederaConfig); this.gasCalculator = requireNonNull(gasCalculator); + this.functionality = requireNonNull(functionality); } /** @@ -247,7 +249,7 @@ public void refundFee(@NonNull final AccountID payerId, final long amount) { */ @Override public void chargeStorageRent(final long contractNumber, final long amount, final boolean itemizeStoragePayments) { - // TODO - implement before enabling contract expiry + // (FUTURE) Needed before enabling contract expiry } /** @@ -288,11 +290,13 @@ public void createContract(final long number, final long parentNumber, @Nullable public void createContract( final long number, @NonNull final ContractCreateTransactionBody body, @Nullable final Bytes evmAddress) { requireNonNull(body); + // Note that a EthereumTransaction with a top-level creation still needs to externalize its + // implied ContractCreateTransactionBody (unlike ContractCreate, which evidently already does so) dispatchAndMarkCreation( number, synthAccountCreationFromHapi( ContractID.newBuilder().contractNum(number).build(), evmAddress, body), - null, + functionality == HederaFunctionality.ETHEREUM_TRANSACTION ? body : null, body.autoRenewAccountId(), evmAddress); } @@ -304,11 +308,8 @@ public void createContract( public void deleteAliasedContract(@NonNull final Bytes evmAddress) { requireNonNull(evmAddress); final var tokenServiceApi = context.serviceApi(TokenServiceApi.class); - final ContractID contractId = - ContractID.newBuilder().evmAddress(evmAddress).build(); - - tokenServiceApi.deleteContract(contractId); - addContractDeleteChildRecord(contractId); + tokenServiceApi.deleteContract( + ContractID.newBuilder().evmAddress(evmAddress).build()); } /** @@ -317,11 +318,8 @@ public void deleteAliasedContract(@NonNull final Bytes evmAddress) { @Override public void deleteUnaliasedContract(final long number) { final var tokenServiceApi = context.serviceApi(TokenServiceApi.class); - final ContractID contractId = - ContractID.newBuilder().contractNum(number).build(); - - tokenServiceApi.deleteContract(contractId); - addContractDeleteChildRecord(contractId); + tokenServiceApi.deleteContract( + ContractID.newBuilder().contractNum(number).build()); } /** @@ -329,7 +327,6 @@ public void deleteUnaliasedContract(final long number) { */ @Override public List getModifiedAccountNumbers() { - // TODO - remove this method, isn't needed return Collections.emptyList(); } @@ -346,20 +343,17 @@ public long getOriginalSlotsUsed(final long contractNumber) { AccountID.newBuilder().accountNum(contractNumber).build()); } - private void addContractDeleteChildRecord(final ContractID contractId) { - final var childRecordBuilder = context.addChildRecordBuilder(ContractDeleteRecordBuilder.class); - childRecordBuilder.contractID(contractId).transaction(Transaction.DEFAULT); - } - @Override - public void externalizeHollowAccountMerge(@NonNull ContractID contractId, @Nullable Bytes evmAddress) { - var recordBuilder = context.addRemovableChildRecordBuilder(ContractCreateRecordBuilder.class); - recordBuilder + public void externalizeHollowAccountMerge( + @NonNull ContractID contractId, @NonNull ContractID parentId, @Nullable Bytes evmAddress) { + final var accountStore = context.readableStore(ReadableAccountStore.class); + final var parent = requireNonNull(accountStore.getContractById(parentId)); + context.addRemovableChildRecordBuilder(ContractCreateRecordBuilder.class) .contractID(contractId) - // add dummy transaction, because SingleTransactionRecord require NonNull on build - .transaction(Transaction.newBuilder() - .signedTransactionBytes(Bytes.EMPTY) - .build()) + .status(SUCCESS) + .transaction(transactionWith(TransactionBody.newBuilder() + .contractCreateInstance(synthContractCreationFromParent(contractId, parent)) + .build())) .contractCreateResult(ContractFunctionResult.newBuilder() .contractID(contractId) .evmAddress(evmAddress) @@ -392,50 +386,83 @@ private void dispatchAndMarkCreation( context.payer(), (bodyToExternalize == null) ? SUPPRESSING_EXTERNALIZED_RECORD_CUSTOMIZER - : contractBodyCustomizerFor(bodyToExternalize)); + : contractBodyCustomizerFor(number, bodyToExternalize)); + // TODO - deal with MAX_ENTITIES_IN_PRICE_REGIME_CREATED + if (recordBuilder.status() != OK && recordBuilder.status() != SUCCESS) { + throw new AssertionError("Not implemented"); + } + // On success we add extra information on the created contract final var contractId = ContractID.newBuilder().contractNum(number).build(); - // add additional create record fields recordBuilder .contractID(contractId) .contractCreateResult(ContractFunctionResult.newBuilder() .contractID(contractId) .evmAddress(evmAddress) .build()); - // TODO - switch OK to SUCCESS once some status-setting responsibilities are clarified - if (recordBuilder.status() != OK && recordBuilder.status() != SUCCESS) { - throw new AssertionError("Not implemented"); - } - // Then use the TokenService API to mark the created account as a contract + // Mark the created account as a contract with the given auto-renew account id final var tokenServiceApi = context.serviceApi(TokenServiceApi.class); final var accountId = AccountID.newBuilder().accountNum(number).build(); - tokenServiceApi.markAsContract(accountId, autoRenewAccountId); } - private ExternalizedRecordCustomizer contractBodyCustomizerFor(@NonNull final ContractCreateTransactionBody op) { + private ExternalizedRecordCustomizer contractBodyCustomizerFor( + final long createdNumber, @NonNull final ContractCreateTransactionBody op) { return transaction -> { try { - final var signedTransaction = SignedTransaction.PROTOBUF.parseStrict( + final var dispatchedTransaction = SignedTransaction.PROTOBUF.parseStrict( transaction.signedTransactionBytes().toReadableSequentialData()); - final var body = TransactionBody.PROTOBUF.parseStrict( - signedTransaction.bodyBytes().toReadableSequentialData()); - if (!body.hasCryptoCreateAccount()) { + final var dispatchedBody = TransactionBody.PROTOBUF.parseStrict( + dispatchedTransaction.bodyBytes().toReadableSequentialData()); + if (!dispatchedBody.hasCryptoCreateAccount()) { throw new IllegalArgumentException("Dispatched transaction body was not a crypto create"); } - final var finishedBody = - body.copyBuilder().contractCreateInstance(op).build(); - final var finishedSignedTransaction = signedTransaction + return transactionWith(dispatchedBody .copyBuilder() - .bodyBytes(TransactionBody.PROTOBUF.toBytes(finishedBody)) - .build(); - return transaction - .copyBuilder() - .signedTransactionBytes(SignedTransaction.PROTOBUF.toBytes(finishedSignedTransaction)) - .build(); - } catch (IOException internal) { - // This should never happen - throw new UncheckedIOException(internal); + .contractCreateInstance(standardized(createdNumber, op)) + .build()); + } catch (IOException e) { + // Should be impossible + throw new UncheckedIOException(e); } }; } + + private ContractCreateTransactionBody standardized( + final long createdNumber, @NonNull final ContractCreateTransactionBody op) { + var standardAdminKey = op.adminKey(); + if (op.hasAdminKey()) { + final var adminNum = + op.adminKeyOrThrow().contractIDOrElse(ContractID.DEFAULT).contractNumOrElse(0L); + // For mono-service fidelity, don't set an explicit admin key for a self-managed contract + if (createdNumber == adminNum) { + standardAdminKey = null; + } + } + if (needsStandardization(op, standardAdminKey)) { + // Initial balance, gas, and initcode are only set on top-level HAPI transactions + return new ContractCreateTransactionBody( + com.hedera.hapi.node.contract.codec.ContractCreateTransactionBodyProtoCodec.INITCODE_SOURCE_UNSET, + standardAdminKey, + 0L, + 0L, + op.proxyAccountID(), + op.autoRenewPeriod(), + op.constructorParameters(), + op.shardID(), + op.realmID(), + op.newRealmAdminKey(), + op.memo(), + op.maxAutomaticTokenAssociations(), + op.autoRenewAccountId(), + op.stakedId(), + op.declineReward()); + } else { + return op; + } + } + + private boolean needsStandardization( + @NonNull final ContractCreateTransactionBody op, @Nullable final Key standardAdminKey) { + return op.hasInitcode() || op.gas() > 0L || op.initialBalance() > 0L || standardAdminKey != op.adminKey(); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java index 2865a3e52309..f567606e6339 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleSystemContractOperations.java @@ -16,14 +16,18 @@ package com.hedera.node.app.service.contract.impl.exec.scope; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.CHILD; import static com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder.transactionWith; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Transaction; +import com.hedera.hapi.node.base.TransactionID; +import com.hedera.hapi.node.contract.ContractCallTransactionBody; import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.hapi.node.transaction.TransactionBody; @@ -33,6 +37,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import java.util.function.Predicate; import javax.inject.Inject; +import org.apache.tuweni.bytes.Bytes; /** * Provides the "extended" scope a Hedera system contract needs to perform its operations. @@ -101,6 +106,33 @@ public void externalizeResult( .contractCallResult(result); } + @Override + public void externalizeResult( + @NonNull final ContractFunctionResult result, + @NonNull final ResponseCodeEnum responseStatus, + @NonNull Transaction transaction) { + requireNonNull(transaction); + context.addChildRecordBuilder(ContractCallRecordBuilder.class) + .transaction(transaction) + .status(responseStatus) + .contractCallResult(result); + } + + @Override + public Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall) { + var functionParameters = tuweniToPbjBytes(input); + var contractCallBodyBuilder = + ContractCallTransactionBody.newBuilder().contractID(contractID).functionParameters(functionParameters); + if (isViewCall) { + contractCallBodyBuilder.gas(1L); + } + var transactionBody = TransactionBody.newBuilder() + .transactionID(TransactionID.DEFAULT) + .contractCall(contractCallBodyBuilder.build()) + .build(); + return transactionWith(transactionBody); + } + /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java index fa4c9b3702a7..dca22b10312a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HederaOperations.java @@ -249,7 +249,8 @@ public interface HederaOperations { * @param contractId ContractId of hollow account * @param evmAddress Evm address of hollow account */ - void externalizeHollowAccountMerge(@NonNull ContractID contractId, @Nullable Bytes evmAddress); + void externalizeHollowAccountMerge( + @NonNull ContractID contractId, @NonNull ContractID parentId, @Nullable Bytes evmAddress); /** * Given a {@link ContractID}, returns it if the shard and realm match for this node; otherwise, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java index a1cf10d4808b..c4464475c006 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QueryHederaOperations.java @@ -38,7 +38,7 @@ import org.hyperledger.besu.datatypes.Address; /** - * TODO - a read-only {@link HederaOperations} implementation based on a {@link QueryContext}. + * A read-only {@link HederaOperations} implementation based on a {@link QueryContext}. */ @QueryScope public class QueryHederaOperations implements HederaOperations { @@ -268,7 +268,8 @@ public RecordListCheckPoint createRecordListCheckPoint() { return null; } - public void externalizeHollowAccountMerge(@NonNull ContractID contractId, @Nullable Bytes evmAddress) { + public void externalizeHollowAccountMerge( + @NonNull ContractID contractId, @NonNull ContractID parentId, @Nullable Bytes evmAddress) { throw new UnsupportedOperationException("Queries cannot create accounts"); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java index 87d9b006fc93..c79a5cbdd22a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/QuerySystemContractOperations.java @@ -17,8 +17,10 @@ package com.hedera.node.app.service.contract.impl.exec.scope; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.hapi.node.transaction.TransactionBody; @@ -26,9 +28,11 @@ import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.spi.workflows.QueryContext; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; import java.util.function.Predicate; import javax.inject.Inject; +import org.apache.tuweni.bytes.Bytes; /** * Provides the "extended" scope a Hedera system contract needs to perform its operations. @@ -73,13 +77,27 @@ public ContractCallRecordBuilder externalizePreemptedDispatch( throw new UnsupportedOperationException("Cannot compute a signature test"); } + @Override + public void externalizeResult(@NonNull ContractFunctionResult result, @NonNull ResponseCodeEnum responseStatus) {} + /** * {@inheritDoc} */ @Override public void externalizeResult( - @NonNull final ContractFunctionResult result, @NonNull final ResponseCodeEnum responseStatus) { + @NonNull final ContractFunctionResult result, + @NonNull final ResponseCodeEnum responseStatus, + @Nullable Transaction transaction) { + // no-op + } + + /** + * {@inheritDoc} + */ + @Override + public Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall) { // no-op + return null; } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java index 8f523dbeb647..7e434fdd7fca 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/SystemContractOperations.java @@ -17,14 +17,17 @@ package com.hedera.node.app.service.contract.impl.exec.scope; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.contract.ContractFunctionResult; import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.function.Predicate; +import org.apache.tuweni.bytes.Bytes; public interface SystemContractOperations { /** @@ -77,6 +80,26 @@ ContractCallRecordBuilder externalizePreemptedDispatch( void externalizeResult( @NonNull final ContractFunctionResult result, @NonNull final ResponseCodeEnum responseStatus); + /** + * Attempts to create a child record of the current record, with the given {@code result} + * + * @param result contract function result + */ + void externalizeResult( + @NonNull ContractFunctionResult result, + @NonNull ResponseCodeEnum responseStatus, + @NonNull Transaction transaction); + + /** + * Generate synthetic transaction for child hts call + * + * @param input + * @param contractID + * @param isViewCall + * @return + */ + Transaction syntheticTransactionForHtsCall(Bytes input, ContractID contractID, boolean isViewCall); + /** * Returns the {@Link ExchangeRate} for the current consensus time. This will enable the translation from hbars * to dollars diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java index 89e450a5a50d..4556747814cf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/HtsSystemContract.java @@ -47,12 +47,12 @@ @Singleton public class HtsSystemContract extends AbstractFullContract implements HederaSystemContract { - private static final Logger log = LogManager.getLogger(HtsSystemContract.class); - private static final Bytes STATIC_CALL_REVERT_REASON = Bytes.of("HTS precompiles are not static".getBytes()); - private static final String HTS_SYSTEM_CONTRACT_NAME = "HTS"; + + public static final String HTS_SYSTEM_CONTRACT_NAME = "HTS"; public static final String HTS_EVM_ADDRESS = "0x167"; - private static final ContractID HTS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HTS_EVM_ADDRESS)); + public static final ContractID HTS_CONTRACT_ID = asNumberedContractId(Address.fromHexString(HTS_EVM_ADDRESS)); + private final HtsCallFactory callFactory; @Inject @@ -78,11 +78,10 @@ public FullResult computeFully(@NonNull final Bytes input, @NonNull final Messag // without setting a halt reason to simulate mono-service for differential testing return haltResult(contractsConfigOf(frame).precompileHtsDefaultGasCost()); } - } catch (final RuntimeException e) { + } catch (final Exception e) { log.debug("Failed to create HTS call from input {}", input, e); return haltResult(ExceptionalHaltReason.INVALID_OPERATION, frame.getRemainingGas()); } - return resultOfExecuting(attempt, call, input, frame); } @@ -119,7 +118,10 @@ private static FullResult resultOfExecuting( frame.getRemainingGas(), frame.getInputData(), attempt.senderId()), - responseCode); + responseCode, + enhancement + .systemOperations() + .syntheticTransactionForHtsCall(input, HTS_CONTRACT_ID, true)); } else { enhancement .systemOperations() @@ -128,7 +130,10 @@ private static FullResult resultOfExecuting( pricedResult.fullResult().gasRequirement(), responseCode.toString(), HTS_CONTRACT_ID), - responseCode); + responseCode, + enhancement + .systemOperations() + .syntheticTransactionForHtsCall(input, HTS_CONTRACT_ID, true)); } } } catch (final HandleException handleException) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java index 277dba88d9d9..6e5da0ceeec9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/DispatchForResponseCodeHtsCall.java @@ -16,10 +16,15 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall.OutputFn.STANDARD_OUTPUT_FN; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.encodedRc; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ResponseCodeEnum; @@ -29,19 +34,18 @@ import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; import java.util.Objects; import java.util.function.Function; +import org.hyperledger.besu.evm.frame.MessageFrame; /** * An HTS call that simply dispatches a synthetic transaction body and returns a result that is * an encoded {@link com.hedera.hapi.node.base.ResponseCodeEnum}. - * - * @param the type of the record builder to expect from the dispatch */ -public class DispatchForResponseCodeHtsCall extends AbstractHtsCall { +public class DispatchForResponseCodeHtsCall extends AbstractHtsCall { /** * The "standard" failure customizer that replaces {@link ResponseCodeEnum#INVALID_SIGNATURE} with * {@link ResponseCodeEnum#INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE}. (Note this code no longer @@ -52,8 +56,10 @@ public class DispatchForResponseCodeHtsCall standardized(code); private final AccountID senderId; + + @Nullable private final TransactionBody syntheticBody; - private final Class recordBuilderType; + private final OutputFn outputFn; private final FailureCustomizer failureCustomizer; private final VerificationStrategy verificationStrategy; @@ -100,20 +106,17 @@ public interface OutputFn extends Function recordBuilderType, + @Nullable final TransactionBody syntheticBody, @NonNull final DispatchGasCalculator dispatchGasCalculator) { this( attempt.enhancement(), attempt.systemContractGasCalculator(), attempt.addressIdConverter().convertSender(attempt.senderAddress()), syntheticBody, - recordBuilderType, attempt.defaultVerificationStrategy(), dispatchGasCalculator, STANDARD_FAILURE_CUSTOMIZER, @@ -125,13 +128,11 @@ public DispatchForResponseCodeHtsCall( * * @param attempt the attempt to translate to a dispatching * @param syntheticBody the synthetic body to dispatch - * @param recordBuilderType the type of the record builder to expect from the dispatch * @param dispatchGasCalculator the dispatch gas calculator to use */ public DispatchForResponseCodeHtsCall( @NonNull final HtsCallAttempt attempt, - @NonNull final TransactionBody syntheticBody, - @NonNull final Class recordBuilderType, + @Nullable final TransactionBody syntheticBody, @NonNull final DispatchGasCalculator dispatchGasCalculator, @NonNull final FailureCustomizer failureCustomizer) { this( @@ -139,7 +140,6 @@ public DispatchForResponseCodeHtsCall( attempt.systemContractGasCalculator(), attempt.addressIdConverter().convertSender(attempt.senderAddress()), syntheticBody, - recordBuilderType, attempt.defaultVerificationStrategy(), dispatchGasCalculator, failureCustomizer, @@ -151,13 +151,11 @@ public DispatchForResponseCodeHtsCall( * * @param attempt the attempt to translate to a dispatching * @param syntheticBody the synthetic body to dispatch - * @param recordBuilderType the type of the record builder to expect from the dispatch * @param dispatchGasCalculator the dispatch gas calculator to use */ public DispatchForResponseCodeHtsCall( @NonNull final HtsCallAttempt attempt, - @NonNull final TransactionBody syntheticBody, - @NonNull final Class recordBuilderType, + @Nullable final TransactionBody syntheticBody, @NonNull final DispatchGasCalculator dispatchGasCalculator, @NonNull final OutputFn outputFn) { this( @@ -165,7 +163,6 @@ public DispatchForResponseCodeHtsCall( attempt.systemContractGasCalculator(), attempt.addressIdConverter().convertSender(attempt.senderAddress()), syntheticBody, - recordBuilderType, attempt.defaultVerificationStrategy(), dispatchGasCalculator, STANDARD_FAILURE_CUSTOMIZER, @@ -178,7 +175,6 @@ public DispatchForResponseCodeHtsCall( * @param enhancement the enhancement to use * @param senderId the id of the spender * @param syntheticBody the synthetic body to dispatch - * @param recordBuilderType the type of the record builder to expect from the dispatch * @param verificationStrategy the verification strategy to use * @param dispatchGasCalculator the dispatch gas calculator to use * @param failureCustomizer the status customizer to use @@ -186,12 +182,11 @@ public DispatchForResponseCodeHtsCall( */ // too many parameters @SuppressWarnings("java:S107") - public DispatchForResponseCodeHtsCall( + public DispatchForResponseCodeHtsCall( @NonNull final HederaWorldUpdater.Enhancement enhancement, @NonNull final SystemContractGasCalculator gasCalculator, @NonNull final AccountID senderId, - @NonNull final TransactionBody syntheticBody, - @NonNull final Class recordBuilderType, + @Nullable final TransactionBody syntheticBody, @NonNull final VerificationStrategy verificationStrategy, @NonNull final DispatchGasCalculator dispatchGasCalculator, @NonNull final FailureCustomizer failureCustomizer, @@ -199,15 +194,22 @@ public DispatchForResponseCodeHtsCall super(gasCalculator, enhancement, false); this.senderId = Objects.requireNonNull(senderId); this.outputFn = Objects.requireNonNull(outputFn); - this.syntheticBody = Objects.requireNonNull(syntheticBody); - this.recordBuilderType = Objects.requireNonNull(recordBuilderType); + this.syntheticBody = syntheticBody; this.verificationStrategy = Objects.requireNonNull(verificationStrategy); this.dispatchGasCalculator = Objects.requireNonNull(dispatchGasCalculator); this.failureCustomizer = Objects.requireNonNull(failureCustomizer); } @Override - public @NonNull PricedResult execute() { + public @NonNull PricedResult execute(@NonNull final MessageFrame frame) { + if (syntheticBody == null) { + return gasOnly( + haltResult( + ERROR_DECODING_PRECOMPILE_INPUT, + contractsConfigOf(frame).precompileHtsDefaultGasCost()), + INVALID_TRANSACTION_BODY, + false); + } final var recordBuilder = systemContractOperations() .dispatch(syntheticBody, verificationStrategy, senderId, ContractCallRecordBuilder.class); final var gasRequirement = diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java index fca993d85f93..5b50fe905eef 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/associations/AssociationsTranslator.java @@ -27,7 +27,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -69,10 +68,9 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { */ @Override public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, matchesHrcSelector(attempt.selector()) ? bodyForHrc(attempt) : bodyForClassic(attempt), - SingleTransactionRecordBuilder.class, AssociationsTranslator::gasRequirement); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java index 1286b06c664f..5c15391c8ee9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/burn/BurnTranslator.java @@ -29,7 +29,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -55,10 +54,9 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { final var body = bodyForClassic(attempt); final var isFungibleMint = body.tokenBurnOrThrow().serialNumbers().isEmpty(); - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, body, - SingleTransactionRecordBuilder.class, isFungibleMint ? BurnTranslator::fungibleBurnGasRequirement : BurnTranslator::nftBurnGasRequirement, BURN_OUTPUT_FN); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index 99e7873c04f5..ca34798587db 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -19,7 +19,11 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_EXPIRATION_TIME; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.MISSING_TOKEN_SYMBOL; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.haltResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.revertResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult.successResult; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.HtsSystemContract.HTS_EVM_ADDRESS; @@ -28,6 +32,7 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ADDRESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.standardized; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.contractsConfigOf; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.stackIncludesActiveAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmContractId; @@ -40,7 +45,6 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.TokenType; -import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy; @@ -55,7 +59,9 @@ import com.hedera.node.config.data.ContractsConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.ByteBuffer; +import java.util.Collections; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -66,7 +72,7 @@ public class ClassicCreatesCall extends AbstractHtsCall { */ private static final long FIXED_GAS_COST = 100_000L; - @NonNull + @Nullable final TransactionBody syntheticCreate; private final VerificationStrategy verificationStrategy; @@ -76,23 +82,35 @@ public class ClassicCreatesCall extends AbstractHtsCall { public ClassicCreatesCall( @NonNull final SystemContractGasCalculator systemContractGasCalculator, @NonNull final HederaWorldUpdater.Enhancement enhancement, - @NonNull final TransactionBody syntheticCreate, + @Nullable final TransactionBody syntheticCreate, @NonNull final VerificationStrategy verificationStrategy, @NonNull final Address spender, @NonNull final AddressIdConverter addressIdConverter) { super(systemContractGasCalculator, enhancement, false); - this.syntheticCreate = requireNonNull(syntheticCreate); this.verificationStrategy = requireNonNull(verificationStrategy); this.spenderId = addressIdConverter.convert(asHeadlongAddress(spender.toArrayUnsafe())); - final var baseCost = gasCalculator.canonicalPriceInTinybars(syntheticCreate, spenderId); - // The non-gas cost is a 20% surcharge on the HAPI TokenCreate price, minus the fee taken as gas - this.nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); + this.syntheticCreate = syntheticCreate; + if (syntheticCreate != null) { + final var baseCost = gasCalculator.canonicalPriceInTinybars(syntheticCreate, spenderId); + // The non-gas cost is a 20% surcharge on the HAPI TokenCreate price, minus the fee taken as gas + this.nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); + } else { + this.nonGasCost = 0L; + } } private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besuAddress) {} @Override public @NonNull PricedResult execute(@NonNull final MessageFrame frame) { + if (syntheticCreate == null) { + return gasOnly( + haltResult( + ERROR_DECODING_PRECOMPILE_INPUT, + contractsConfigOf(frame).precompileHtsDefaultGasCost()), + INVALID_TRANSACTION_BODY, + false); + } if (frame.getValue().lessThan(Wei.of(nonGasCost))) { return completionWith( FIXED_GAS_COST, @@ -102,18 +120,9 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu operations().collectFee(spenderId, nonGasCost); } - final var token = ((TokenCreateTransactionBody) syntheticCreate.data().value()); - if (token.symbol().isEmpty()) { - return externalizeUnsuccessfulResult(MISSING_TOKEN_SYMBOL, gasCalculator.viewGasRequirement()); - } - - final var treasuryAccount = - nativeOperations().getAccount(token.treasuryOrThrow().accountNumOrThrow()); - if (treasuryAccount == null) { - return externalizeUnsuccessfulResult(INVALID_ACCOUNT_ID, gasCalculator.viewGasRequirement()); - } - if (token.autoRenewAccount() == null) { - return externalizeUnsuccessfulResult(INVALID_EXPIRATION_TIME, gasCalculator.viewGasRequirement()); + final var validity = validityOfSynthOp(); + if (validity != OK) { + return externalizeUnsuccessfulResult(validity, gasCalculator.viewGasRequirement()); } // Choose a dispatch verification strategy based on whether the legacy activation address is active @@ -122,46 +131,62 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu .dispatch(syntheticCreate, dispatchVerificationStrategy, spenderId, ContractCallRecordBuilder.class); recordBuilder.status(standardized(recordBuilder.status())); - final var customFees = - ((TokenCreateTransactionBody) syntheticCreate.data().value()).customFees(); - final var tokenType = - ((TokenCreateTransactionBody) syntheticCreate.data().value()).tokenType(); final var status = recordBuilder.status(); if (status != ResponseCodeEnum.SUCCESS) { return gasOnly(revertResult(recordBuilder, FIXED_GAS_COST), status, false); } else { - final var isFungible = tokenType == TokenType.FUNGIBLE_COMMON; ByteBuffer encodedOutput; - - if (isFungible && customFees.isEmpty()) { - encodedOutput = CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 - .getOutputs() - .encodeElements( - (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), - headlongAddressOf(recordBuilder.tokenID())); - } else if (isFungible && !customFees.isEmpty()) { - encodedOutput = CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V1 - .getOutputs() - .encodeElements( - (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), - headlongAddressOf(recordBuilder.tokenID())); - } else if (customFees.isEmpty()) { - encodedOutput = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 - .getOutputs() - .encodeElements( - (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), - headlongAddressOf(recordBuilder.tokenID())); + final var op = syntheticCreate.tokenCreationOrThrow(); + final var customFees = op.customFeesOrElse(Collections.emptyList()); + if (op.tokenType() == TokenType.FUNGIBLE_COMMON) { + if (customFees.isEmpty()) { + encodedOutput = CreateTranslator.CREATE_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); + } else { + encodedOutput = CreateTranslator.CREATE_FUNGIBLE_WITH_CUSTOM_FEES_V1 + .getOutputs() + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); + } } else { - encodedOutput = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 - .getOutputs() - .encodeElements( - (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), - headlongAddressOf(recordBuilder.tokenID())); + if (customFees.isEmpty()) { + encodedOutput = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1 + .getOutputs() + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); + } else { + encodedOutput = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_WITH_CUSTOM_FEES_V1 + .getOutputs() + .encodeElements( + (long) ResponseCodeEnum.SUCCESS.protoOrdinal(), + headlongAddressOf(recordBuilder.tokenID())); + } } return gasOnly(successResult(encodedOutput, FIXED_GAS_COST, recordBuilder), status, false); } } + private ResponseCodeEnum validityOfSynthOp() { + final var op = syntheticCreate.tokenCreationOrThrow(); + if (op.symbol().isEmpty()) { + return MISSING_TOKEN_SYMBOL; + } + final var treasuryAccount = + nativeOperations().getAccount(op.treasuryOrThrow().accountNumOrThrow()); + if (treasuryAccount == null) { + return INVALID_ACCOUNT_ID; + } + if (op.autoRenewAccount() == null) { + return INVALID_EXPIRATION_TIME; + } + return OK; + } + private VerificationStrategy verificationStrategyFor(@NonNull final MessageFrame frame) { final var legacyActivation = legacyActivationIn(frame); @@ -185,7 +210,6 @@ private LegacyActivation legacyActivationIn(@NonNull final MessageFrame frame) { return new LegacyActivation(contractNum, pbjAddress, pbjToBesuAddress(pbjAddress)); } - // @TODO extract externalizeResult() calls into a single location on a higher level private PricedResult externalizeUnsuccessfulResult(ResponseCodeEnum responseCode, long gasRequirement) { final var result = gasOnly(FullResult.revertResult(responseCode, gasRequirement), responseCode, false); final var contractID = asEvmContractId(Address.fromHexString(HTS_EVM_ADDRESS)); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateDecoder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateDecoder.java index 5ced84938070..838d3e58a0e2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateDecoder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateDecoder.java @@ -35,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.utils.TokenKeyWrapper; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -64,7 +65,7 @@ public CreateDecoder() { * @param encoded the encoded call * @return the synthetic transaction body */ - public TransactionBody decodeCreateFungibleTokenV1( + public @Nullable TransactionBody decodeCreateFungibleTokenV1( @NonNull final byte[] encoded, @NonNull final AccountID senderId, @NonNull final HederaNativeOperations nativeOperations, @@ -73,9 +74,9 @@ public TransactionBody decodeCreateFungibleTokenV1( final var hederaToken = (Tuple) call.get(HEDERA_TOKEN); final var initSupply = ((BigInteger) call.get(INIT_SUPPLY)).longValue(); final var decimals = ((BigInteger) call.get(DECIMALS)).intValue(); - final TokenCreateWrapper tokenCreateWrapper = getTokenCreateWrapper( + final var tokenCreateWrapper = getTokenCreateWrapper( hederaToken, true, initSupply, decimals, senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -95,7 +96,7 @@ public TransactionBody decodeCreateFungibleTokenV2( final var decimals = ((Long) call.get(DECIMALS)).intValue(); final TokenCreateWrapper tokenCreateWrapper = getTokenCreateWrapper( hederaToken, true, initSupply, decimals, senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -118,7 +119,7 @@ public TransactionBody decodeCreateFungibleTokenV3( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -147,7 +148,7 @@ public TransactionBody decodeCreateFungibleTokenWithCustomFeesV1( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -176,7 +177,7 @@ public TransactionBody decodeCreateFungibleTokenWithCustomFeesV2( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -200,7 +201,7 @@ public TransactionBody decodeCreateFungibleTokenWithCustomFeesV3( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -217,7 +218,7 @@ public TransactionBody decodeCreateNonFungibleV1( final var call = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V1.decodeCall(encoded); final TokenCreateWrapper tokenCreateWrapper = getTokenCreateWrapperNonFungible( call.get(HEDERA_TOKEN), senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -234,7 +235,7 @@ public TransactionBody decodeCreateNonFungibleV2( final var call = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V2.decodeCall(encoded); final TokenCreateWrapper tokenCreateWrapper = getTokenCreateWrapperNonFungible( call.get(HEDERA_TOKEN), senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -251,7 +252,7 @@ public TransactionBody decodeCreateNonFungibleV3( final var call = CreateTranslator.CREATE_NON_FUNGIBLE_TOKEN_V3.decodeCall(encoded); final TokenCreateWrapper tokenCreateWrapper = getTokenCreateWrapperNonFungible( call.get(HEDERA_TOKEN), senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -273,7 +274,7 @@ public TransactionBody decodeCreateNonFungibleWithCustomFeesV1( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -295,7 +296,7 @@ public TransactionBody decodeCreateNonFungibleWithCustomFeesV2( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } /** @@ -317,7 +318,7 @@ public TransactionBody decodeCreateNonFungibleWithCustomFeesV3( senderId, nativeOperations, addressIdConverter); - return bodyOf(createToken(tokenCreateWrapper)); + return bodyFor(tokenCreateWrapper); } private TransactionBody bodyOf(@NonNull final TokenCreateTransactionBody.Builder tokenCreate) { @@ -564,4 +565,12 @@ public static List decodeRoyaltyFees( } return decodedRoyaltyFees; } + + private @Nullable TransactionBody bodyFor(@NonNull final TokenCreateWrapper tokenCreateWrapper) { + try { + return bodyOf(createToken(tokenCreateWrapper)); + } catch (IllegalArgumentException ignore) { + return null; + } + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateSyntheticTxnFactory.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateSyntheticTxnFactory.java index fd6eb3243b37..47c64a95648b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateSyntheticTxnFactory.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateSyntheticTxnFactory.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.create; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenSupplyType; import com.hedera.hapi.node.base.TokenType; @@ -72,6 +73,9 @@ private static void setTokenKeys( @NonNull final TokenCreateWrapper tokenCreateWrapper, final Builder txnBodyBuilder) { tokenCreateWrapper.getTokenKeys().forEach(tokenKeyWrapper -> { final var key = tokenKeyWrapper.key().asGrpc(); + if (key == Key.DEFAULT) { + throw new IllegalArgumentException(); + } if (tokenKeyWrapper.isUsedForAdminKey()) { txnBodyBuilder.adminKey(key); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java index 040f7892bdc8..bd4d066de18f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/CreateTranslator.java @@ -32,6 +32,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.AbstractHtsCallTranslator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Arrays; import javax.inject.Inject; @@ -156,7 +157,7 @@ public ClassicCreatesCall callFrom(@NonNull HtsCallAttempt attempt) { attempt.addressIdConverter()); } - private TransactionBody nominalBodyFor(@NonNull final HtsCallAttempt attempt) { + private @Nullable TransactionBody nominalBodyFor(@NonNull final HtsCallAttempt attempt) { final var inputBytes = attempt.inputBytes(); final var senderId = attempt.senderId(); final var nativeOperations = attempt.nativeOperations(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java index 9da81a2753d5..0787b2a52daa 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/delete/DeleteTranslator.java @@ -29,7 +29,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -49,11 +48,7 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { @Override public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( - attempt, - bodyForClassic(attempt), - SingleTransactionRecordBuilder.class, - DeleteTranslator::gasRequirement); + return new DispatchForResponseCodeHtsCall(attempt, bodyForClassic(attempt), DeleteTranslator::gasRequirement); } public static long gasRequirement( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java index 3609e478c5b4..957a34f5d510 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/freeze/FreezeUnfreezeTranslator.java @@ -29,7 +29,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -62,10 +61,9 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { */ @Override public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, bodyForClassic(attempt), - SingleTransactionRecordBuilder.class, Arrays.equals(attempt.selector(), FREEZE.selector()) ? FreezeUnfreezeTranslator::freezeGasRequirement : FreezeUnfreezeTranslator::unfreezeGasRequirement, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java index 5bacccb420a7..12de66eb7413 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantapproval/GrantApprovalTranslator.java @@ -30,7 +30,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.Arrays; @@ -74,11 +73,8 @@ public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { } else if (matchesClassicSelector(attempt.selector())) { return bodyForClassicCall(attempt); } else { - return new DispatchForResponseCodeHtsCall<>( - attempt, - bodyForClassic(attempt), - SingleTransactionRecordBuilder.class, - GrantApprovalTranslator::gasRequirement); + return new DispatchForResponseCodeHtsCall( + attempt, bodyForClassic(attempt), GrantApprovalTranslator::gasRequirement); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java index ba5d8a50192e..368181aaa296 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/grantrevokekyc/GrantRevokeKycTranslator.java @@ -25,7 +25,6 @@ import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.*; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -60,10 +59,9 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { */ @Override public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, bodyForClassic(attempt), - SingleTransactionRecordBuilder.class, Arrays.equals(attempt.selector(), GRANT_KYC.selector()) ? GrantRevokeKycTranslator::grantGasRequirement : GrantRevokeKycTranslator::revokeGasRequirement, diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java index 6e05b784bd5e..c45b7f8bfb52 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/mint/MintTranslator.java @@ -28,7 +28,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -61,10 +60,9 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { final var body = bodyForClassic(attempt); final var isFungibleMint = body.tokenMintOrThrow().metadata().isEmpty(); - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, body, - SingleTransactionRecordBuilder.class, isFungibleMint ? MintTranslator::fungibleMintGasRequirement : MintTranslator::nftMintGasRequirement, MINT_OUTPUT_FN); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java index eb6822d38987..f1cb7a0ff494 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/pauses/PausesTranslator.java @@ -27,7 +27,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -62,10 +61,9 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { */ @Override public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, bodyForClassic(attempt), - SingleTransactionRecordBuilder.class, Arrays.equals(attempt.selector(), PAUSE.selector()) ? PausesTranslator::pauseGasRequirement : PausesTranslator::unpauseGasRequirement); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateDecoder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateDecoder.java index 267a979b601e..74f3bdfe2c2e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateDecoder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateDecoder.java @@ -26,6 +26,7 @@ import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.hapi.node.base.Duration; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.token.Token; @@ -101,7 +102,7 @@ private static boolean isKnownImmutable(@Nullable final Token token) { * @param attempt the attempt * @return the synthetic transaction body */ - public TransactionBody decodeTokenUpdateV1(@NonNull final HtsCallAttempt attempt) { + public @Nullable TransactionBody decodeTokenUpdateV1(@NonNull final HtsCallAttempt attempt) { final var call = UpdateTranslator.TOKEN_UPDATE_INFO_FUNCTION_V1.decodeCall( attempt.input().toArrayUnsafe()); return decodeTokenUpdate(call, attempt.addressIdConverter()); @@ -113,7 +114,7 @@ public TransactionBody decodeTokenUpdateV1(@NonNull final HtsCallAttempt attempt * @param attempt the attempt * @return the synthetic transaction body */ - public TransactionBody decodeTokenUpdateV2(@NonNull final HtsCallAttempt attempt) { + public @Nullable TransactionBody decodeTokenUpdateV2(@NonNull final HtsCallAttempt attempt) { final var call = UpdateTranslator.TOKEN_UPDATE_INFO_FUNCTION_V2.decodeCall( attempt.input().toArrayUnsafe()); return decodeTokenUpdate(call, attempt.addressIdConverter()); @@ -125,7 +126,7 @@ public TransactionBody decodeTokenUpdateV2(@NonNull final HtsCallAttempt attempt * @param attempt the attempt * @return the synthetic transaction body */ - public TransactionBody decodeTokenUpdateV3(@NonNull final HtsCallAttempt attempt) { + public @Nullable TransactionBody decodeTokenUpdateV3(@NonNull final HtsCallAttempt attempt) { final var call = UpdateTranslator.TOKEN_UPDATE_INFO_FUNCTION_V3.decodeCall( attempt.input().toArrayUnsafe()); return decodeTokenUpdate(call, attempt.addressIdConverter()); @@ -155,7 +156,7 @@ public TransactionBody decodeTokenUpdateExpiryV2(@NonNull final HtsCallAttempt a return decodeTokenUpdateExpiry(call, attempt.addressIdConverter()); } - private TransactionBody decodeTokenUpdate( + private @Nullable TransactionBody decodeTokenUpdate( @NonNull final Tuple call, @NonNull final AddressIdConverter addressIdConverter) { final var tokenId = ConversionUtils.asTokenId(call.get(TOKEN_ADDRESS)); final var hederaToken = (Tuple) call.get(HEDERA_TOKEN); @@ -195,9 +196,14 @@ private TransactionBody decodeTokenUpdate( txnBodyBuilder.autoRenewPeriod(tokenExpiry.autoRenewPeriod()); } - return checkTokenKeysTypeAndBuild(tokenKeys, txnBodyBuilder); + try { + return bodyWith(tokenKeys, txnBodyBuilder); + } catch (IllegalArgumentException ignore) { + return null; + } } + @Nullable public TransactionBody decodeTokenUpdateKeys(@NonNull final HtsCallAttempt attempt) { final var call = UpdateKeysTranslator.TOKEN_UPDATE_KEYS_FUNCTION.decodeCall( attempt.input().toArrayUnsafe()); @@ -209,13 +215,20 @@ public TransactionBody decodeTokenUpdateKeys(@NonNull final HtsCallAttempt attem final var txnBodyBuilder = TokenUpdateTransactionBody.newBuilder(); txnBodyBuilder.token(tokenId); - return checkTokenKeysTypeAndBuild(tokenKeys, txnBodyBuilder); + try { + return bodyWith(tokenKeys, txnBodyBuilder); + } catch (IllegalArgumentException ignore) { + return null; + } } - private TransactionBody checkTokenKeysTypeAndBuild( + private TransactionBody bodyWith( final List tokenKeys, final TokenUpdateTransactionBody.Builder builder) { tokenKeys.forEach(tokenKeyWrapper -> { final var key = tokenKeyWrapper.key().asGrpc(); + if (key == Key.DEFAULT) { + throw new IllegalArgumentException(); + } if (tokenKeyWrapper.isUsedForAdminKey()) { builder.adminKey(key); } @@ -238,7 +251,6 @@ private TransactionBody checkTokenKeysTypeAndBuild( builder.pauseKey(key); } }); - return TransactionBody.newBuilder().tokenUpdate(builder).build(); } @@ -273,9 +285,9 @@ private static List decodeTokenKeys( final var keyType = ((BigInteger) tokenKeyTuple.get(KEY_TYPE)).intValue(); final Tuple keyValueTuple = tokenKeyTuple.get(KEY_VALUE); final var inheritAccountKey = (Boolean) keyValueTuple.get(INHERIT_ACCOUNT_KEY); + final byte[] ed25519 = keyValueTuple.get(ED25519); + final byte[] ecdsaSecp256K1 = keyValueTuple.get(ECDSA_SECP_256K1); final var contractId = asNumericContractId(addressIdConverter.convert(keyValueTuple.get(CONTRACT_ID))); - final var ed25519 = (byte[]) keyValueTuple.get(ED25519); - final var ecdsaSecp256K1 = (byte[]) keyValueTuple.get(ECDSA_SECP_256K1); final var delegatableContractId = asNumericContractId(addressIdConverter.convert(keyValueTuple.get(DELEGATABLE_CONTRACT_ID))); @@ -283,10 +295,10 @@ private static List decodeTokenKeys( keyType, new KeyValueWrapper( inheritAccountKey, - contractId.contractNum() != 0 ? contractId : null, + contractId.contractNumOrThrow() != 0 ? contractId : null, ed25519, ecdsaSecp256K1, - delegatableContractId.contractNum() != 0 ? delegatableContractId : null))); + delegatableContractId.contractNumOrThrow() != 0 ? delegatableContractId : null))); } return tokenKeys; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java index 6f359a009062..a2a6b064a3bf 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateExpiryTranslator.java @@ -31,7 +31,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -57,12 +56,8 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { @Override public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( - attempt, - nominalBodyFor(attempt), - SingleTransactionRecordBuilder.class, - UpdateTranslator::gasRequirement, - FAILURE_CUSTOMIZER); + return new DispatchForResponseCodeHtsCall( + attempt, nominalBodyFor(attempt), UpdateTranslator::gasRequirement, FAILURE_CUSTOMIZER); } public static long gasRequirement( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java index 966cb9d07f76..9a3e1b5f4475 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateKeysTranslator.java @@ -31,7 +31,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -54,10 +53,9 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { @Override public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, decoder.decodeTokenUpdateKeys(attempt), - SingleTransactionRecordBuilder.class, UpdateKeysTranslator::gasRequirement, FAILURE_CUSTOMIZER); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java index e20629cd5366..93fba8135be2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/update/UpdateTranslator.java @@ -32,7 +32,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -69,12 +68,8 @@ public boolean matches(@NonNull HtsCallAttempt attempt) { @Override public HtsCall callFrom(@NonNull HtsCallAttempt attempt) { - return new DispatchForResponseCodeHtsCall<>( - attempt, - nominalBodyFor(attempt), - SingleTransactionRecordBuilder.class, - UpdateTranslator::gasRequirement, - UpdateDecoder.FAILURE_CUSTOMIZER); + return new DispatchForResponseCodeHtsCall( + attempt, nominalBodyFor(attempt), UpdateTranslator::gasRequirement, UpdateDecoder.FAILURE_CUSTOMIZER); } public static long gasRequirement( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java index 40dcb674ae3d..9a433a347b10 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/wipe/WipeTranslator.java @@ -29,7 +29,6 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; import javax.inject.Inject; @@ -69,10 +68,9 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { public HtsCall callFrom(@NonNull final HtsCallAttempt attempt) { final var body = bodyForClassic(attempt); final var isFungibleWipe = body.tokenWipeOrThrow().serialNumbers().isEmpty(); - return new DispatchForResponseCodeHtsCall<>( + return new DispatchForResponseCodeHtsCall( attempt, body, - SingleTransactionRecordBuilder.class, isFungibleWipe ? WipeTranslator::fungibleWipeGasRequirement : WipeTranslator::nftWipeGasRequirement, NOOP_CUSTOMIZER); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/KeyValueWrapper.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/KeyValueWrapper.java index d68773e6c512..0a6dd3e689ef 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/KeyValueWrapper.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/KeyValueWrapper.java @@ -16,21 +16,23 @@ package com.hedera.node.app.service.contract.impl.exec.utils; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.Key; -import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.hederahashgraph.api.proto.java.ResponseCodeEnum; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; public final class KeyValueWrapper { - public enum KeyValueType { + public enum KeyType { INVALID_KEY, INHERIT_ACCOUNT_KEY, CONTRACT_ID, DELEGATABLE_CONTRACT_ID, ED25519, - ECDSA_SECPK256K1 + ECDSA_SECP256K1 } /* --- Only 1 of these values should be set when the input is valid. --- */ @@ -39,7 +41,7 @@ public enum KeyValueType { private final byte[] ed25519; private final byte[] ecdsaSecp256k1; private final ContractID delegatableContractID; - private final KeyValueWrapper.KeyValueType keyValueType; + private final KeyType keyType; private static final int ED25519_BYTE_LENGTH = 32; private static final int ECDSA_SECP256K1_COMPRESSED_KEY_LENGTH = 33; @@ -49,18 +51,16 @@ public enum KeyValueType { public KeyValueWrapper( final boolean shouldInheritAccountKey, - final ContractID contractID, - final byte[] ed25519, - final byte[] ecdsaSecp256k1, - final ContractID delegatableContractID) { - var isKeyValid = - keyValidityCheck(shouldInheritAccountKey, contractID, ed25519, ecdsaSecp256k1, delegatableContractID); + @Nullable final ContractID contractID, + @NonNull final byte[] ed25519, + @NonNull final byte[] ecdsaSecp256k1, + @Nullable final ContractID delegatableContractID) { this.shouldInheritAccountKey = shouldInheritAccountKey; this.contractID = contractID; - this.ed25519 = ed25519; - this.ecdsaSecp256k1 = ecdsaSecp256k1; + this.ed25519 = requireNonNull(ed25519); + this.ecdsaSecp256k1 = requireNonNull(ecdsaSecp256k1); this.delegatableContractID = delegatableContractID; - this.keyValueType = isKeyValid ? this.setKeyValueType() : KeyValueType.INVALID_KEY; + this.keyType = isValidConstruction() ? this.computeKeyType() : KeyType.INVALID_KEY; } private boolean isContractIDSet() { @@ -87,60 +87,42 @@ public void setInheritedKey(final Key key) { this.inheritedKey = key; } - private boolean keyValidityCheck( - boolean shouldInheritAccountKey, - ContractID contractID, - byte[] ed25519, - byte[] ecdsaSecp256k1, - ContractID delegatableContractID) { + private boolean isValidConstruction() { var keyCount = 0; - if (contractID != null) keyCount++; - if (delegatableContractID != null) keyCount++; - if (shouldInheritAccountKey) keyCount++; - if (ed25519.length == ED25519_BYTE_LENGTH) keyCount++; - if (ecdsaSecp256k1.length == ECDSA_SECP256K1_COMPRESSED_KEY_LENGTH) keyCount++; + if (isContractIDSet()) keyCount++; + if (isDelegatableContractIdSet()) keyCount++; + if (isShouldInheritAccountKeySet()) keyCount++; + if (isEd25519KeySet()) keyCount++; + if (isEcdsaSecp256k1KeySet()) keyCount++; return keyCount == 1; } - private KeyValueType setKeyValueType() { + private KeyType computeKeyType() { if (isShouldInheritAccountKeySet()) { - return (!isEcdsaSecp256k1KeySet() - && !isDelegatableContractIdSet() - && !isContractIDSet() - && !isEd25519KeySet()) - ? KeyValueType.INHERIT_ACCOUNT_KEY - : KeyValueType.INVALID_KEY; + return KeyType.INHERIT_ACCOUNT_KEY; } else if (isContractIDSet()) { - return !isEcdsaSecp256k1KeySet() && !isDelegatableContractIdSet() && !isEd25519KeySet() - ? KeyValueType.CONTRACT_ID - : KeyValueType.INVALID_KEY; + return KeyType.CONTRACT_ID; } else if (isEd25519KeySet()) { - return !isEcdsaSecp256k1KeySet() && !isDelegatableContractIdSet() - ? KeyValueType.ED25519 - : KeyValueType.INVALID_KEY; + return KeyType.ED25519; } else if (isEcdsaSecp256k1KeySet()) { - return !isDelegatableContractIdSet() ? KeyValueType.ECDSA_SECPK256K1 : KeyValueType.INVALID_KEY; + return KeyType.ECDSA_SECP256K1; } else { - return isDelegatableContractIdSet() ? KeyValueType.DELEGATABLE_CONTRACT_ID : KeyValueType.INVALID_KEY; + return KeyType.DELEGATABLE_CONTRACT_ID; } } - public KeyValueType getKeyValueType() { - return this.keyValueType; - } - public Key asGrpc() { - return switch (keyValueType) { + return switch (keyType) { + case INVALID_KEY -> Key.DEFAULT; case INHERIT_ACCOUNT_KEY -> this.inheritedKey; case CONTRACT_ID -> Key.newBuilder().contractID(contractID).build(); case ED25519 -> Key.newBuilder().ed25519(Bytes.wrap(ed25519)).build(); - case ECDSA_SECPK256K1 -> Key.newBuilder() + case ECDSA_SECP256K1 -> Key.newBuilder() .ecdsaSecp256k1(Bytes.wrap(ecdsaSecp256k1)) .build(); case DELEGATABLE_CONTRACT_ID -> Key.newBuilder() .delegatableContractId(delegatableContractID) .build(); - default -> throw new InvalidTransactionException("INVALID_KEY", ResponseCodeEnum.INVALID_TRANSACTION_BODY); }; } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java index ce5b21c9ea7a..755fb303bae8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java @@ -37,6 +37,7 @@ import com.hedera.node.app.service.contract.impl.records.ContractDeleteRecordBuilder; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.TokenServiceApi; +import com.hedera.node.app.service.token.api.TokenServiceApi.FreeAliasOnDeletion; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -99,7 +100,12 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var recordBuilder = context.recordBuilder(ContractDeleteRecordBuilder.class); final var deletedId = toBeDeleted.accountIdOrThrow(); context.serviceApi(TokenServiceApi.class) - .deleteAndTransfer(deletedId, obtainer.accountIdOrThrow(), context.expiryValidator(), recordBuilder); + .deleteAndTransfer( + deletedId, + obtainer.accountIdOrThrow(), + context.expiryValidator(), + recordBuilder, + FreeAliasOnDeletion.YES); recordBuilder.contractID(asNumericContractId(deletedId)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java index d3898fada5f7..001cef331fb5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java @@ -54,6 +54,7 @@ import com.hedera.node.config.data.StakingConfig; import com.hedera.node.config.data.TokensConfig; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; @@ -97,7 +98,7 @@ private boolean isAdminSigRequired(final ContractUpdateTransactionBody op) { || op.hasProxyAccountID() || op.hasAutoRenewPeriod() || op.hasFileID() - || op.memoOrElse("").length() > 0; + || !op.memoOrElse("").isEmpty(); } private boolean hasCryptoAdminKey(final ContractUpdateTransactionBody op) { @@ -113,17 +114,19 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = context.readableStore(ReadableAccountStore.class); final var toBeUpdated = accountStore.getContractById(target); validateSemantics(toBeUpdated, context, op, accountStore); - final var changed = update(toBeUpdated, context, op); - + final var changed = update(requireNonNull(toBeUpdated), context, op); context.serviceApi(TokenServiceApi.class).updateContract(changed); - context.recordBuilder(ContractUpdateRecordBuilder.class).contractID(target); + context.recordBuilder(ContractUpdateRecordBuilder.class) + .contractID(ContractID.newBuilder() + .contractNum(toBeUpdated.accountIdOrThrow().accountNumOrThrow()) + .build()); } private void validateSemantics( - Account contract, - HandleContext context, - ContractUpdateTransactionBody op, - ReadableAccountStore accountStore) { + @Nullable final Account contract, + @NonNull final HandleContext context, + @NonNull final ContractUpdateTransactionBody op, + @NonNull final ReadableAccountStore accountStore) { validateTrue(contract != null, INVALID_CONTRACT_ID); validateTrue(!contract.deleted(), INVALID_CONTRACT_ID); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index f2e9181f6a89..f6f826b411a6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -26,6 +26,8 @@ import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.SubType; +import com.hedera.hapi.node.contract.EthereumTransactionBody; +import com.hedera.node.app.hapi.utils.ethereum.EthTxSigs; import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder; import com.hedera.node.app.service.contract.impl.exec.TransactionComponent; import com.hedera.node.app.service.contract.impl.infra.EthTxSigsCache; @@ -42,7 +44,9 @@ import com.hedera.node.app.spi.workflows.TransactionHandler; import com.hedera.node.config.data.HederaConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; @@ -70,18 +74,22 @@ public EthereumTransactionHandler( @Override public void preHandle(@NonNull final PreHandleContext context) throws PreCheckException { requireNonNull(context); - final var body = context.body().ethereumTransactionOrThrow(); - final var fileStore = context.createStore(ReadableFileStore.class); - final var hederaConfig = context.configuration().getConfigData(HederaConfig.class); - final var hydratedTx = callDataHydration.tryToHydrate(body, fileStore, hederaConfig.firstUserEntity()); - - validateTruePreCheck(hydratedTx.status() == OK, hydratedTx.status()); - - final var ethTxData = hydratedTx.ethTxData(); - validateTruePreCheck(ethTxData != null, INVALID_ETHEREUM_TRANSACTION); - // Ignore the return value; we just want to cache the signature for use in handle() - ethereumSignatures.computeIfAbsent(ethTxData); + computeEthTxSigsFor( + context.body().ethereumTransactionOrThrow(), + context.createStore(ReadableFileStore.class), + context.configuration()); + } + + public @Nullable EthTxSigs maybeEthTxSigsFor( + @NonNull final EthereumTransactionBody op, + @NonNull final ReadableFileStore fileStore, + @NonNull final Configuration config) { + try { + return computeEthTxSigsFor(op, fileStore, config); + } catch (PreCheckException ignore) { + return null; + } } @Override @@ -120,4 +128,17 @@ public Fees calculateFees(@NonNull final FeeContext feeContext) { .legacyCalculate(sigValueObj -> new EthereumTransactionResourceUsage(new SmartContractFeeBuilder()) .usageGiven(fromPbj(body), sigValueObj, null)); } + + private EthTxSigs computeEthTxSigsFor( + @NonNull final EthereumTransactionBody op, + @NonNull final ReadableFileStore fileStore, + @NonNull final Configuration config) + throws PreCheckException { + final var hederaConfig = config.getConfigData(HederaConfig.class); + final var hydratedTx = callDataHydration.tryToHydrate(op, fileStore, hederaConfig.firstUserEntity()); + validateTruePreCheck(hydratedTx.status() == OK, hydratedTx.status()); + final var ethTxData = hydratedTx.ethTxData(); + validateTruePreCheck(ethTxData != null, INVALID_ETHEREUM_TRANSACTION); + return ethereumSignatures.computeIfAbsent(ethTxData); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java index e6aa8f69e278..dd34dca11ce4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java @@ -254,9 +254,10 @@ Optional tryTrackingSelfDestructBeneficiary( * exist in Besu because there contracts are just normal accounts with code; but in Hedera, there * are a few other properties that need to be set to "convert" an account into a contract. * - * @param alias the hollow account to be finalized as a contract + * @param address the hollow account to be finalized as a contract + * @param parent the address of the "parent" account finalizing the hollow account */ - void finalizeHollowAccount(@NonNull Address alias); + void finalizeHollowAccount(@NonNull Address address, @NonNull Address parent); /** * Returns all storage updates that would be committed by this updater, necessary for constructing diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index cad6d34a7e9b..574aa91b53cb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -295,13 +295,19 @@ public void setupInternalAliasedCreate(@NonNull final Address origin, @NonNull f * {@inheritDoc} */ @Override - public void finalizeHollowAccount(@NonNull final Address alias) { - evmFrameState.finalizeHollowAccount(alias); - // add child record on merge + public void finalizeHollowAccount(@NonNull final Address address, @NonNull final Address parent) { + // (FUTURE) Since for mono-service parity we externalize a ContractCreate populated with the + // contract-specific Hedera properties of the parent, we should either (1) actually set those + // properties on the finalized hollow account with those properties; or (2) stop adding them + // to the externalized creation record + evmFrameState.finalizeHollowAccount(address); + // Reset pending creation to null, as a CREATE2 operation "collided" with an existing + // hollow account instead of creating a truly new contract pendingCreation = null; - var contractId = getHederaContractId(alias); - var evmAddress = aliasFrom(alias); - enhancement.operations().externalizeHollowAccountMerge(contractId, evmAddress); + enhancement + .operations() + .externalizeHollowAccountMerge( + getHederaContractId(address), getHederaContractId(parent), aliasFrom(address)); } @Override diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java index 5cc21054540c..5cbdbb9c9773 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java @@ -137,8 +137,10 @@ public static long asExactLongValueOrZero(@NonNull final BigInteger value) { public static com.esaulpaugh.headlong.abi.Address headlongAddressOf(@NonNull final AccountID accountID) { requireNonNull(accountID); final var integralAddress = accountID.hasAccountNum() - ? asEvmAddress(accountID.accountNum()) - : accountID.alias().toByteArray(); + ? asEvmAddress(accountID.accountNumOrThrow()) + : accountID + .aliasOrElse(com.hedera.pbj.runtime.io.buffer.Bytes.EMPTY) + .toByteArray(); return asHeadlongAddress(integralAddress); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java index a4227c7f088d..95bbb649667f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/SynthTxnUtils.java @@ -96,7 +96,6 @@ public static ContractCreateTransactionBody synthContractCreationFromParent( @NonNull final ContractID pendingId, @NonNull final Account parent) { requireNonNull(parent); requireNonNull(pendingId); - // TODO - for mono-service equivalence, need to set the initial balance here final var builder = ContractCreateTransactionBody.newBuilder() .maxAutomaticTokenAssociations(parent.maxAutoAssociations()) .declineReward(parent.declineReward()) @@ -111,10 +110,11 @@ public static ContractCreateTransactionBody synthContractCreationFromParent( builder.stakedAccountId(parent.stakedAccountIdOrThrow()); } final var parentAdminKey = parent.keyOrThrow(); - if (!parentAdminKey.hasContractID()) { - builder.adminKey(parentAdminKey); - } else { + if (isSelfAdmin(parent)) { + // The new contract will manage itself as well, which we indicate via self-referential admin key builder.adminKey(Key.newBuilder().contractID(pendingId)); + } else { + builder.adminKey(parentAdminKey); } return builder.build(); } @@ -156,4 +156,10 @@ public static ContractCreateTransactionBody synthEthTxCreation( .initcode(Bytes.wrap(ethTxData.callData())) .build(); } + + private static boolean isSelfAdmin(@NonNull final Account parent) { + final long adminNum = + parent.keyOrThrow().contractIDOrElse(ContractID.DEFAULT).contractNumOrElse(0L); + return parent.accountIdOrThrow().accountNumOrThrow() == adminNum; + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java index 189bdb5dff6b..40f2dfc5e727 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java @@ -135,6 +135,6 @@ void finalizesHollowAccountWhenPendingContractIsHollowAccountAndLazyCreationEnab childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); childFrame.notifyCompletion(); verify(frame).pushStackItem(Words.fromAddress(EIP_1014_ADDRESS)); - verify(worldUpdater).finalizeHollowAccount(EIP_1014_ADDRESS); + verify(worldUpdater).finalizeHollowAccount(EIP_1014_ADDRESS, RECIEVER_ADDRESS); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java index 0adbc317aeb5..a5b34cbf76d2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java @@ -21,8 +21,10 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.*; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthAccountCreationFromHapi; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthContractCreationFromParent; +import static com.hedera.node.app.spi.workflows.record.ExternalizedRecordCustomizer.SUPPRESSING_EXTERNALIZED_RECORD_CUSTOMIZER; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -33,6 +35,7 @@ import com.hedera.hapi.node.base.*; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.hapi.node.contract.EthereumTransactionBody; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.token.CryptoCreateTransactionBody; import com.hedera.hapi.node.token.TokenCreateTransactionBody; @@ -103,6 +106,9 @@ class HandleHederaOperationsTest { @Mock private SystemContractGasCalculator gasCalculator; + @Mock + private ReadableAccountStore readableAccountStore; + private HandleHederaOperations subject; @BeforeEach @@ -113,7 +119,8 @@ void setUp() { context, tinybarValues, gasCalculator, - DEFAULT_HEDERA_CONFIG); + DEFAULT_HEDERA_CONFIG, + HederaFunctionality.CONTRACT_CALL); } @Test @@ -292,9 +299,10 @@ void updateStorageMetadataUsesApi() { @Test @SuppressWarnings("unchecked") - void createContractWithParentDispatchesAsExpectedThenMarksCreated() throws IOException { + void createContractWithNonSelfAdminParentDispatchesAsExpectedThenMarksCreated() throws IOException { final var parent = Account.newBuilder() - .key(Key.newBuilder().contractID(ContractID.newBuilder().contractNum(123L))) + .key(Key.newBuilder().contractID(ContractID.newBuilder().contractNum(124L))) + .accountId(AccountID.newBuilder().accountNum(123L).build()) .autoRenewAccountId(NON_SYSTEM_ACCOUNT_ID) .stakedNodeId(3) .declineReward(true) @@ -334,6 +342,54 @@ void createContractWithParentDispatchesAsExpectedThenMarksCreated() throws IOExc .markAsContract(AccountID.newBuilder().accountNum(666L).build(), NON_SYSTEM_ACCOUNT_ID); } + @Test + @SuppressWarnings("unchecked") + void createContractWithSelfAdminParentDispatchesAsExpectedThenMarksCreated() throws IOException { + final var parent = Account.newBuilder() + .key(Key.newBuilder().contractID(ContractID.newBuilder().contractNum(123L))) + .accountId(AccountID.newBuilder().accountNum(123L).build()) + .autoRenewAccountId(NON_SYSTEM_ACCOUNT_ID) + .stakedNodeId(3) + .declineReward(true) + .autoRenewSeconds(666L) + .maxAutoAssociations(321) + .memo("Something") + .build(); + final var pendingId = ContractID.newBuilder().contractNum(666L).build(); + final var synthContractCreation = synthContractCreationFromParent(pendingId, parent) + .copyBuilder() + .adminKey((Key) null) + .build(); + final var synthAccountCreation = + synthAccountCreationFromHapi(pendingId, CANONICAL_ALIAS, synthContractCreation); + final var synthTxn = TransactionBody.newBuilder() + .cryptoCreateAccount(synthAccountCreation) + .build(); + final var captor = ArgumentCaptor.forClass(ExternalizedRecordCustomizer.class); + given(context.serviceApi(TokenServiceApi.class)).willReturn(tokenServiceApi); + given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); + given(contractCreateRecordBuilder.contractID(any(ContractID.class))).willReturn(contractCreateRecordBuilder); + given(contractCreateRecordBuilder.contractCreateResult(any(ContractFunctionResult.class))) + .willReturn(contractCreateRecordBuilder); + given(context.dispatchRemovableChildTransaction( + eq(synthTxn), + eq(ContractCreateRecordBuilder.class), + eq(null), + eq(A_NEW_ACCOUNT_ID), + captor.capture())) + .willReturn(contractCreateRecordBuilder); + given(contractCreateRecordBuilder.status()).willReturn(OK); + given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); + given(accountStore.getAccountById(NON_SYSTEM_ACCOUNT_ID)).willReturn(parent); + given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); + + subject.createContract(666L, NON_SYSTEM_ACCOUNT_ID.accountNumOrThrow(), CANONICAL_ALIAS); + + assertInternalFinisherAsExpected(captor.getValue(), synthContractCreation); + verify(tokenServiceApi) + .markAsContract(AccountID.newBuilder().accountNum(666L).build(), NON_SYSTEM_ACCOUNT_ID); + } + private void assertInternalFinisherAsExpected( @NonNull final UnaryOperator internalFinisher, @NonNull final ContractCreateTransactionBody expectedOp) @@ -414,7 +470,15 @@ void createContractWithBodyDispatchesThenMarksAsContract() { } @Test - void createContractWithFailedDispatchNotImplemented() { + void createContractInsideEthereumTransactionWithBodyDispatchesThenMarksAsContract() { + subject = new HandleHederaOperations( + DEFAULT_LEDGER_CONFIG, + DEFAULT_CONTRACTS_CONFIG, + context, + tinybarValues, + gasCalculator, + DEFAULT_HEDERA_CONFIG, + HederaFunctionality.ETHEREUM_TRANSACTION); final var someBody = ContractCreateTransactionBody.newBuilder() .adminKey(AN_ED25519_KEY) .autoRenewAccountId(NON_SYSTEM_ACCOUNT_ID) @@ -425,9 +489,47 @@ void createContractWithFailedDispatchNotImplemented() { .cryptoCreateAccount(synthAccountCreationFromHapi(pendingId, CANONICAL_ALIAS, someBody)) .build(); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); + given(context.serviceApi(TokenServiceApi.class)).willReturn(tokenServiceApi); given(contractCreateRecordBuilder.contractID(any(ContractID.class))).willReturn(contractCreateRecordBuilder); given(contractCreateRecordBuilder.contractCreateResult(any(ContractFunctionResult.class))) .willReturn(contractCreateRecordBuilder); + given(context.dispatchRemovableChildTransaction( + eq(synthTxn), + eq(ContractCreateRecordBuilder.class), + eq(null), + eq(A_NEW_ACCOUNT_ID), + any(ExternalizedRecordCustomizer.class))) + .willReturn(contractCreateRecordBuilder); + given(contractCreateRecordBuilder.status()).willReturn(OK); + given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); + + subject.createContract(666L, someBody, CANONICAL_ALIAS); + + final var captor = ArgumentCaptor.forClass(ExternalizedRecordCustomizer.class); + verify(context) + .dispatchRemovableChildTransaction( + eq(synthTxn), + eq(ContractCreateRecordBuilder.class), + eq(null), + eq(A_NEW_ACCOUNT_ID), + captor.capture()); + assertNotSame(SUPPRESSING_EXTERNALIZED_RECORD_CUSTOMIZER, captor.getValue()); + verify(tokenServiceApi) + .markAsContract(AccountID.newBuilder().accountNum(666L).build(), NON_SYSTEM_ACCOUNT_ID); + } + + @Test + void createContractWithFailedDispatchNotImplemented() { + final var someBody = ContractCreateTransactionBody.newBuilder() + .adminKey(AN_ED25519_KEY) + .autoRenewAccountId(NON_SYSTEM_ACCOUNT_ID) + .autoRenewPeriod(SOME_DURATION) + .build(); + final var pendingId = ContractID.newBuilder().contractNum(666L).build(); + final var synthTxn = TransactionBody.newBuilder() + .cryptoCreateAccount(synthAccountCreationFromHapi(pendingId, CANONICAL_ALIAS, someBody)) + .build(); + given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); given(context.dispatchRemovableChildTransaction( eq(synthTxn), eq(ContractCreateRecordBuilder.class), @@ -442,9 +544,6 @@ void createContractWithFailedDispatchNotImplemented() { @Test void deleteUnaliasedContractUsesApi() { - given(contractDeleteRecordBuilder.contractID(any(ContractID.class))).willReturn(contractDeleteRecordBuilder); - given(contractDeleteRecordBuilder.transaction(any(Transaction.class))).willReturn(contractDeleteRecordBuilder); - given(context.addChildRecordBuilder(ContractDeleteRecordBuilder.class)).willReturn(contractDeleteRecordBuilder); given(context.serviceApi(TokenServiceApi.class)).willReturn(tokenServiceApi); subject.deleteUnaliasedContract(CALLED_CONTRACT_ID.contractNumOrThrow()); verify(tokenServiceApi).deleteContract(CALLED_CONTRACT_ID); @@ -452,9 +551,9 @@ void deleteUnaliasedContractUsesApi() { @Test void deleteAliasedContractUsesApi() { - given(contractDeleteRecordBuilder.contractID(any(ContractID.class))).willReturn(contractDeleteRecordBuilder); - given(contractDeleteRecordBuilder.transaction(any(Transaction.class))).willReturn(contractDeleteRecordBuilder); - given(context.addChildRecordBuilder(ContractDeleteRecordBuilder.class)).willReturn(contractDeleteRecordBuilder); + var txnBody = TransactionBody.newBuilder() + .ethereumTransaction(EthereumTransactionBody.DEFAULT) + .build(); given(context.serviceApi(TokenServiceApi.class)).willReturn(tokenServiceApi); subject.deleteAliasedContract(CANONICAL_ALIAS); verify(tokenServiceApi) @@ -477,18 +576,26 @@ void getOriginalSlotsUsedDelegatesToApi() { @Test void externalizeHollowAccountMerge() { // given - given(context.addRemovableChildRecordBuilder(ContractCreateRecordBuilder.class)) + var parentAccount = Account.newBuilder() + .accountId(AccountID.newBuilder().accountNum(1001).build()) + .key(Key.DEFAULT) + .build(); + var contractId = ContractID.newBuilder().contractNum(1001).build(); + given(context.readableStore(ReadableAccountStore.class)).willReturn(readableAccountStore); + given(readableAccountStore.getContractById(ContractID.DEFAULT)).willReturn(parentAccount); + given(context.addRemovableChildRecordBuilder(eq(ContractCreateRecordBuilder.class))) .willReturn(contractCreateRecordBuilder); - given(contractCreateRecordBuilder.contractID(ContractID.DEFAULT)).willReturn(contractCreateRecordBuilder); + given(contractCreateRecordBuilder.contractID(eq(contractId))).willReturn(contractCreateRecordBuilder); + given(contractCreateRecordBuilder.status(any())).willReturn(contractCreateRecordBuilder); given(contractCreateRecordBuilder.transaction(any(Transaction.class))).willReturn(contractCreateRecordBuilder); given(contractCreateRecordBuilder.contractCreateResult(any(ContractFunctionResult.class))) .willReturn(contractCreateRecordBuilder); // when - subject.externalizeHollowAccountMerge(ContractID.DEFAULT, VALID_CONTRACT_ADDRESS.evmAddress()); + subject.externalizeHollowAccountMerge(contractId, ContractID.DEFAULT, VALID_CONTRACT_ADDRESS.evmAddress()); // then - verify(contractCreateRecordBuilder).contractID(ContractID.DEFAULT); + verify(contractCreateRecordBuilder).contractID(contractId); verify(contractCreateRecordBuilder).contractCreateResult(any(ContractFunctionResult.class)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java index 749a40b2f07b..270390c0472c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java @@ -20,6 +20,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.CHILD; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -28,8 +29,10 @@ import static org.mockito.Mockito.verify; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Transaction; +import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.exec.scope.HandleSystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; @@ -42,6 +45,7 @@ import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.HandleContext; import java.util.function.Predicate; +import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -136,6 +140,33 @@ void externalizeSuccessfulResultTest() { verify(recordBuilder).contractCallResult(contractFunctionResult); } + @Test + void externalizeSuccessfulResultWithTransactionBodyTest() { + var transaction = Transaction.newBuilder() + .body(TransactionBody.newBuilder() + .transactionID(TransactionID.DEFAULT) + .build()) + .build(); + var contractFunctionResult = SystemContractUtils.contractFunctionResultSuccessFor( + 0, + org.apache.tuweni.bytes.Bytes.EMPTY, + 100L, + org.apache.tuweni.bytes.Bytes.EMPTY, + AccountID.newBuilder().build()); + + // given + given(context.addChildRecordBuilder(ContractCallRecordBuilder.class)).willReturn(recordBuilder); + given(recordBuilder.transaction(transaction)).willReturn(recordBuilder); + given(recordBuilder.status(ResponseCodeEnum.SUCCESS)).willReturn(recordBuilder); + + // when + subject.externalizeResult(contractFunctionResult, ResponseCodeEnum.SUCCESS, transaction); + + // then + verify(recordBuilder).status(ResponseCodeEnum.SUCCESS); + verify(recordBuilder).contractCallResult(contractFunctionResult); + } + @Test void externalizeFailedResultTest() { var contractFunctionResult = SystemContractUtils.contractFunctionResultSuccessFor( @@ -161,6 +192,11 @@ void externalizeFailedResultTest() { verify(recordBuilder).contractCallResult(contractFunctionResult); } + @Test + void syntheticTransactionForHtsCallTest() { + assertNotNull(subject.syntheticTransactionForHtsCall(Bytes.EMPTY, ContractID.DEFAULT, true)); + } + @Test void currentExchangeRateTest() { given(context.exchangeRateInfo()).willReturn(exchangeRateInfo); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java index 7f7f4495f6ce..8066ba8c27c5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/DispatchForResponseCodeHtsCallTest.java @@ -19,7 +19,11 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TREASURY_ACCOUNT_FOR_TOKEN; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall.OutputFn.STANDARD_OUTPUT_FN; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONFIG; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONTRACTS_CONFIG; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verifyNoInteractions; @@ -31,7 +35,10 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.DispatchForResponseCodeHtsCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; -import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Optional; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -52,16 +59,17 @@ class DispatchForResponseCodeHtsCallTest extends HtsCallTestBase { @Mock private ContractCallRecordBuilder recordBuilder; - private DispatchForResponseCodeHtsCall subject; + private final Deque stack = new ArrayDeque<>(); + + private DispatchForResponseCodeHtsCall subject; @BeforeEach void setUp() { - subject = new DispatchForResponseCodeHtsCall<>( + subject = new DispatchForResponseCodeHtsCall( mockEnhancement(), gasCalculator, AccountID.DEFAULT, TransactionBody.DEFAULT, - SingleTransactionRecordBuilder.class, verificationStrategy, dispatchGasCalculator, failureCustomizer, @@ -81,13 +89,38 @@ TransactionBody.DEFAULT, gasCalculator, mockEnhancement(), AccountID.DEFAULT)) .willReturn(123L); given(recordBuilder.status()).willReturn(SUCCESS); - final var pricedResult = subject.execute(); + final var pricedResult = subject.execute(frame); final var contractResult = pricedResult.fullResult().result().getOutput(); assertArrayEquals(ReturnTypes.encodedRc(SUCCESS).array(), contractResult.toArray()); verifyNoInteractions(failureCustomizer); } + @Test + void haltsImmediatelyWithNullDispatch() { + given(frame.getMessageFrameStack()).willReturn(stack); + given(frame.getContextVariable(CONFIG_CONTEXT_VARIABLE)).willReturn(DEFAULT_CONFIG); + given(frame.getMessageFrameStack()).willReturn(stack); + + subject = new DispatchForResponseCodeHtsCall( + mockEnhancement(), + gasCalculator, + AccountID.DEFAULT, + null, + verificationStrategy, + dispatchGasCalculator, + failureCustomizer, + STANDARD_OUTPUT_FN); + + final var pricedResult = subject.execute(frame); + final var fullResult = pricedResult.fullResult(); + + assertEquals( + Optional.of(ERROR_DECODING_PRECOMPILE_INPUT), + fullResult.result().getHaltReason()); + assertEquals(DEFAULT_CONTRACTS_CONFIG.precompileHtsDefaultGasCost(), fullResult.gasRequirement()); + } + @Test void failureResultCustomized() { given(systemContractOperations.dispatch( @@ -103,7 +136,7 @@ TransactionBody.DEFAULT, gasCalculator, mockEnhancement(), AccountID.DEFAULT)) given(failureCustomizer.customize(TransactionBody.DEFAULT, INVALID_ACCOUNT_ID, mockEnhancement())) .willReturn(INVALID_TREASURY_ACCOUNT_FOR_TOKEN); - final var pricedResult = subject.execute(); + final var pricedResult = subject.execute(frame); final var contractResult = pricedResult.fullResult().result().getOutput(); assertArrayEquals( ReturnTypes.encodedRc(INVALID_TREASURY_ACCOUNT_FOR_TOKEN).array(), contractResult.toArray()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractHandlerTestBase.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractHandlerTestBase.java index 830fa3ecdcba..10f7b8a4d51b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractHandlerTestBase.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractHandlerTestBase.java @@ -35,6 +35,7 @@ import com.hedera.node.app.spi.key.HederaKey; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; +import com.hedera.pbj.runtime.io.buffer.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -56,6 +57,12 @@ public class ContractHandlerTestBase implements TransactionFactory { protected final ContractID targetContract = ContractID.newBuilder().contractNum(9_999L).build(); + @Mock + private Bytes evmAddress; + + protected final ContractID targetContractWithEvmAddress = + ContractID.newBuilder().evmAddress(evmAddress).build(); + @Mock protected MerkleAccount payerMerkleAccount; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java index c136353d415e..e60a65adf777 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractUpdateHandlerTest.java @@ -428,6 +428,8 @@ void maxAutomaticTokenAssociationsWhenItIsNotAllowedFails() { void verifyTheCorrectOutsideValidatorsAndUpdateContractAPIAreCalled() { doReturn(attributeValidator).when(context).attributeValidator(); when(accountStore.getContractById(targetContract)).thenReturn(contract); + when(contract.accountIdOrThrow()) + .thenReturn(AccountID.newBuilder().accountNum(666).build()); when(contract.key()).thenReturn(Key.newBuilder().build()); when(context.expiryValidator()).thenReturn(expiryValidator); when(context.serviceApi(TokenServiceApi.class)).thenReturn(tokenServiceApi); @@ -632,4 +634,37 @@ void updateAllFields() { assertEquals(op.maxAutomaticTokenAssociations(), updatedContract.maxAutoAssociations()); verify(attributeValidator, times(1)).validateMemo(op.memo()); } + + @Test + void handleWhenTargetIdContainOnlyEvmAddress() { + doReturn(attributeValidator).when(context).attributeValidator(); + when(accountStore.getContractById(targetContractWithEvmAddress)).thenReturn(contract); + when(contract.accountIdOrThrow()) + .thenReturn(AccountID.newBuilder().accountNum(999L).build()); + when(contract.key()).thenReturn(Key.newBuilder().build()); + when(context.expiryValidator()).thenReturn(expiryValidator); + when(context.serviceApi(TokenServiceApi.class)).thenReturn(tokenServiceApi); + given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); + final var txn = TransactionBody.newBuilder() + .contractUpdateInstance(ContractUpdateTransactionBody.newBuilder() + .contractID(targetContractWithEvmAddress) + .adminKey(adminKey) + .memo("memo")) + .transactionID(transactionID) + .build(); + when(context.body()).thenReturn(txn); + when(context.configuration()).thenReturn(configuration); + when(configuration.getConfigData(StakingConfig.class)).thenReturn(stakingConfig); + when(stakingConfig.isEnabled()).thenReturn(true); + when(contract.copyBuilder()).thenReturn(mock(Builder.class)); + when(context.recordBuilder(ContractUpdateRecordBuilder.class)).thenReturn(recordBuilder); + + subject.handle(context); + + verify(expiryValidator, times(1)).resolveUpdateAttempt(any(), any(), anyBoolean()); + verify(tokenServiceApi, times(1)) + .assertValidStakingElectionForUpdate(anyBoolean(), anyBoolean(), any(), any(), any(), any(), any()); + verify(tokenServiceApi, times(1)).updateContract(any()); + verify(recordBuilder, times(1)).contractID(any()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java index 5897c764e69e..48d856ab7c48 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/ProxyWorldUpdaterTest.java @@ -21,6 +21,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.OUTPUT_DATA; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.PERMITTED_ADDRESS_CALLER; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.RELAYER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.aliasFrom; @@ -217,8 +218,11 @@ void delegatesFeeCharging() { @Test void delegatesHollowFinalization() { given(evmFrameState.getAccount(EIP_1014_ADDRESS)).willReturn(proxyEvmAccount); + given(evmFrameState.getAccount(PERMITTED_ADDRESS_CALLER)).willReturn(proxyEvmAccount); + given(proxyEvmAccount.hederaContractId()) + .willReturn(ContractID.newBuilder().contractNum(999L).build()); subject.setupTopLevelLazyCreate(EIP_1014_ADDRESS); - subject.finalizeHollowAccount(EIP_1014_ADDRESS); + subject.finalizeHollowAccount(EIP_1014_ADDRESS, PERMITTED_ADDRESS_CALLER); verify(evmFrameState).finalizeHollowAccount(EIP_1014_ADDRESS); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java index 706b673fae84..b39ab5f95b62 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/SynthTxnUtilsTest.java @@ -33,6 +33,7 @@ import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthHollowAccountCreation; import static org.junit.jupiter.api.Assertions.*; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.KeyList; @@ -56,6 +57,7 @@ void createsExpectedHollowSynthBody() { @Test void convertsParentWithStakedNodeAndDeclinedRewardAndAutoRenewIdAndNoAdminKeyAsExpected() { final var parent = Account.newBuilder() + .accountId(AccountID.newBuilder().accountNum(123L).build()) .key(Key.newBuilder().contractID(ContractID.newBuilder().contractNum(123L))) .autoRenewAccountId(NON_SYSTEM_ACCOUNT_ID) .stakedNodeId(3) @@ -77,6 +79,7 @@ void convertsParentWithStakedNodeAndDeclinedRewardAndAutoRenewIdAndNoAdminKeyAsE @Test void convertsParentWithStakedAccountAndNoAutoRenewIdAndAdminKeyAsExpected() { final var parent = Account.newBuilder() + .accountId(AccountID.newBuilder().accountNum(123L).build()) .key(AN_ED25519_KEY) .stakedAccountId(A_NEW_ACCOUNT_ID) .declineReward(true) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java index 02b8fab22ea8..067a475805e4 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAccountStore.java @@ -205,7 +205,10 @@ public Set modifiedAccountsInState() { final var contractId = ContractID.newBuilder() .contractNum(accountId.accountNumOrThrow()) .build(); - updates.add(new ContractNonceInfo(contractId, newAccount.ethereumNonce())); + // exclude nonce info if contract was destructed + if (!newAccount.deleted()) { + updates.add(new ContractNonceInfo(contractId, newAccount.ethereumNonce())); + } if (oldAccount == null) { newContractIds.add(contractId); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java index 182f089db408..443a715f082f 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/api/TokenServiceApiImpl.java @@ -23,6 +23,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSFER_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES; +import static com.hedera.node.app.service.token.api.TokenServiceApi.FreeAliasOnDeletion.YES; import static com.hedera.node.app.spi.key.KeyUtils.IMMUTABILITY_SENTINEL_KEY; import static com.hedera.node.app.spi.workflows.HandleException.validateFalse; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; @@ -482,8 +483,9 @@ public boolean checkForCustomFees(@NonNull final CryptoTransferTransactionBody o public void deleteAndTransfer( @NonNull final AccountID deletedId, @NonNull final AccountID obtainerId, - @NonNull ExpiryValidator expiryValidator, - @NonNull final DeleteCapableTransactionRecordBuilder recordBuilder) { + @NonNull final ExpiryValidator expiryValidator, + @NonNull final DeleteCapableTransactionRecordBuilder recordBuilder, + @NonNull final FreeAliasOnDeletion freeAliasOnDeletion) { // validate the semantics involving dynamic properties and state. // Gets delete and transfer accounts from state final var deleteAndTransferAccounts = validateSemantics(deletedId, obtainerId, expiryValidator); @@ -492,12 +494,12 @@ public void deleteAndTransfer( // get the account from account store that has all balance changes // commit the account with deleted flag set to true final var updatedDeleteAccount = requireNonNull(accountStore.getForModify(deletedId)); - accountStore.removeAlias(updatedDeleteAccount.alias()); - accountStore.put(updatedDeleteAccount - .copyBuilder() - .alias(Bytes.EMPTY) - .deleted(true) - .build()); + final var builder = updatedDeleteAccount.copyBuilder().deleted(true); + if (freeAliasOnDeletion == YES) { + accountStore.removeAlias(updatedDeleteAccount.alias()); + builder.alias(Bytes.EMPTY); + } + accountStore.put(builder.build()); // add the transfer account for this deleted account to record builder. // This is needed while computing staking rewards. In the future it will also be added diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java index 1675a330791e..8d5589f01593 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteHandler.java @@ -30,6 +30,7 @@ import com.hedera.node.app.hapi.utils.fee.CryptoFeeBuilder; import com.hedera.node.app.service.mono.fees.calculation.crypto.txns.CryptoDeleteResourceUsage; import com.hedera.node.app.service.token.api.TokenServiceApi; +import com.hedera.node.app.service.token.api.TokenServiceApi.FreeAliasOnDeletion; import com.hedera.node.app.service.token.records.CryptoDeleteRecordBuilder; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; @@ -85,7 +86,8 @@ public void handle(@NonNull final HandleContext context) { op.deleteAccountIDOrThrow(), op.transferAccountIDOrThrow(), context.expiryValidator(), - context.recordBuilder(CryptoDeleteRecordBuilder.class)); + context.recordBuilder(CryptoDeleteRecordBuilder.class), + FreeAliasOnDeletion.NO); } @NonNull diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java index dac248226317..3f94cb1fbbb2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java @@ -21,11 +21,11 @@ import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.calculateRewardSumHistory; import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.computeNextStake; import static com.hedera.node.app.service.token.impl.handlers.staking.EndOfStakingPeriodUtils.readableNonZeroHistory; +import static com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder.transactionWith; import com.google.common.annotations.VisibleForTesting; import com.hedera.hapi.node.base.Fraction; import com.hedera.hapi.node.base.Timestamp; -import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.state.token.NetworkStakingRewards; import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.hapi.node.transaction.NodeStake; @@ -241,9 +241,7 @@ public void updateNodes(@NonNull final TokenContext context) { final var nodeStakeUpdateBuilder = context.addUncheckedPrecedingChildRecordBuilder(NodeStakeUpdateRecordBuilder.class); nodeStakeUpdateBuilder - .transaction(Transaction.newBuilder() - .body(syntheticNodeStakeUpdateTxn.build()) - .build()) + .transaction(transactionWith(syntheticNodeStakeUpdateTxn.build())) .memo("End of staking period calculation record"); } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java index 60f34b4e11d4..c16972d8e322 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/api/TokenServiceApi.java @@ -45,6 +45,11 @@ public interface TokenServiceApi { */ boolean checkForCustomFees(@NonNull CryptoTransferTransactionBody op); + enum FreeAliasOnDeletion { + YES, + NO + } + /** * Deletes the account with the given id and transfers any remaining hbar balance to the given obtainer id. * @@ -52,13 +57,15 @@ public interface TokenServiceApi { * @param obtainerId the id of the account to transfer the remaining hbar balance to * @param expiryValidator the expiry validator to use * @param recordBuilder the record builder to record the transfer in + * @param freeAliasOnDeletion whether to free the deleted account's alias (if any) * @throws HandleException if the account could not be deleted for some reason */ void deleteAndTransfer( @NonNull AccountID deletedId, @NonNull AccountID obtainerId, @NonNull ExpiryValidator expiryValidator, - @NonNull DeleteCapableTransactionRecordBuilder recordBuilder); + @NonNull DeleteCapableTransactionRecordBuilder recordBuilder, + @NonNull FreeAliasOnDeletion freeAliasOnDeletion); /** * Validates the creation of a given staking election relative to the given account store, network info, and staking config. diff --git a/hedera-node/test-clients/record-snapshots/Create2Operation.json b/hedera-node/test-clients/record-snapshots/Create2Operation.json new file mode 100644 index 000000000000..42ea74350612 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/Create2Operation.json @@ -0,0 +1 @@ +{"specSnapshots":{"Create2FactoryWorksAsExpected":{"placeholderNum":2961,"encodedItems":[{"b64Body":"Cg8KCQid9v+rBhCfAhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjZxNqvBhDg4OEJGm0KIhIgJ94oToc0cewsuZ3IJrVtu0gziTFJsommRyuYEmYI3p8KIzohAvSJHTH4WSJpAvPXzPCCfnQLMEv4F98CNzgjgrehsjR+CiISIF/VY1sJ66kCxTLbshzNcaFu1jRIhGYiTl6hp2OFl9gYIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGJIXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBggQEhl95sXiCRZgjLTM/aHou/+w0+G982ihiLeub8LsMQMbVp6dhuhLbW6TPVU9AaCwjZ9v+rBhCr2+YvIg8KCQid9v+rBhCfAhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQid9v+rBhCjAhICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxiSFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGI0NTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwM2Y1NzYwMDAzNTYwZTAxYzgwNjM4MTg3MWNiYzE0NjEwMDQ0NTc4MDYzOTRjYTJjYjUxNDYxMDA4MTU3ODA2MzljNGFlMmQwMTQ2MTAwYmU1NzgwNjNhMzRkMDYwMzE0NjEwMGRhNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwNTA1NzYwMDA4MGZkNWI1MDYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwMmZhNTY1YjYxMDBmNjU2NWI2MDQwNTE2MTAwNzg5MTkwNjEwM2NhNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDA4ZDU3NjAwMDgwZmQ1YjUwNjEwMGE4NjAwNDgwMzYwMzgxMDE5MDYxMDBhMzkxOTA2MTA1MjE1NjViNjEwMTZjNTY1YjYwNDA1MTYxMDBiNTkxOTA2MTA1OGM1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGQ4NjAwNDgwMzYwMzgxMDE5MDYxMDBkMzkxOTA2MTA1MjE1NjViNjEwMWI2NTY1YjAwNWI2MTAwZjQ2MDA0ODAzNjAzODEwMTkwNjEwMGVmOTE5MDYxMDUyMTU2NWI2MTAyMGM1NjViMDA1YjYwNjA2MDAwNjA0MDUxODA2MDIwMDE2MTAxMGE5MDYxMDI0NTU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDgwODQ4NDYwNDA1MTYwMjAwMTYxMDEzNDkyOTE5MDYxMDViNjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI2MDQwNTE2MDIwMDE2MTAxNTQ5MjkxOTA2MTA2MWI1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjBmZjYwZjgxYjMwODQ4NjgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTkxOTQ5MzkyOTE5MDYxMDcyMDU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDYwMDAxYzkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTgzNTE2MDIwODUwMTM0ZjU5MDUwODAzYjYxMDFjZTU3NjAwMDgwZmQ1YjdmYjAzYzUzYjI4ZTc4YTg4ZTMxNjA3YTI3ZTFmYTQ4MjM0ZGNlMjhkNWQ5ZDllYzdiMjk1YWViMDJlNjc0YTFlMTgxODM2MDQwNTE2MTAxZmY5MjkxOTA2MTA1YjY1NjViNjA0MDUxODA5MTAzOTBhMTUwNTA1MDU2NWI2MDAwODA2MDAyMzQ2MTAyMWM5MTkwNjEwNzlkNTY1YjkwNTA4Mjg0NTE2MDIwODYwMTgzZjU5MTUwODI4NDUxNjAyMDg2MDE4M2Y1OTE1MDgxM2I2MTAyM2Y1NzYwMDA4MGZkNWI1MDUwNTA1MDU2NWI2MTAzNDE4MDYxMDdjZjgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDI5MTgyNjEwMjY2NTY1YjkwNTA5MTkwNTA1NjViNjEwMmExODE2MTAyODY1NjViODExNDYxMDJhYzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJiZTgxNjEwMjk4NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMmQ3ODE2MTAyYzQ1NjViODExNDYxMDJlMjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJmNDgxNjEwMmNlNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMzExNTc2MTAzMTA2MTAyNWM1NjViNWI2MDAwNjEwMzFmODU4Mjg2MDE2MTAyYWY1NjViOTI1MDUwNjAyMDYxMDMzMDg1ODI4NjAxNjEwMmU1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MjgyNTI2MDIwODIwMTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDM3NDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDM1OTU2NWI2MDAwODQ4NDAxNTI1MDUwNTA1MDU2NWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViNjAwMDYxMDM5YzgyNjEwMzNhNTY1YjYxMDNhNjgxODU2MTAzNDU1NjViOTM1MDYxMDNiNjgxODU2MDIwODYwMTYxMDM1NjU2NWI2MTAzYmY4MTYxMDM4MDU2NWI4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTAzZTQ4MTg0NjEwMzkxNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTA0MmU4MjYxMDM4MDU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTA0NGQ1NzYxMDQ0YzYxMDNmNjU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTA0NjA2MTAyNTI1NjViOTA1MDYxMDQ2YzgyODI2MTA0MjU1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTA0OGM1NzYxMDQ4YjYxMDNmNjU2NWI1YjYxMDQ5NTgyNjEwMzgwNTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDRjNDYxMDRiZjg0NjEwNDcxNTY1YjYxMDQ1NjU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTA0ZTA1NzYxMDRkZjYxMDNmMTU2NWI1YjYxMDRlYjg0ODI4NTYxMDRhMjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDUwODU3NjEwNTA3NjEwM2VjNTY1YjViODEzNTYxMDUxODg0ODI2MDIwODYwMTYxMDRiMTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDUzODU3NjEwNTM3NjEwMjVjNTY1YjViNjAwMDgzMDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDU1NjU3NjEwNTU1NjEwMjYxNTY1YjViNjEwNTYyODU4Mjg2MDE2MTA0ZjM1NjViOTI1MDUwNjAyMDYxMDU3Mzg1ODI4NjAxNjEwMmU1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwNTg2ODE2MTAyODY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDVhMTYwMDA4MzAxODQ2MTA1N2Q1NjViOTI5MTUwNTA1NjViNjEwNWIwODE2MTAyYzQ1NjViODI1MjUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMDVjYjYwMDA4MzAxODU2MTA1N2Q1NjViNjEwNWQ4NjAyMDgzMDE4NDYxMDVhNzU2NWI5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNWY1ODI2MTAzM2E1NjViNjEwNWZmODE4NTYxMDVkZjU2NWI5MzUwNjEwNjBmODE4NTYwMjA4NjAxNjEwMzU2NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA2Mjc4Mjg1NjEwNWVhNTY1YjkxNTA2MTA2MzM4Mjg0NjEwNWVhNTY1YjkxNTA4MTkwNTA5MzkyNTA1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDY4NjYxMDY4MTgyNjEwNjNmNTY1YjYxMDY2YjU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YTQ4MjYxMDY4YzU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YjY4MjYxMDY5OTU2NWI5MDUwOTE5MDUwNTY1YjYxMDZjZTYxMDZjOTgyNjEwMjg2NTY1YjYxMDZhYjU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDZlZjYxMDZlYTgyNjEwMmM0NTY1YjYxMDZkNDU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNzFhNjEwNzE1ODI2MTA2ZjU1NjViNjEwNmZmNTY1YjgyNTI1MDUwNTY1YjYwMDA2MTA3MmM4Mjg3NjEwNjc1NTY1YjYwMDE4MjAxOTE1MDYxMDczYzgyODY2MTA2YmQ1NjViNjAxNDgyMDE5MTUwNjEwNzRjODI4NTYxMDZkZTU2NWI2MDIwODIwMTkxNTA2MTA3NWM4Mjg0NjEwNzA5NTY1YjYwMjA4MjAxOTE1MDgxOTA1MDk1OTQ1MDUwNTA1MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwMTI2MDA0NTI2MDI0NjAwMGZkNWI2MDAwNjEwN2E4ODI2MTAyYzQ1NjViOTE1MDYxMDdiMzgzNjEwMmM0NTY1YjkyNTA4MjYxMDdjMzU3NjEwN2MyNjEwNzZlNTY1YjViODI4MjA0OTA1MDkyOTE1MDUwNTZmZTYwODA2MDQwNTI2MDQwNTE2MTAzNDEzODAzODA2MTAzNDE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwDqaxE5Xi1ynJKITiDvZlBnloWUrRAcd7eKCLpWhsOYkaWyH3GVwvUH2Y/eZ3mRAuGgwI2fb/qwYQ47q3sQIiDwoJCJ32/6sGEKMCEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQie9v+rBhCpAhICGAISAhgDGLnZvjQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB0g0SAxiSFyLKDTgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMjU5MTkwNjEwMTBjNTY1YjgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxODE5MDU1NTA1MDUwNjEwMTRjNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDBhMzgyNjEwMDc4NTY1YjkwNTA5MTkwNTA1NjViNjEwMGIzODE2MTAwOTg1NjViODExNDYxMDBiZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDBkMDgxNjEwMGFhNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMGU5ODE2MTAwZDY1NjViODExNDYxMDBmNDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDEwNjgxNjEwMGUwNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMTIzNTc2MTAxMjI2MTAwNzM1NjViNWI2MDAwNjEwMTMxODU4Mjg2MDE2MTAwYzE1NjViOTI1MDUwNjAyMDYxMDE0Mjg1ODI4NjAxNjEwMGY3NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMWU2ODA2MTAxNWI2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA1MTU3ODA2MzczN2JjM2M5MTQ2MTAwNmY1NzgwNjM4ZGE1Y2I1YjE0NjEwMDc5NTc4MDYzYzI5ODU1NzgxNDYxMDA5NzU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGI1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAxMzk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGJkNTY1YjAwNWI2MTAwODE2MTAwZjY1NjViNjA0MDUxNjEwMDhlOTE5MDYxMDE5NTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwOWY2MTAxMWE1NjViNjA0MDUxNjEwMGFjOTE5MDYxMDEzOTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNDc5MDUwOTA1NjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1NjViNjAwMTU0ODE1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAxMzM4MTYxMDEyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTRlNjAwMDgzMDE4NDYxMDEyYTU2NWI5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDE3ZjgyNjEwMTU0NTY1YjkwNTA5MTkwNTA1NjViNjEwMThmODE2MTAxNzQ1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDFhYTYwMDA4MzAxODQ2MTAxODY1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBjMDAxMmU4ZmVkYjY5ZTBhZTgyNTEyZjg4NjA0MTFiNDM5MzM3MmQ4NTE2YzU4ZDg2ZDAzNmNiNDNhNTkxOGFiNjQ3MzZmNmM2MzQzMDAwODExMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwMmU3NWVmZWE2YTA1OWNiZTZkOWRhNWQzMmI4NzVjNDU3Y2ExMzlmNzg2OGE5MzEzMjE3YzY2ZDQ1NzZmZWUxMDY0NzM2ZjZjNjM0MzAwMDgxMTAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwT4y0WOqoZeb0jOd+Je9nICwMA0SpkQPUaTEb0mgVPGjq0Iem8xl4mmb5Jt99OL8TGgsI2vb/qwYQk9ilOCIPCgkInvb/qwYQqQISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQie9v+rBhCrAhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIHKt8109uILwF6ztTqXCnx4O+3KnTqK7LOYnkm/j/M+wEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGJMXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBv14z56GTpI6+6ryGnczKefiyjBbDxcd27B+peOZ+HX85AZPIJDDyWjWofdZfBdcIaDAja9v+rBhCLmuu5AiIPCgkInvb/qwYQqwISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+P38BKCgsKAxiTFxCAkN/ASg=="},{"b64Body":"Cg8KCQif9v+rBhCtAhICGAISAhgDGKL815MDIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CSQoDGJIXGiISINCpW0CZq4GY6Oid1V95Z2poWVpPtG4bZK06BU/aiDsOIJChD0IFCIHO2gNSAFoAagpKVVNUIERPIElUegMYkxc=","b64Record":"CiUIFiIDGJQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDtJTgVGUqJQA1Y0aMj98c2tSUXgnQ1J/rxIIOE8/8cZAoVO6wc1re9PsadP80KxqYaCwjb9v+rBhDTjZc+Ig8KCQif9v+rBhCtAhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC+hgKAxiUFxLFFmCAYEBSYAQ2EGEAP1dgADVg4ByAY4GHHLwUYQBEV4BjlMostRRhAIFXgGOcSuLQFGEAvleAY6NNBgMUYQDaV1tgAID9WzSAFWEAUFdgAID9W1BhAGtgBIA2A4EBkGEAZpGQYQL6VlthAPZWW2BAUWEAeJGQYQPKVltgQFGAkQOQ81s0gBVhAI1XYACA/VtQYQCoYASANgOBAZBhAKORkGEFIVZbYQFsVltgQFFhALWRkGEFjFZbYEBRgJEDkPNbYQDYYASANgOBAZBhANORkGEFIVZbYQG2VlsAW2EA9GAEgDYDgQGQYQDvkZBhBSFWW2ECDFZbAFtgYGAAYEBRgGAgAWEBCpBhAkVWW2AgggGBA4JSYB8ZYB+CARZgQFJQkFCAhIRgQFFgIAFhATSSkZBhBbZWW2BAUWAggYMDA4FSkGBAUmBAUWAgAWEBVJKRkGEGG1ZbYEBRYCCBgwMDgVKQYEBSkVBQkpFQUFZbYACAYP9g+BswhIaAUZBgIAEgYEBRYCABYQGRlJOSkZBhByBWW2BAUWAggYMDA4FSkGBAUoBRkGAgASCQUIBgAByRUFCSkVBQVltgAIGDUWAghQE09ZBQgDthAc5XYACA/Vt/sDxTso54qI4xYHon4fpII03OKNXZ2ex7KVrrAuZ0oeGBg2BAUWEB/5KRkGEFtlZbYEBRgJEDkKFQUFBWW2AAgGACNGECHJGQYQedVluQUIKEUWAghgGD9ZFQgoRRYCCGAYP1kVCBO2ECP1dgAID9W1BQUFBWW2EDQYBhB8+DOQGQVltgAGBAUZBQkFZbYACA/VtgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhApGCYQJmVluQUJGQUFZbYQKhgWEChlZbgRRhAqxXYACA/VtQVltgAIE1kFBhAr6BYQKYVluSkVBQVltgAIGQUJGQUFZbYQLXgWECxFZbgRRhAuJXYACA/VtQVltgAIE1kFBhAvSBYQLOVluSkVBQVltgAIBgQIOFAxIVYQMRV2EDEGECXFZbW2AAYQMfhYKGAWECr1ZbklBQYCBhAzCFgoYBYQLlVluRUFCSUJKQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBVhA3RXgIIBUYGEAVJgIIEBkFBhA1lWW2AAhIQBUlBQUFBWW2AAYB8ZYB+DARaQUJGQUFZbYABhA5yCYQM6VlthA6aBhWEDRVZbk1BhA7aBhWAghgFhA1ZWW2EDv4FhA4BWW4QBkVBQkpFQUFZbYABgIIIBkFCBgQNgAIMBUmED5IGEYQORVluQUJKRUFBWW2AAgP1bYACA/Vt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EELoJhA4BWW4EBgYEQZ///////////ghEXFWEETVdhBExhA/ZWW1uAYEBSUFBQVltgAGEEYGECUlZbkFBhBGyCgmEEJVZbkZBQVltgAGf//////////4IRFWEEjFdhBIthA/ZWW1thBJWCYQOAVluQUGAggQGQUJGQUFZbgoGDN2AAg4MBUlBQUFZbYABhBMRhBL+EYQRxVlthBFZWW5BQgoFSYCCBAYSEhAERFWEE4FdhBN9hA/FWW1thBOuEgoVhBKJWW1CTklBQUFZbYACCYB+DARJhBQhXYQUHYQPsVltbgTVhBRiEgmAghgFhBLFWW5FQUJKRUFBWW2AAgGBAg4UDEhVhBThXYQU3YQJcVltbYACDATVn//////////+BERVhBVZXYQVVYQJhVltbYQVihYKGAWEE81ZbklBQYCBhBXOFgoYBYQLlVluRUFCSUJKQUFZbYQWGgWEChlZbglJQUFZbYABgIIIBkFBhBaFgAIMBhGEFfVZbkpFQUFZbYQWwgWECxFZbglJQUFZbYABgQIIBkFBhBctgAIMBhWEFfVZbYQXYYCCDAYRhBadWW5OSUFBQVltgAIGQUJKRUFBWW2AAYQX1gmEDOlZbYQX/gYVhBd9WW5NQYQYPgYVgIIYBYQNWVluAhAGRUFCSkVBQVltgAGEGJ4KFYQXqVluRUGEGM4KEYQXqVluRUIGQUJOSUFBQVltgAH//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIWkFCRkFBWW2AAgZBQkZBQVlthBoZhBoGCYQY/VlthBmtWW4JSUFBWW2AAgWBgG5BQkZBQVltgAGEGpIJhBoxWW5BQkZBQVltgAGEGtoJhBplWW5BQkZBQVlthBs5hBsmCYQKGVlthBqtWW4JSUFBWW2AAgZBQkZBQVlthBu9hBuqCYQLEVlthBtRWW4JSUFBWW2AAgZBQkZBQVltgAIGQUJGQUFZbYQcaYQcVgmEG9VZbYQb/VluCUlBQVltgAGEHLIKHYQZ1VltgAYIBkVBhBzyChmEGvVZbYBSCAZFQYQdMgoVhBt5WW2AgggGRUGEHXIKEYQcJVltgIIIBkVCBkFCVlFBQUFBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgEmAEUmAkYAD9W2AAYQeogmECxFZbkVBhB7ODYQLEVluSUIJhB8NXYQfCYQduVltbgoIEkFCSkVBQVv5ggGBAUmBAUWEDQTgDgGEDQYM5gYEBYEBSgQGQYQAlkZBhAQxWW4FgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAYAGBkFVQUFBhAUxWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEAo4JhAHhWW5BQkZBQVlthALOBYQCYVluBFGEAvldgAID9W1BWW2AAgVGQUGEA0IFhAKpWW5KRUFBWW2AAgZBQkZBQVlthAOmBYQDWVluBFGEA9FdgAID9W1BWW2AAgVGQUGEBBoFhAOBWW5KRUFBWW2AAgGBAg4UDEhVhASNXYQEiYQBzVltbYABhATGFgoYBYQDBVluSUFBgIGEBQoWChgFhAPdWW5FQUJJQkpBQVlthAeaAYQFbYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMSBl/gFGEAUVeAY3N7w8kUYQBvV4BjjaXLWxRhAHlXgGPCmFV4FGEAl1dbYACA/VthAFlhALVWW2BAUWEAZpGQYQE5VltgQFGAkQOQ81thAHdhAL1WWwBbYQCBYQD2VltgQFFhAI6RkGEBlVZbYEBRgJEDkPNbYQCfYQEaVltgQFFhAKyRkGEBOVZbYEBRgJEDkPNbYABHkFCQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1tgAIBUkGEBAAqQBHP//////////////////////////xaBVltgAVSBVltgAIGQUJGQUFZbYQEzgWEBIFZbglJQUFZbYABgIIIBkFBhAU5gAIMBhGEBKlZbkpFQUFZbYABz//////////////////////////+CFpBQkZBQVltgAGEBf4JhAVRWW5BQkZBQVlthAY+BYQF0VluCUlBQVltgAGAgggGQUGEBqmAAgwGEYQGGVluSkVBQVv6iZGlwZnNYIhIgwAEuj+22ngroJRL4hgQRtDkzcthRbFjYbQNstDpZGKtkc29sY0MACBEAM6JkaXBmc1giEiAude/qagWcvm2dpdMrh1xFfKE594aKkxMhfGbUV2/uEGRzb2xjQwAIEQAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxiUF0oWChQAAAAAAAAAAAAAAAAAAAAAAAALlHIHCgMYlBcQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQif9v+rBhC7AhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxiUFxCAkvQBGNIJIoQIo00GAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIISIDGJQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDj3wVg0U3bgnFKX7WE+zQwnwmxf1lWgkBoesffv+e1nD16o/GlfvGQO3Pr57GCttYaDAjb9v+rBhDjwt3bAiIPCgkIn/b/qwYQuwISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCWxLGFAToJGgIweCi6vPABUhgKCgoCGAIQq4jjigIKCgoCGGIQrIjjigI="},{"b64Body":"Cg8KCQig9v+rBhC/AhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxiUFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGJQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDjs0t1ESm1a3cTNwJiWw1NuHUz9za6orF4NznMeno2JBHcRS6UASdYsOhyzqqXdLAaDAjc9v+rBhCz/cWAASIPCgkIoPb/qwYQvwISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpMFCgMYlBcigAIAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwEy7AIKAxiUFxKAAgAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAQAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaILA8U7KOeKiOMWB6J+H6SCNNzijV2dnseyla6wLmdKHhIkAAAAAAAAAAAAAAAACdFyy2RXJrOeHHw+3VIDSgsZQRzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqOgMYlRdyBwoDGJQXEAJyBwoDGJUXEAFSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYlRcQpBM="},{"b64Body":"ChEKCQig9v+rBhC/AhICGAIgAUI8GiISINCpW0CZq4GY6Oid1V95Z2poWVpPtG4bZK06BU/aiDsOQgUIgc7aA2oKSlVTVCBETyBJVHoDGJMX","b64Record":"CgcIFiIDGJUXEjDvOg2zlcc4gcO1EU4e8O74B+54h5gPzqIoufMnM/Cyc++6ooj+HyERM00sOs7wQsYaDAjc9v+rBhC0/cWAASIRCgkIoPb/qwYQvwISAhgCIAFCHQoDGJUXShYKFJ0XLLZFcms54cfD7dUgNKCxlBHMUgB6DAjc9v+rBhCz/cWAAQ=="},{"b64Body":"Cg8KCQig9v+rBhDBAhICGAISAhgDGI+ryAQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjrIBHAoWIhSdFyy2RXJrOeHHw+3VIDSgsZQRzBICGAI=","b64Record":"CiUIFiIDGJUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDGPAzUPEFuegkNovA11HUQU8ccJBP1Q/Mxx7Bulf7ATPuL6kv757KFXZy8LiAugOAaDAjc9v+rBhDTjuPkAiIPCgkIoPb/qwYQwQISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlITCgcKAhgCEKQTCggKAxiVFxCjEw=="},{"b64Body":"Cg8KCQih9v+rBhDDAhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxiUFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGJQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC7ABf8yFN6Tr5HNJITl5jjeBcxX/SOUWDjLRCweEObNaPhoXgPkrhRsS4f1PUTnhQaDAjd9v+rBhCjna2KASIPCgkIofb/qwYQwwISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpMFCgMYlBcigAIAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwEy7AIKAxiUFxKAAgAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAQAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaILA8U7KOeKiOMWB6J+H6SCNNzijV2dnseyla6wLmdKHhIkAAAAAAAAAAAAAAAACdFyy2RXJrOeHHw+3VIDSgsZQRzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqOgMYlhdyBwoDGJQXEANyBwoDGJYXEAFSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYlhcQpBM="},{"b64Body":"ChEKCQih9v+rBhDDAhICGAIgAUI8GiISINCpW0CZq4GY6Oid1V95Z2poWVpPtG4bZK06BU/aiDsOQgUIgc7aA2oKSlVTVCBETyBJVHoDGJMX","b64Record":"CgcIFiIDGJYXEjA7QUSGxsQQPK19tg3we9/e2BDDYl42WbpwEx0bEWNRpvdIXbGlZCOgaxqJVV4XcO4aDAjd9v+rBhCkna2KASIRCgkIofb/qwYQwwISAhgCIAFCHQoDGJYXShYKFJ0XLLZFcms54cfD7dUgNKCxlBHMUgB6DAjd9v+rBhCjna2KAQ=="},{"b64Body":"Cg8KCQih9v+rBhDZAhICGAISAhgDGMnrlhciAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjko8ChYiFJ0XLLZFcms54cfD7dUgNKCxlBHMGiISIPwQDCuH/cdpE2wVs92UfWnv4YWF2TB/fpO591LqJZje","b64Record":"CiUIFiIDGJYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBOvKk4IAwflnFbTXMOcPQF3qM1joR/aauOWBuUHMQgFDXwB1XTVdZeoGv+v2WdkesaDAjd9v+rBhDrq7WLAyIPCgkIofb/qwYQ2QISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQii9v+rBhDbAhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46kQgKAxiUFxCAkvQBIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIISIDGJQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD25FLNNmWNo30KCAw6bj/UAKCpvWd4ODvhLLUjGdS/f6Yldym38r5b16pwNkaM/F8aDAje9v+rBhCLnuevASIPCgkIovb/qwYQ2wISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDHpayFAToJGgIweCiBs/ABUhgKCgoCGAIQjcvYigIKCgoCGGIQjsvYigI="},{"b64Body":"Cg8KCQii9v+rBhDtAhICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoiChYiFJ0XLLZFcms54cfD7dUgNKCxlBHMEKCNBiIEc3vDyQ==","b64Record":"CiUIFiIDGJYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBEZ+c7DO7NFjP5nJ0rlz0Y+U483Bz2ApzXUfm2RwEnR30Cog9NmlA/SV3QRXEihbwaDAje9v+rBhCj/6yxAyIPCgkIovb/qwYQ7QISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYlhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIqCgkKAhgCEP+ttQUKCQoCGGIQgK61BQoICgMYlBcQpBMKCAoDGJYXEKMT"}]},"PayableCreate2WorksAsExpected":{"placeholderNum":2967,"encodedItems":[{"b64Body":"Cg8KCQin9v+rBhD/AhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjjxNqvBhCg5YONAhptCiISIJJHQyF5OP7+QmWS+DJoKK3lKI+qXpnMkR/et+ySAsIECiM6IQIN62iQqDad9FdonHUEX/BkIWTNg4gyA7yiuE49ItcceAoiEiBhGGW6XD97pa38AgP6ZLTFPvpbfyqMfEb+WYkcTv2RDiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJgXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCwqwhYNPiRn+5rpaBTQIiuoHTvzg6B4+ksQuQnyD4+lwugAazOwgOn6B4fwVXvMlAaDAjj9v+rBhCDo8GYAiIPCgkIp/b/qwYQ/wISAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQio9v+rBhCDAxICGAISAhgDGMeA0y4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBrAQKAxiYFyKkBDYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MGY0ODA2MTAwMWU2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTI2MDA0MzYxMDYwMWM1NzYwMDAzNTYwZTAxYzgwNjM2ZmIyOTc5ODE0NjAyMTU3NWI2MDAwODBmZDViNjAyNzYwMjk1NjViMDA1YjYwMDA2MDQwNTE4MDYwMjAwMTYwMzk5MDYwNjM1NjViNjAyMDgyMDE4MTAzODI1MjYwMWYxOTYwMWY4MjAxMTY2MDQwNTI1MDkwNTAzMzgxNTE2MDIwODMwMTM0ZjU2MDYwNTc2MDAwODBmZDViNTA1NjViNjA1MDgwNjA2ZjgzMzkwMTkwNTZmZTYwODA2MDQwNTI2MDNmODA2MDExNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwMDgwZmRmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYmJlN2M5MjdiZjEzMTMzNDBlYzY4YmI3NDNlMzAzNDEwMGQ3MWMyNzQ0MTQ4YTRmMzU1Y2RjYmRiMmM0OGEzNzY0NzM2ZjZjNjM0MzAwMDgwNzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDk0YWVhNzgwMjlmY2RkMGRiNDE4YTBkOTMxZDRhYTAxNjcyYjZhOWQ2Y2ZiOTBkYzI1MjUxZjA1YmQyNTNhYWE2NDczNmY2YzYzNDMwMDA4MDcwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwf75cLJfXKQrt8rNI/6zQKzg5a2PX3PbpYUYvDTgpRp6iakSlnGgOgRk8lxfKNnchGgsI5Pb/qwYQw7OdPSIPCgkIqPb/qwYQgwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQio9v+rBhCFAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJgXGiISIMPs4ScFLfswigaFbGOMeChqtVvRUbvNhKmIHN6/eEo4IMCEPUIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBZD8oMPhRkQ0GTBaWm4sULz4OomLzOxEEr7Lkw9FRt8dvj4Y4sDSmh1ZaJhnkavEoaDAjk9v+rBhDb28yiAiIPCgkIqPb/qwYQhQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA5oobQqkECgMYmRcS9AFggGBAUmAENhBgHFdgADVg4ByAY2+yl5gUYCFXW2AAgP1bYCdgKVZbAFtgAGBAUYBgIAFgOZBgY1ZbYCCCAYEDglJgHxlgH4IBFmBAUlCQUDOBUWAggwE09WBgV2AAgP1bUFZbYFCAYG+DOQGQVv5ggGBAUmA/gGARYAA5YADz/mCAYEBSYACA/f6iZGlwZnNYIhIgu+fJJ78TEzQOxou3Q+MDQQDXHCdEFIpPNVzcvbLEijdkc29sY0MACAcAM6JkaXBmc1giEiCUrqeAKfzdDbQYoNkx1KoBZytqnWz7kNwlJR8FvSU6qmRzb2xjQwAIBwAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA6jA6AxiZF0oWChQAAAAAAAAAAAAAAAAAAAAAAAALmXIHCgMYmRcQAVIWCgkKAhgCEP/LlTYKCQoCGGIQgMyVNg=="},{"b64Body":"Cg8KCQip9v+rBhCHAxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoRCgMYmRcQoI0GGGQiBG+yl5g=","b64Record":"CiUIFiIDGJkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAs39ewxLTe2wuMwbi1vD3Hrw8UbmFhUWkcvJ5TeJ44fIxIh6p0Eg36MpTTNZMhOt0aCwjl9v+rBhCrnYlHIg8KCQip9v+rBhCHAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6owIKAxiZFyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEOgMYmhdyBwoDGJkXEAJyBwoDGJoXEAFSIAoJCgIYAhDHr7UFCgkKAhhiEICutQUKCAoDGJoXEMgB"},{"b64Body":"ChEKCQip9v+rBhCHAxICGAIgAUI4GiISIMPs4ScFLfswigaFbGOMeChqtVvRUbvNhKmIHN6/eEo4QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJoXEjBeiBmObE+DMsjdcZS+GttksiuK46T724ZD7ZU/fMziYRZtcwLvuMnlZ7s3TrN8gEcaCwjl9v+rBhCsnYlHIhEKCQip9v+rBhCHAxICGAIgAUIdCgMYmhdKFgoU40ftwKIT5nEjiJW7VkusILP7UF5SAHoLCOX2/6sGEKudiUc="}]},"CanDeleteViaAlias":{"placeholderNum":2971,"encodedItems":[{"b64Body":"Cg8KCQit9v+rBhChAxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjpxNqvBhDops6iAhptCiISIKceT1/zEk7dxHdXcKOPe2+06EPn59yhZq48vN3F+t6pCiM6IQNLaapJEc/OSj1EAhbADD7Dy+mbSQXMCPVjmgfJpeZlVwoiEiCV+T8fogDts/8+qUNuI6g1E3xnhmSF0KyoxGY+LiP5iSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJwXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD4t23WbvShPavwMyu8zoChycxkWuUZIbE9un0WNNIYNDuU6MJoWUT1IIPkE4kyEssaDAjp9v+rBhDD/+ywAiIPCgkIrfb/qwYQoQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiu9v+rBhClAxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxicFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGNiZTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzY0MTVlOTUyMTQ2MTAwM2I1NzgwNjM3YTY5ZmY1NzE0NjEwMDU3NTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDIzNDU2NWI2MTAwNzM1NjViMDA1YjYxMDA3MTYwMDQ4MDM2MDM4MTAxOTA2MTAwNmM5MTkwNjEwMmJmNTY1YjYxMDE3NzU2NWIwMDViNjAwMDYwMmE5MDUwNjAwMDYwZmY2MGY4MWIzMDg0NjA0MDUxODA2MDIwMDE2MTAwOTI5MDYxMDFlYzU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwODU2MDQwNTE2MDIwMDE2MTAwYjg5MjkxOTA2MTAzYTQ1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDQwNTE2MDIwMDE2MTAwZTE5NDkzOTI5MTkwNjEwNDgyNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjgwNTE5MDYwMjAwMTIwNjAwMDFjOTA1MDYwMDA4MzgzNjA0MDUxNjEwMTBjOTA2MTAxZWM1NjViNjEwMTE2OTE5MDYxMDRkZjU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAxMzY1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDE3MTU3NjAwMDgwZmQ1YjUwNTA1MDUwNTY1YjYwMDA4MjkwNTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMwY2E3ZTY4NjgzNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwMWI1OTE5MDYxMDUwOTU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMWNmNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMWUzNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTY1YjYxMDc2NDgwNjEwNTI1ODMzOTAxOTA1NjViNjAwMDgwZmQ1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjExODE2MTAxZmU1NjViODExNDYxMDIxYzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDIyZTgxNjEwMjA4NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI0YTU3NjEwMjQ5NjEwMWY5NTY1YjViNjAwMDYxMDI1ODg0ODI4NTAxNjEwMjFmNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMjhjODI2MTAyNjE1NjViOTA1MDkxOTA1MDU2NWI2MTAyOWM4MTYxMDI4MTU2NWI4MTE0NjEwMmE3NTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMmI5ODE2MTAyOTM1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyZDY1NzYxMDJkNTYxMDFmOTU2NWI1YjYwMDA2MTAyZTQ4NTgyODYwMTYxMDJhYTU2NWI5MjUwNTA2MDIwNjEwMmY1ODU4Mjg2MDE2MTAyMWY1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMzMzNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMzE4NTY1YjgzODExMTE1NjEwMzQyNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTAzNTM4MjYxMDJmZjU2NWI2MTAzNWQ4MTg1NjEwMzBhNTY1YjkzNTA2MTAzNmQ4MTg1NjAyMDg2MDE2MTAzMTU1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDM5ZTYxMDM5OTgyNjEwMzc5NTY1YjYxMDM4MzU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwM2IwODI4NTYxMDM0ODU2NWI5MTUwNjEwM2JjODI4NDYxMDM4ZDU2NWI2MDIwODIwMTkxNTA4MTkwNTA5MzkyNTA1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDQxMzYxMDQwZTgyNjEwM2NjNTY1YjYxMDNmODU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0MzE4MjYxMDQxOTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0NDM4MjYxMDQyNjU2NWI5MDUwOTE5MDUwNTY1YjYxMDQ1YjYxMDQ1NjgyNjEwMjgxNTY1YjYxMDQzODU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDQ3YzYxMDQ3NzgyNjEwMWZlNTY1YjYxMDQ2MTU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwNDhlODI4NzYxMDQwMjU2NWI2MDAxODIwMTkxNTA2MTA0OWU4Mjg2NjEwNDRhNTY1YjYwMTQ4MjAxOTE1MDYxMDRhZTgyODU2MTA0NmI1NjViNjAyMDgyMDE5MTUwNjEwNGJlODI4NDYxMDQ2YjU2NWI2MDIwODIwMTkxNTA4MTkwNTA5NTk0NTA1MDUwNTA1MDU2NWI2MTA0ZDk4MTYxMDM3OTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNGY0NjAwMDgzMDE4NDYxMDRkMDU2NWI5MjkxNTA1MDU2NWI2MTA1MDM4MTYxMDFmZTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNTFlNjAwMDgzMDE4NDYxMDRmYTU2NWI5MjkxNTA1MDU2ZmU2MDgwNjA0MDUyNjA0MDUxNjEwNzY0MzgwMzgwNjEwNzY0ODMzOTgxODEwMTYwNDA1MjgxMDE5MDYxMDAyNTkxOTA2MTAwNmQ1NjViODA2MDAwODE5MDU1NTA1MDYxMDA5YTU2NWI2MDAwODBmZDViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAwNGE4MTYxMDAzNzU2NWI4MTE0NjEwMDU1NTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwMDY3ODE2MTAwNDE1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMDgzNTc2MTAwODI2MTAwMzI1NjViNWI2MDAwNjEwMDkxODQ4Mjg1MDE2MTAwNTg1NjViOTE1MDUwOTI5MTUwNTA1NjViNjEwNmJiODA2MTAwYTk2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMDIxYmY3MmMxNDYxMDA1MTU3ODA2MzAyZWQxOGRkMTQ2MTAwNmY1NzgwNjMwY2E3ZTY4NjE0NjEwMDhkNTc4MDYzNDUyMDE4OTYxNDYxMDBhOTU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGM1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAyNTc1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGNiNTY1YjYwNDA1MTYxMDA4NDkxOTA2MTAyNTc1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGE3NjAwNDgwMzYwMzgxMDE5MDYxMDBhMjkxOTA2MTAyYWQ1NjViNjEwMGQ0NTY1YjAwNWI2MTAwYzM2MDA0ODAzNjAzODEwMTkwNjEwMGJlOTE5MDYxMDJhZDU2NWI2MTAxNGU1NjViMDA1YjYwMDA1NDgxNTY1YjYwMDA4MDU0OTA1MDkwNTY1YjgwMzA2MDAwNTQ2MDQwNTE2MTAwZTU5MDYxMDIzMTU2NWI2MTAwZjA5MjkxOTA2MTAzMWI1NjViODE5MDYwNDA1MTgwOTEwMzkwNjAwMGY1OTA1MDgwMTU4MDE1NjEwMTEwNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA3ZjdmZjUxYzM3ZjdhNWQ3MjFlMzg4YzU2NDU2N2U5MGFlNDBiZmFhNzdhZGNlNjFmNzU1ZDUzMGE2ZGZmZjYzYzE2MDAwNTQ2MDQwNTE2MTAxNDM5MTkwNjEwMjU3NTY1YjYwNDA1MTgwOTEwMzkwYTE1MDU2NWI2MDAwODEzMDYwMDA1NDYwNDA1MTYxMDE2MTkwNjEwMjMxNTY1YjYxMDE2YzkyOTE5MDYxMDMxYjU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAxOGM1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzNzM3YmMzYzk2MDQwNTE4MTYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMWQ3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMWViNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwODEzMDYwMDA1NDYwNDA1MTYxMDIwMDkwNjEwMjMxNTY1YjYxMDIwYjkyOTE5MDYxMDMxYjU2NWI4MTkwNjA0MDUxODA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwsv9EsVJhnLPu1WG+o4mVzaXJlFigOMw1lFfK55MwWO3P8pgPiJGRDDGgHHKYcDfXGgsI6vb/qwYQy4buVSIPCgkIrvb/qwYQpQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiu9v+rBhCrAxICGAISAhgDGOuF5jciAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBxBMSAxicFyK8EzkxMDM5MDYwMDBmNTkwNTA4MDE1ODAxNTYxMDIyYjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDU2NWI2MTAzNDE4MDYxMDM0NTgzMzkwMTkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjUxODE2MTAyM2U1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDI2YzYwMDA4MzAxODQ2MTAyNDg1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjhhODE2MTAyNzc1NjViODExNDYxMDI5NTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJhNzgxNjEwMjgxNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDJjMzU3NjEwMmMyNjEwMjcyNTY1YjViNjAwMDYxMDJkMTg0ODI4NTAxNjEwMjk4NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMzA1ODI2MTAyZGE1NjViOTA1MDkxOTA1MDU2NWI2MTAzMTU4MTYxMDJmYTU2NWI4MjUyNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwMzMwNjAwMDgzMDE4NTYxMDMwYzU2NWI2MTAzM2Q2MDIwODMwMTg0NjEwMjQ4NTY1YjkzOTI1MDUwNTA1NmZlNjA4MDYwNDA1MjYwNDA1MTYxMDM0MTM4MDM4MDYxMDM0MTgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMjU5MTkwNjEwMTBjNTY1YjgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxODE5MDU1NTA1MDUwNjEwMTRjNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDBhMzgyNjEwMDc4NTY1YjkwNTA5MTkwNTA1NjViNjEwMGIzODE2MTAwOTg1NjViODExNDYxMDBiZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDBkMDgxNjEwMGFhNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMGU5ODE2MTAwZDY1NjViODExNDYxMDBmNDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDEwNjgxNjEwMGUwNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMTIzNTc2MTAxMjI2MTAwNzM1NjViNWI2MDAwNjEwMTMxODU4Mjg2MDE2MTAwYzE1NjViOTI1MDUwNjAyMDYxMDE0Mjg1ODI4NjAxNjEwMGY3NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMWU2ODA2MTAxNWI2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA1MTU3ODA2MzczN2JjM2M5MTQ2MTAwNmY1NzgwNjM4ZGE1Y2I1YjE0NjEwMDc5NTc4MDYzYzI5ODU1NzgxNDYxMDA5NzU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGI1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAxMzk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGJkNTY1YjAwNWI2MTAwODE2MTAwZjY1NjViNjA0MDUxNjEwMDhlOTE5MDYxMDE5NTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwOWY2MTAxMWE1NjViNjA0MDUxNjEwMGFjOTE5MDYxMDEzOTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNDc5MDUwOTA1NjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1NjViNjAwMTU0ODE1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAxMzM4MTYxMDEyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTRlNjAwMDgzMDE4NDYxMDEyYTU2NWI5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDE3ZjgyNjEwMTU0NTY1YjkwNTA5MTkwNTA1NjViNjEwMThmODE2MTAxNzQ1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDFhYTYwMDA4MzAxODQ2MTAxODY1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA2ODg1ZTllYTc3NjQyMGZkNjFiOTU3NGE3MmU1ZjA1ZGFlOTQ5MWE5OTNjNjFkMDgwZmZkZmE3NzlhYWFhMTRkNjQ3MzZmNmM2MzQzMDAwODBjMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwMGVjYzhmOWQ3OGQxMTE3ZGM4YjAyZGI0YzNlNGIxNmZlOTg3YmQwZmY0MGM2YzdmNzIwZWE5ZGNlZWY3NTg4YzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDc1ZjE0MTZjZDRjZjg3OWZjOWExNjkwOTgyYjg1ZTVkNjI0NTg2MTlmMWRmN2UyY2EyNmNiMTFlN2Y3M2ViMTQ2NDczNmY2YzYzNDMwMDA4MGMwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwE5zPvNFLqYpytfNTw6G7/huC6K9UgJJolWoG9oLjWokdEbSNjMnlzmYWacJLOeMrGgwI6vb/qwYQ86TquQIiDwoJCK72/6sGEKsDEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiv9v+rBhCtAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJwXGiISIFnyMIZ+TDwRqymg8djMtc67H6K939DfgmmsuKsJXeZeIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJ0XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjANIW5+4wHF/LnsYBa8z+B9JVzBTkrY+/0zbmF7Evip4EHYRfg+CtX3VpPyicKgKG8aCwjr9v+rBhDznoxfIg8KCQiv9v+rBhCtAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZC8xsKAxidFxK+GWCAYEBSNIAVYQAQV2AAgP1bUGAENhBhADZXYAA1YOAcgGNkFelSFGEAO1eAY3pp/1cUYQBXV1tgAID9W2EAVWAEgDYDgQGQYQBQkZBhAjRWW2EAc1ZbAFthAHFgBIA2A4EBkGEAbJGQYQK/VlthAXdWWwBbYABgKpBQYABg/2D4GzCEYEBRgGAgAWEAkpBhAexWW2AgggGBA4JSYB8ZYB+CARZgQFJQhWBAUWAgAWEAuJKRkGEDpFZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGBAUWAgAWEA4ZSTkpGQYQSCVltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgYAAckFBgAIODYEBRYQEMkGEB7FZbYQEWkZBhBN9WW4GQYEBRgJEDkGAA9ZBQgBWAFWEBNlc9YACAPj1gAP1bUJBQgXP//////////////////////////xaBc///////////////////////////FhRhAXFXYACA/VtQUFBQVltgAIKQUIBz//////////////////////////8WYwyn5oaDYEBRgmP/////FmDgG4FSYAQBYQG1kZBhBQlWW2AAYEBRgIMDgWAAh4A7FYAVYQHPV2AAgP1bUFrxFYAVYQHjVz1gAIA+PWAA/VtQUFBQUFBQVlthB2SAYQUlgzkBkFZbYACA/VtgAIGQUJGQUFZbYQIRgWEB/lZbgRRhAhxXYACA/VtQVltgAIE1kFBhAi6BYQIIVluSkVBQVltgAGAggoQDEhVhAkpXYQJJYQH5VltbYABhAliEgoUBYQIfVluRUFCSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQKMgmECYVZbkFCRkFBWW2ECnIFhAoFWW4EUYQKnV2AAgP1bUFZbYACBNZBQYQK5gWECk1ZbkpFQUFZbYACAYECDhQMSFWEC1ldhAtVhAflWW1tgAGEC5IWChgFhAqpWW5JQUGAgYQL1hYKGAWECH1ZbkVBQklCSkFBWW2AAgVGQUJGQUFZbYACBkFCSkVBQVltgAFuDgRAVYQMzV4CCAVGBhAFSYCCBAZBQYQMYVluDgREVYQNCV2AAhIQBUltQUFBQVltgAGEDU4JhAv9WW2EDXYGFYQMKVluTUGEDbYGFYCCGAWEDFVZbgIQBkVBQkpFQUFZbYACBkFCRkFBWW2AAgZBQkZBQVlthA55hA5mCYQN5VlthA4NWW4JSUFBWW2AAYQOwgoVhA0hWW5FQYQO8goRhA41WW2AgggGRUIGQUJOSUFBQVltgAH//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIWkFCRkFBWW2AAgZBQkZBQVlthBBNhBA6CYQPMVlthA/hWW4JSUFBWW2AAgWBgG5BQkZBQVltgAGEEMYJhBBlWW5BQkZBQVltgAGEEQ4JhBCZWW5BQkZBQVlthBFthBFaCYQKBVlthBDhWW4JSUFBWW2AAgZBQkZBQVlthBHxhBHeCYQH+VlthBGFWW4JSUFBWW2AAYQSOgodhBAJWW2ABggGRUGEEnoKGYQRKVltgFIIBkVBhBK6ChWEEa1ZbYCCCAZFQYQS+goRhBGtWW2AgggGRUIGQUJWUUFBQUFBWW2EE2YFhA3lWW4JSUFBWW2AAYCCCAZBQYQT0YACDAYRhBNBWW5KRUFBWW2EFA4FhAf5WW4JSUFBWW2AAYCCCAZBQYQUeYACDAYRhBPpWW5KRUFBW/mCAYEBSYEBRYQdkOAOAYQdkgzmBgQFgQFKBAZBhACWRkGEAbVZbgGAAgZBVUFBhAJpWW2AAgP1bYACBkFCRkFBWW2EASoFhADdWW4EUYQBVV2AAgP1bUFZbYACBUZBQYQBngWEAQVZbkpFQUFZbYABgIIKEAxIVYQCDV2EAgmEAMlZbW2AAYQCRhIKFAWEAWFZbkVBQkpFQUFZbYQa7gGEAqWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjAhv3LBRhAFFXgGMC7RjdFGEAb1eAYwyn5oYUYQCNV4BjRSAYlhRhAKlXW2AAgP1bYQBZYQDFVltgQFFhAGaRkGECV1ZbYEBRgJEDkPNbYQB3YQDLVltgQFFhAISRkGECV1ZbYEBRgJEDkPNbYQCnYASANgOBAZBhAKKRkGECrVZbYQDUVlsAW2EAw2AEgDYDgQGQYQC+kZBhAq1WW2EBTlZbAFtgAFSBVltgAIBUkFCQVluAMGAAVGBAUWEA5ZBhAjFWW2EA8JKRkGEDG1ZbgZBgQFGAkQOQYAD1kFCAFYAVYQEQVz1gAIA+PWAA/VtQUH9/9Rw396XXIeOIxWRWfpCuQL+qd63OYfdV1TCm3/9jwWAAVGBAUWEBQ5GQYQJXVltgQFGAkQOQoVBWW2AAgTBgAFRgQFFhAWGQYQIxVlthAWySkZBhAxtWW4GQYEBRgJEDkGAA9ZBQgBWAFWEBjFc9YACAPj1gAP1bUJBQgHP//////////////////////////xZjc3vDyWBAUYFj/////xZg4BuBUmAEAWAAYEBRgIMDgWAAh4A7FYAVYQHXV2AAgP1bUFrxFYAVYQHrVz1gAIA+PWAA/VtQUFBQgTBgAFRgQFFhAgCQYQIxVlthAguSkZBhAxtWW4GQYEBRgJEDkGAA9ZBQgBWAFWECK1c9YACAPj1gAP1bUFBQUFZbYQNBgGEDRYM5AZBWW2AAgZBQkZBQVlthAlGBYQI+VluCUlBQVltgAGAgggGQUGECbGAAgwGEYQJIVluSkVBQVltgAID9W2AAgZBQkZBQVlthAoqBYQJ3VluBFGEClVdgAID9W1BWW2AAgTWQUGECp4FhAoFWW5KRUFBWW2AAYCCChAMSFWECw1dhAsJhAnJWW1tgAGEC0YSChQFhAphWW5FQUJKRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAwWCYQLaVluQUJGQUFZbYQMVgWEC+lZbglJQUFZbYABgQIIBkFBhAzBgAIMBhWEDDFZbYQM9YCCDAYRhAkhWW5OSUFBQVv5ggGBAUmBAUWEDQTgDgGEDQYM5gYEBYEBSgQGQYQAlkZBhAQxWW4FgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAYAGBkFVQUFBhAUxWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEAo4JhAHhWW5BQkZBQVlthALOBYQCYVluBFGEAvldgAID9W1BWW2AAgVGQUGEA0IFhAKpWW5KRUFBWW2AAgZBQkZBQVlthAOmBYQDWVluBFGEA9FdgAID9W1BWW2AAgVGQUGEBBoFhAOBWW5KRUFBWW2AAgGBAg4UDEhVhASNXYQEiYQBzVltbYABhATGFgoYBYQDBVluSUFBgIGEBQoWChgFhAPdWW5FQUJJQkpBQVlthAeaAYQFbYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMSBl/gFGEAUVeAY3N7w8kUYQBvV4BjjaXLWxRhAHlXgGPCmFV4FGEAl1dbYACA/VthAFlhALVWW2BAUWEAZpGQYQE5VltgQFGAkQOQ81thAHdhAL1WWwBbYQCBYQD2VltgQFFhAI6RkGEBlVZbYEBRgJEDkPNbYQCfYQEaVltgQFFhAKyRkGEBOVZbYEBRgJEDkPNbYABHkFCQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1tgAIBUkGEBAAqQBHP//////////////////////////xaBVltgAVSBVltgAIGQUJGQUFZbYQEzgWEBIFZbglJQUFZbYABgIIIBkFBhAU5gAIMBhGEBKlZbkpFQUFZbYABz//////////////////////////+CFpBQkZBQVltgAGEBf4JhAVRWW5BQkZBQVlthAY+BYQF0VluCUlBQVltgAGAgggGQUGEBqmAAgwGEYQGGVluSkVBQVv6iZGlwZnNYIhIgaIXp6ndkIP1huVdKcuXwXa6UkamTxh0ID/36d5qqoU1kc29sY0MACAwAM6JkaXBmc1giEiAOzI+deNERfciwLbTD5LFv6Ye9D/QMbH9yDqnc7vdYjGRzb2xjQwAIDAAzomRpcGZzWCISIHXxQWzUz4efyaFpCYK4Xl1iRYYZ8d9+LKJssR5/c+sUZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJ0XShYKFAAAAAAAAAAAAAAAAAAAAAAAAAudcgcKAxidFxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQiv9v+rBhCvAxICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGJ0XEICS9AEiJGQV6VKqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGJ0XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA9D3bBKHLj/5ktbBgL0dy4RlYiDtYMdWdKjCGVgXYmovLTkCRvPaCUbZ0qhsp9dfgaDAjr9v+rBhDzwr/EAiIPCgkIr/b/qwYQrwMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYnRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxieF3IHCgMYnRcQAnIHCgMYnhcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQiv9v+rBhCvAxICGAIgAUI4GiISIFnyMIZ+TDwRqymg8djMtc67H6K939DfgmmsuKsJXeZeQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJ4XEjC0BVmIXODPdRqP93ze/nD7mwnN8KOLVRWMdWLDQDrQlg8j3UtfgBF53u6nzZadDA8aDAjr9v+rBhD0wr/EAiIRCgkIr/b/qwYQrwMSAhgCIAFCHQoDGJ4XShYKFJVAskslJk5L0aJWM+umu2h0vnsrUgB6DAjr9v+rBhDzwr/EAg=="},{"b64Body":"Cg8KCQiw9v+rBhC1AxICGAISAhgDGID/2kMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpCChYiFJVAskslJk5L0aJWM+umu2h0vnsrEICJeiIkRSAYlqq7zN3uMwARqrvM3e4zABGqu8zd7jMAEaq7zN3uMwAR","b64Record":"CiUIISIDGJ4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAO9I64Md2ppyjcIFMXfB6qjZO76zE9f+vxe1FactYGqpBMwHKKZWrSaOJ2qIJYSYwaCwjs9v+rBhDLsKdoIg8KCQiw9v+rBhC1AxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMKah3UI6CBoCMHgoqqZ4UhgKCgoCGAIQy8K6hQEKCgoCGGIQzMK6hQE="},{"b64Body":"Cg8KCQiw9v+rBhC7AxICGAISAhgDGKHd2hAiAgh4MiJUaGF0J3Mgd2h5IHlvdSBhbHdheXMgbGVhdmUgYSBub3RlShgKFiIUlUCySyUmTkvRolYz66a7aHS+eys=","b64Record":"CiUIFiIDGJ4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBSdd9a7rkMiZQtSVPglAhNbUmUWq07hHqNtiWw0kUCXavdyeohwM+MJGVfEFe9IxUaDAjs9v+rBhCD867NAiIPCgkIsPb/qwYQuwMSAhgCKiJUaGF0J3Mgd2h5IHlvdSBhbHdheXMgbGVhdmUgYSBub3RlUgA="},{"b64Body":"Cg8KCQix9v+rBhDBAxICGAISAhgDGI+ryAQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjrIBMAoWIhSVQLJLJSZOS9GiVjPrprtodL57KxoWIhQAAAAAAAAAAAAAAAAAAAAAAAALng==","b64Record":"CiAITSocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw165Z1Gnnnh5Z7niKVJvg+LDMv8RC57D6R5bBkbLgL2QF2BktNcFrk05S6T92BhUdGgsI7fb/qwYQm4SdciIPCgkIsfb/qwYQwQMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQix9v+rBhDDAxICGAISAhgDGI+ryAQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjrIBMAoWIhQAAAAAAAAAAAAAAAAAAAAAAAALnhoWIhSVQLJLJSZOS9GiVjPrprtodL57Kw==","b64Record":"CiAITSocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwL2NXdQ0FSCHKIKnqdTswV0Li+efOBiIQTJjvlU5/U59Gup5ZDrJsUsL8CZK9BWplGgwI7fb/qwYQ+9ji1gIiDwoJCLH2/6sGEMMDEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiy9v+rBhDLAxICGAISAhgDGI+ryAQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjrIBHAoWIhSVQLJLJSZOS9GiVjPrprtodL57KxICGGI=","b64Record":"CiUIFiIDGJ4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjACa13uHBV4RGHticBO+jIsYLGfbF/fq9u1ZvyXLLTxsQRXz+9bB5GvvSQHSh25gDAaCwju9v+rBhDj6Kx7Ig8KCQiy9v+rBhDLAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="}]},"CannotSelfDestructToMirrorAddress":{"placeholderNum":2975,"encodedItems":[{"b64Body":"Cg8KCQi29v+rBhDnAxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjyxNqvBhCokKvFAhptCiISIAVdMxn4EtLe+WLQyTCJvIhJeo7A3rE+d1xr+Mw1wGgwCiM6IQJtS7F6SXN17rljRv1uotbNUnFiW73XOnrDWCmAMFGYcwoiEiDsJMmxMmqL9Y584uybIDqfrDlzE+FNqE/V57w7BtyvcyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKAXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDjVf+I0LJG/gCpfD56hgcx7bdwmElj5nnIYd9YzXgFi9/N/pMuS1AGBRIHga8kWLsaDAjy9v+rBhCL+o/NAiIPCgkItvb/qwYQ5wMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi39v+rBhDrAxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxigFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDg1MDgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwMzQ1NzYwMDAzNTYwZTAxYzgwNjMzODI3MmQzOTE0NjEwMDM5NTc4MDYzYTE1MDQyNzUxNDYxMDA1NTU3ODA2M2Q0NjYxMGMzMTQ2MTAwNzE1NzViNjAwMDgwZmQ1YjYxMDA1MzYwMDQ4MDM2MDM4MTAxOTA2MTAwNGU5MTkwNjEwMzFlNTY1YjYxMDA4ZDU2NWIwMDViNjEwMDZmNjAwNDgwMzYwMzgxMDE5MDYxMDA2YTkxOTA2MTAzMWU1NjViNjEwMTgwNTY1YjAwNWI2MTAwOGI2MDA0ODAzNjAzODEwMTkwNjEwMDg2OTE5MDYxMDMxZTU2NWI2MTAyNWY1NjViMDA1YjYwMDA2MGZmNjBmODFiMzA4MzYwNDA1MTgwNjAyMDAxNjEwMGE2OTA2MTAyY2M1NjViNjAyMDgyMDE4MTAzODI1MjYwMWYxOTYwMWY4MjAxMTY2MDQwNTI1MDYwNDA1MTYwMjAwMTYxMDBjYTkxOTA2MTAzYzU1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDQwNTE2MDIwMDE2MTAwZjM5NDkzOTI5MTkwNjEwNGM0NTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjgwNTE5MDYwMjAwMTIwNjAwMDFjOTA1MDYwMDA4MjYwNjQ2MDQwNTE2MTAxMWY5MDYxMDJjYzU2NWI4MjkwNjA0MDUxODA5MTAzOTA4M2Y1OTA1MDkwNTA4MDE1ODAxNTYxMDE0MDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjE0NjEwMTdiNTc2MDAwODBmZDViNTA1MDUwNTY1YjMwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2Q0NjYxMGMzNjA2NDgzNjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwMWJiOTE5MDYxMDUyMTU2NWI2MDAwNjA0MDUxODA4MzAzODE4NTg4ODAzYjE1ODAxNTYxMDFkNDU3NjAwMDgwZmQ1YjUwNWFmMTkzNTA1MDUwNTA4MDE1NjEwMWU2NTc1MDYwMDE1YjYxMDIyOTU3NjEwMWYyNjEwNTQ5NTY1YjgwNjMwOGMzNzlhMDE0MTU2MTAyMTg1NzUwNjEwMjA3NjEwNWRjNTY1YjgwNjEwMjEyNTc1MDYxMDIxYTU2NWI1MDYxMDIyNDU2NWI1MDViM2Q2MDAwODAzZTNkNjAwMGZkNWI2MTAyMmE1NjViNWI4MDYwNjQ2MDQwNTE2MTAyMzk5MDYxMDJjYzU2NWI4MjkwNjA0MDUxODA5MTAzOTA4M2Y1OTA1MDkwNTA4MDE1ODAxNTYxMDI1YTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1NjViODA2MDY0NjA0MDUxNjEwMjZlOTA2MTAyY2M1NjViODI5MDYwNDA1MTgwOTEwMzkwODNmNTkwNTA5MDUwODAxNTgwMTU2MTAyOGY1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAyYzM5MDYxMDZjZjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI2MTAxMmI4MDYxMDZmMDgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMmZiODE2MTAyZTg1NjViODExNDYxMDMwNjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDMxODgxNjEwMmYyNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDMzNDU3NjEwMzMzNjEwMmUzNTY1YjViNjAwMDYxMDM0Mjg0ODI4NTAxNjEwMzA5NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDViODM4MTEwMTU2MTAzN2Y1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAzNjQ1NjViODM4MTExMTU2MTAzOGU1NzYwMDA4NDg0MDE1MjViNTA1MDUwNTA1NjViNjAwMDYxMDM5ZjgyNjEwMzRiNTY1YjYxMDNhOTgxODU2MTAzNTY1NjViOTM1MDYxMDNiOTgxODU2MDIwODYwMTYxMDM2MTU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwM2QxODI4NDYxMDM5NDU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDdmZmYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgyMTY5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNDIzNjEwNDFlODI2MTAzZGM1NjViNjEwNDA4NTY1YjgyNTI1MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwNDU0ODI2MTA0Mjk1NjViOTA1MDkxOTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0NzM4MjYxMDQ1YjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0ODU4MjYxMDQ2ODU2NWI5MDUwOTE5MDUwNTY1YjYxMDQ5ZDYxMDQ5ODgyNjEwNDQ5NTY1YjYxMDQ3YTU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDRiZTYxMDRiOTgyNjEwMmU4NTY1YjYxMDRhMzU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwNGQwODI4NzYxMDQxMjU2NWI2MDAxODIwMTkxNTA2MTA0ZTA4Mjg2NjEwNDhjNTY1YjYwMTQ4MjAxOTE1MDYxMDRmMDgyODU2MTA0YWQ1NjViNjAyMDgyMDE5MTUwNjEwNTAwODI4NDYxMDRhZDU2NWI2MDIwODIwMTkxNTA4MTkwNTA5NTk0NTA1MDUwNTA1MDU2NWI2MTA1MWI4MTYxMDJlODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNTM2NjAwMDgzMDE4NDYxMDUxMjU2NWI5MjkxNTA1MDU2NWI2MDAwODE2MGUwMWM5MDUwOTE5MDUwNTY1YjYwMDA2MDAzM2QxMTE1NjEwNTY4NTc2MDA0NjAwMDgwM2U2MTA1NjU2MDAwNTE2MTA1M2M1NjViOTA1MDViOTA1NjViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTA1YjQ4MjYxMDU2YjU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTA1ZDM1NzYxMDVkMjYxMDU3YzU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MDQ0M2QxMDE1NjEwNWVjNTc2MTA2NmY1NjViNjEwNWY0NjEwMmQ5NTY1YjYwMDQzZDAzNjAwNDgyM2U4MDUxM2Q2MDI0ODIwMTExNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTA2MWM1NzUwNTA2MTA2NmY1NjViODA4MjAxODA1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDYzYTU3NTA1MDUwNTA2MTA2NmY1NjViODA2MDIwODMwMTAxNjAwNDNkMDM4NTAxODExMTE1NjEwNjU3NTc1MDUwNTA1MDUwNjEwNjZmNTY1YjYxMDY2NjgyNjAyMDAxODUwMTg2NjEwNWFiNTY1YjgyOTU1MDUwNTA1MDUwNTA1YjkwNTY1YjYwMDA4MjgyNTI2MDIwODIwMTkwNTA5MjkxNTA1MDU2NWI3ZjRlNGY1MDQ1MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMDZiOTYwMDQ4MzYxMDY3MjU2NWI5MTUwNjEwNmM0ODI2MTA2ODM1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwNmU4ODE2MTA2YWM1NjViOTA1MDkxOTA1MDU2ZmU2MDgwNjA0MDUyNjEwMTE4ODA2MTAwMTM2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDA0MzYxMDYwMjg1NzYwMDAzNTYwZTAxYzgwNjNhODkwMDBjODE0NjAyZDU3NWI2MDAwODBmZDViNjA0MzYwMDQ4MDM2MDM4MTAxOTA2MDNmOTE5MDYwYmE1NjViNjA0NTU2NWIwMDViODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNmZmNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MDhjODI2MDYzNTY1YjkwNTA5MTkwNTA1NjViNjA5YTgxNjA4MzU2NWI4MTE0NjBhNDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYwYjQ4MTYwOTM1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjBjZDU3NjBjYzYwNWU1NjViNWI2MDAwNjBkOTg0ODI4NTAxNjBhNzU2NWI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwIBE5btLkqlJuhlWiwvbHghmNsDjnzboPzKHJ4qBsmyKqIPfqh6X1Abqx8sYNM/jBGgsI8/b/qwYQq9zwcSIPCgkIt/b/qwYQ6wMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi39v+rBhDxAxICGAISAhgDGLrR7C0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB6AESAxigFyLgATkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwNTA3NDA2MTAyMmUwZTY2ZTc0NzM1ZTk0NWY0ODU1ODU0ZDg5OTBiYWI0N2ZiZmM2MmI4NzE3ZjZjNzMyNDMxMzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMGVmYjAzOGVhMGM0NmMxNWFlMGEyOTg1MDZiZjgyYTQ5MzQ2YWEyYTRjNWNiZTYyMjJiNDhlMjkxYjFkMjg2ZWE2NDczNmY2YzYzNDMwMDA4MGMwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwHAXt1Wyo+3p1EU4l+NnZQZr3uFCnnuS8sI+ALvla0bBfHe4D76Dhx3mzKBXcfg/ZGgwI8/b/qwYQi4fE1gIiDwoJCLf2/6sGEPEDEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQi49v+rBhDzAxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKAXGiISIOb1RgxT5eFnnGiGi3cmblabvwLVU4/3kbG9XdQ811LdIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKEXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCcermzXecKvm+o1dqBdEknt+L/jGu61rbj6F43aGPLY4mGpE5Gbw56y4cpr/IegvgaCwj09v+rBhCT0657Ig8KCQi49v+rBhDzAxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZChRMKAxihFxLQEGCAYEBSYAQ2EGEANFdgADVg4ByAYzgnLTkUYQA5V4BjoVBCdRRhAFVXgGPUZhDDFGEAcVdbYACA/VthAFNgBIA2A4EBkGEATpGQYQMeVlthAI1WWwBbYQBvYASANgOBAZBhAGqRkGEDHlZbYQGAVlsAW2EAi2AEgDYDgQGQYQCGkZBhAx5WW2ECX1ZbAFtgAGD/YPgbMINgQFGAYCABYQCmkGECzFZbYCCCAYEDglJgHxlgH4IBFmBAUlBgQFFgIAFhAMqRkGEDxVZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGBAUWAgAWEA85STkpGQYQTEVltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgYAAckFBgAIJgZGBAUWEBH5BhAsxWW4KQYEBRgJEDkIP1kFCQUIAVgBVhAUBXPWAAgD49YAD9W1CQUIFz//////////////////////////8WgXP//////////////////////////xYUYQF7V2AAgP1bUFBQVlswc///////////////////////////FmPUZhDDYGSDYEBRg2P/////FmDgG4FSYAQBYQG7kZBhBSFWW2AAYEBRgIMDgYWIgDsVgBVhAdRXYACA/VtQWvGTUFBQUIAVYQHmV1BgAVthAilXYQHyYQVJVluAYwjDeaAUFWECGFdQYQIHYQXcVluAYQISV1BhAhpWW1BhAiRWW1BbPWAAgD49YAD9W2ECKlZbW4BgZGBAUWECOZBhAsxWW4KQYEBRgJEDkIP1kFCQUIAVgBVhAlpXPWAAgD49YAD9W1BQUFZbgGBkYEBRYQJukGECzFZbgpBgQFGAkQOQg/WQUJBQgBWAFWECj1c9YACAPj1gAP1bUFBgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWECw5BhBs9WW2BAUYCRA5D9W2EBK4BhBvCDOQGQVltgAGBAUZBQkFZbYACA/VtgAIGQUJGQUFZbYQL7gWEC6FZbgRRhAwZXYACA/VtQVltgAIE1kFBhAxiBYQLyVluSkVBQVltgAGAggoQDEhVhAzRXYQMzYQLjVltbYABhA0KEgoUBYQMJVluRUFCSkVBQVltgAIFRkFCRkFBWW2AAgZBQkpFQUFZbYABbg4EQFWEDf1eAggFRgYQBUmAggQGQUGEDZFZbg4ERFWEDjldgAISEAVJbUFBQUFZbYABhA5+CYQNLVlthA6mBhWEDVlZbk1BhA7mBhWAghgFhA2FWW4CEAZFQUJKRUFBWW2AAYQPRgoRhA5RWW5FQgZBQkpFQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQQjYQQegmED3FZbYQQIVluCUlBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQRUgmEEKVZbkFCRkFBWW2AAgWBgG5BQkZBQVltgAGEEc4JhBFtWW5BQkZBQVltgAGEEhYJhBGhWW5BQkZBQVlthBJ1hBJiCYQRJVlthBHpWW4JSUFBWW2AAgZBQkZBQVlthBL5hBLmCYQLoVlthBKNWW4JSUFBWW2AAYQTQgodhBBJWW2ABggGRUGEE4IKGYQSMVltgFIIBkVBhBPCChWEErVZbYCCCAZFQYQUAgoRhBK1WW2AgggGRUIGQUJWUUFBQUFBWW2EFG4FhAuhWW4JSUFBWW2AAYCCCAZBQYQU2YACDAYRhBRJWW5KRUFBWW2AAgWDgHJBQkZBQVltgAGADPREVYQVoV2AEYACAPmEFZWAAUWEFPFZbkFBbkFZbYABgHxlgH4MBFpBQkZBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EFtIJhBWtWW4EBgYEQZ///////////ghEXFWEF01dhBdJhBXxWW1uAYEBSUFBQVltgAGBEPRAVYQXsV2EGb1ZbYQX0YQLZVltgBD0DYASCPoBRPWAkggERZ///////////ghEXFWEGHFdQUGEGb1ZbgIIBgFFn//////////+BERVhBjpXUFBQUGEGb1ZbgGAggwEBYAQ9A4UBgREVYQZXV1BQUFBQYQZvVlthBmaCYCABhQGGYQWrVluClVBQUFBQUFuQVltgAIKCUmAgggGQUJKRUFBWW39OT1BFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAggFSUFZbYABhBrlgBINhBnJWW5FQYQbEgmEGg1ZbYCCCAZBQkZBQVltgAGAgggGQUIGBA2AAgwFSYQbogWEGrFZbkFCRkFBW/mCAYEBSYQEYgGEAE2AAOWAA8/5ggGBAUjSAFWAPV2AAgP1bUGAENhBgKFdgADVg4ByAY6iQAMgUYC1XW2AAgP1bYENgBIA2A4EBkGA/kZBgulZbYEVWWwBbgHP//////////////////////////xb/W2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGCMgmBjVluQUJGQUFZbYJqBYINWW4EUYKRXYACA/VtQVltgAIE1kFBgtIFgk1ZbkpFQUFZbYABgIIKEAxIVYM1XYMxgXlZbW2AAYNmEgoUBYKdWW5FQUJKRUFBW/qJkaXBmc1giEiBQdAYQIuDmbnRzXpRfSFWFTYmQurR/v8Yrhxf2xzJDE2Rzb2xjQwAIDAAzomRpcGZzWCISIO+wOOoMRsFa4KKYUGv4Kkk0aqKkxcvmIitI4pGx0obqZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGKEXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAuhcgcKAxihFxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQi49v+rBhD1AxICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MwoDGKEXEICS9AEY6AciJDgnLTmqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGKEXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAfqFKnf59hc+Awncge0YpKLt8CKc/xbF3S294FR9EiMqc8dF3qhafU7GwJ6vLSffcaDAj09v+rBhCDk5XgAiIPCgkIuPb/qwYQ9QMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYoRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxiiF3IHCgMYoRcQAnIHCgMYohcQAVIsCgoKAhgCEM+/1tgBCgoKAhhiEICw1tgBCggKAxihFxCIDgoICgMYohcQyAE="},{"b64Body":"ChEKCQi49v+rBhD1AxICGAIgAUI4GiISIOb1RgxT5eFnnGiGi3cmblabvwLVU4/3kbG9XdQ811LdQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKIXEjBhYWEJeHZMB4iwm6AzorAFx2KUBnHmbFkjepY/BOKWyxVxQj2+x26ZUzzJqW8DWmUaDAj09v+rBhCEk5XgAiIRCgkIuPb/qwYQ9QMSAhgCIAFCHQoDGKIXShYKFOOZUMyOLX9GyEcdQDMFkUW7/SigUgB6DAj09v+rBhCDk5XgAg=="},{"b64Body":"Cg8KCQi59v+rBhD7AxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpCChYiFOOZUMyOLX9GyEcdQDMFkUW7/SigEKCNBiIkqJAAyAAAAAAAAAAAAAAAAOOZUMyOLX9GyEcdQDMFkUW7/Sig","b64Record":"CiUITSIDGKIXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAROjb/SqRXn/R34XwZhN4w+DCBzi5jHcuUZPlAmj+jof3RIYDnQBE23hVhA0q0vAcaDAj19v+rBhCD2J6FASIPCgkIufb/qwYQ+wMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOhsaFVNFTEZfREVTVFJVQ1RfVE9fU0VMRiigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQi59v+rBhD9AxICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjpCChYiFOOZUMyOLX9GyEcdQDMFkUW7/SigEKCNBiIkqJAAyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAui","b64Record":"CiUIHSIDGKIXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBsS2jWS3fmuH/bqBTYj6c3RzMyfTbycfUNIsGpyccBbYxNXf8TDyNUSdszRLpzuT0aDAj19v+rBhCzh8nqAiIPCgkIufb/qwYQ/QMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="},{"b64Body":"Cg8KCQi69v+rBhD/AxICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MwoDGKEXEICS9AEY6AciJKFQQnWqu8zd7ogAEaq7zN3uiAARqrvM3e6IABGqu8zd7ogAEQ==","b64Record":"CiUIFiIDGKEXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDzyJMbEVnF5pt7i9A7gyNHOzG18GH0Xxs61vijLqOSGiGALakLUub+WBQ/MoZjuiAaDAj29v+rBhDjq7GOASIPCgkIuvb/qwYQ/wMSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYoRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxijF3IHCgMYoRcQA3IHCgMYoxcQAVIsCgoKAhgCEM+/1tgBCgoKAhhiEICw1tgBCggKAxihFxCIDgoICgMYoxcQyAE="},{"b64Body":"ChEKCQi69v+rBhD/AxICGAIgAUI4GiISIOb1RgxT5eFnnGiGi3cmblabvwLVU4/3kbG9XdQ811LdQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKMXEjDL7tN45OvzaCQXgvvNHFVEwua3pATJxQKVDrX2I0R1wCSzzEaYq3KCfnduqYorvucaDAj29v+rBhDkq7GOASIRCgkIuvb/qwYQ/wMSAhgCIAFCHQoDGKMXShYKFGsfZdJISoX+HTNFX0peIfDhOW59UgB6DAj29v+rBhDjq7GOAQ=="}]},"PriorityAddressIsCreate2ForStaticHapiCalls":{"placeholderNum":2980,"encodedItems":[{"b64Body":"Cg8KCQi+9v+rBhCZBBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAj6xNqvBhCo1NLoAhptCiISIE7nmitbgCzr/K8P1xSHfR0PAk8/bXZeI/oVwmWgsiDbCiM6IQPbhw9cJ0bRQWpPAku/tFKezlcdkKFkPAvMHtHg6cRqJgoiEiDZrXBGPl2YO2bW5icT1uDN7BE/gb8YvPwa/a+SM8sGgyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA/v8t5/qnxVSfw9OUWbDvS8o8diBZs2P1gTGo5Ko+Zl2oM5gT7qHCpxlBpCzvdNG0aDAj69v+rBhCz6Zn9AiIPCgkIvvb/qwYQmQQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQi/9v+rBhCdBBICGAISAhgDGJ/ztjwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBhh0KAxilFyL+HDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDcxZjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzEyNDQ3OTJmMTQ2MTAwM2I1NzgwNjM3MGFlZGEzNjE0NjEwMDZiNTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDI2NDU2NWI2MTAwODc1NjViNjA0MDUxNjEwMDYyOTE5MDYxMDJhMDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwODU2MDA0ODAzNjAzODEwMTkwNjEwMDgwOTE5MDYxMDJmMTU2NWI2MTAxMDQ1NjViMDA1YjYwMDA4MDgyOTA1MDgwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzBiNzExM2ZjNjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxODY1YWZhMTU4MDE1NjEwMGQ4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjA0MDUxM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTAwZmM5MTkwNjEwMzRhNTY1YjkxNTA1MDkxOTA1MDU2NWI2MDAwNjBmZjYwZjgxYjMwODM2MDQwNTE4MDYwMjAwMTYxMDExZDkwNjEwMWY0NTY1YjYwMjA4MjAxODEwMzgyNTI2MDFmMTk2MDFmODIwMTE2NjA0MDUyNTA2MDQwNTE2MDIwMDE2MTAxNDE5MTkwNjEwM2YxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTZhOTQ5MzkyOTE5MDYxMDRiZTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDYwMDAxYzkwNTA2MDAwODI2MDQwNTE2MTAxOTQ5MDYxMDFmNDU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAxYjQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDFlZjU3NjAwMDgwZmQ1YjUwNTA1MDU2NWI2MTAxZGQ4MDYxMDUwZDgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDIzMTgyNjEwMjA2NTY1YjkwNTA5MTkwNTA1NjViNjEwMjQxODE2MTAyMjY1NjViODExNDYxMDI0YzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDI1ZTgxNjEwMjM4NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI3YTU3NjEwMjc5NjEwMjAxNTY1YjViNjAwMDYxMDI4ODg0ODI4NTAxNjEwMjRmNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDI5YTgxNjEwMjA2NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAyYjU2MDAwODMwMTg0NjEwMjkxNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMmNlODE2MTAyYmI1NjViODExNDYxMDJkOTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJlYjgxNjEwMmM1NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDMwNzU3NjEwMzA2NjEwMjAxNTY1YjViNjAwMDYxMDMxNTg0ODI4NTAxNjEwMmRjNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDMyNzgxNjEwMjA2NTY1YjgxMTQ2MTAzMzI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAzNDQ4MTYxMDMxZTU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAzNjA1NzYxMDM1ZjYxMDIwMTU2NWI1YjYwMDA2MTAzNmU4NDgyODUwMTYxMDMzNTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwM2FiNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMzkwNTY1YjgzODExMTE1NjEwM2JhNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTAzY2I4MjYxMDM3NzU2NWI2MTAzZDU4MTg1NjEwMzgyNTY1YjkzNTA2MTAzZTU4MTg1NjAyMDg2MDE2MTAzOGQ1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDNmZDgyODQ2MTAzYzA1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDQ0ZjYxMDQ0YTgyNjEwNDA4NTY1YjYxMDQzNDU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0NmQ4MjYxMDQ1NTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0N2Y4MjYxMDQ2MjU2NWI5MDUwOTE5MDUwNTY1YjYxMDQ5NzYxMDQ5MjgyNjEwMjI2NTY1YjYxMDQ3NDU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDRiODYxMDRiMzgyNjEwMmJiNTY1YjYxMDQ5ZDU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwNGNhODI4NzYxMDQzZTU2NWI2MDAxODIwMTkxNTA2MTA0ZGE4Mjg2NjEwNDg2NTY1YjYwMTQ4MjAxOTE1MDYxMDRlYTgyODU2MTA0YTc1NjViNjAyMDgyMDE5MTUwNjEwNGZhODI4NDYxMDRhNzU2NWI2MDIwODIwMTkxNTA4MTkwNTA5NTk0NTA1MDUwNTA1MDU2ZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MTAxYmQ4MDYxMDAyMDYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MTAwMzY1NzYwMDAzNTYwZTAxYzgwNjMwYjcxMTNmYzE0NjEwMDNiNTc4MDYzODA3ZGNkYTYxNDYxMDA1OTU3NWI2MDAwODBmZDViNjEwMDQzNjEwMDYzNTY1YjYwNDA1MTYxMDA1MDkxOTA2MTAxMTA1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDYxNjEwMDZiNTY1YjAwNWI2MDAwMzA5MDUwOTA1NjViNjA0MDUxNjEwMDc3OTA2MTAwZDU1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDA5MzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDU2NWI2MDVjODA2MTAxMmM4MzM5MDE5MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjEwMTBhODE2MTAwZTE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDEyNTYwMDA4MzAxODQ2MTAxMDE1NjViOTI5MTUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwM2Y4MDYwMWQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTI2MDAwODBmZGZlYTI2NDY5NzA2NjczNTgyMjEyMjA0YmNlMjQ5ZjkyMmMwZjNjOTQzODUyY2EwYzA0M2FlMGExN2Y5NTM4OWFiYzJjNmNlNWVjNjFjZjYyOWMxZjRiNjQ3MzZmNmM2MzQzMDAwODBjMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwZjgwZjM4MmEwOWFkZmU0ZDVmNzc1MzczNjNhYTkyYjViMzBlNjM3MzQ2NWQwNzkzMDEyZDQyM2FhNmYzZTlmNTY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMGJmNmI5ZDBmNjRlYTc4MWMzMTdhNzljZGEyOTU3OTZiYzVjMTZmYWVjZGE3NDg3MGE3YjA2OWNjMjk1MGNhOTI2NDczNmY2YzYzNDMwMDA4MGMwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwXiLCzRfdKbZ7zwn0qH5L9mbJ2I3LqtySYramA/kNUTY5ZNe7cEzTEgZtL2uBsn8zGgwI+/b/qwYQm/XohAEiDwoJCL/2/6sGEJ0EEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQi/9v+rBhCfBBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKUXGiISIL5OuJHMbHuKW6q03/r9iyR20YhoZ+T+hBxnPHDIZIHeIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDux2pw1WFycnOHyKYAX4CT0aLBUMuSb0vhPpS/MDNPw6B++T2eVd6sJAyNHOX90dIaDAj79v+rBhDj7Z6HAyIPCgkIv/b/qwYQnwQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQtQQCgMYphcSnw5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjEkR5LxRhADtXgGNwrto2FGEAa1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQJkVlthAIdWW2BAUWEAYpGQYQKgVltgQFGAkQOQ81thAIVgBIA2A4EBkGEAgJGQYQLxVlthAQRWWwBbYACAgpBQgHP//////////////////////////xZjC3ET/GBAUYFj/////xZg4BuBUmAEAWAgYEBRgIMDgYZa+hWAFWEA2Fc9YACAPj1gAP1bUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhAPyRkGEDSlZbkVBQkZBQVltgAGD/YPgbMINgQFGAYCABYQEdkGEB9FZbYCCCAYEDglJgHxlgH4IBFmBAUlBgQFFgIAFhAUGRkGED8VZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGBAUWAgAWEBapSTkpGQYQS+VltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgYAAckFBgAIJgQFFhAZSQYQH0VluBkGBAUYCRA5BgAPWQUIAVgBVhAbRXPWAAgD49YAD9W1CQUIFz//////////////////////////8WgXP//////////////////////////xYUYQHvV2AAgP1bUFBQVlthAd2AYQUNgzkBkFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQIxgmECBlZbkFCRkFBWW2ECQYFhAiZWW4EUYQJMV2AAgP1bUFZbYACBNZBQYQJegWECOFZbkpFQUFZbYABgIIKEAxIVYQJ6V2ECeWECAVZbW2AAYQKIhIKFAWECT1ZbkVBQkpFQUFZbYQKagWECBlZbglJQUFZbYABgIIIBkFBhArVgAIMBhGECkVZbkpFQUFZbYACBkFCRkFBWW2ECzoFhArtWW4EUYQLZV2AAgP1bUFZbYACBNZBQYQLrgWECxVZbkpFQUFZbYABgIIKEAxIVYQMHV2EDBmECAVZbW2AAYQMVhIKFAWEC3FZbkVBQkpFQUFZbYQMngWECBlZbgRRhAzJXYACA/VtQVltgAIFRkFBhA0SBYQMeVluSkVBQVltgAGAggoQDEhVhA2BXYQNfYQIBVltbYABhA26EgoUBYQM1VluRUFCSkVBQVltgAIFRkFCRkFBWW2AAgZBQkpFQUFZbYABbg4EQFWEDq1eAggFRgYQBUmAggQGQUGEDkFZbg4ERFWEDuldgAISEAVJbUFBQUFZbYABhA8uCYQN3VlthA9WBhWEDglZbk1BhA+WBhWAghgFhA41WW4CEAZFQUJKRUFBWW2AAYQP9goRhA8BWW5FQgZBQkpFQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQRPYQRKgmEECFZbYQQ0VluCUlBQVltgAIFgYBuQUJGQUFZbYABhBG2CYQRVVluQUJGQUFZbYABhBH+CYQRiVluQUJGQUFZbYQSXYQSSgmECJlZbYQR0VluCUlBQVltgAIGQUJGQUFZbYQS4YQSzgmECu1ZbYQSdVluCUlBQVltgAGEEyoKHYQQ+VltgAYIBkVBhBNqChmEEhlZbYBSCAZFQYQTqgoVhBKdWW2AgggGRUGEE+oKEYQSnVltgIIIBkVCBkFCVlFBQUFBQVv5ggGBAUjSAFWEAEFdgAID9W1BhAb2AYQAgYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhADZXYAA1YOAcgGMLcRP8FGEAO1eAY4B9zaYUYQBZV1tgAID9W2EAQ2EAY1ZbYEBRYQBQkZBhARBWW2BAUYCRA5DzW2EAYWEAa1ZbAFtgADCQUJBWW2BAUWEAd5BhANVWW2BAUYCRA5BgAPCAFYAVYQCTVz1gAIA+PWAA/VtQYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQVltgXIBhASyDOQGQVltgAHP//////////////////////////4IWkFCRkFBWW2EBCoFhAOFWW4JSUFBWW2AAYCCCAZBQYQElYACDAYRhAQFWW5KRUFBW/mCAYEBSNIAVYA9XYACA/VtQYD+AYB1gADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiBLziSfkiwPPJQ4UsoMBDrgoX+VOJq8LGzl7GHPYpwfS2Rzb2xjQwAIDAAzomRpcGZzWCISIPgPOCoJrf5NX3dTc2OqkrWzDmNzRl0HkwEtQjqm8+n1ZHNvbGNDAAgMADOiZGlwZnNYIhIgv2udD2TqeBwxennNopV5a8XBb67Np0hwp7BpzClQypJkc29sY0MACAwAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYphdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAC6ZyBwoDGKYXEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjA9v+rBhChBBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGKYXEICS9AEiJHCu2jaqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGKYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBOPy/tUvbGujdqjAIEgf7Btju63hytzn5FnB8b5zm/xMZZtLEUglvYoSMA8mCvwbcaDAj89v+rBhCbxrCOASIPCgkIwPb/qwYQoQQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYphcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxinF3IHCgMYphcQAnIHCgMYpxcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjA9v+rBhChBBICGAIgAUI4GiISIL5OuJHMbHuKW6q03/r9iyR20YhoZ+T+hBxnPHDIZIHeQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKcXEjArE9QCr0E/JJasRwtCB7A1n4idaSL+AM1DAUcrFZbItzVtlCij3X88sSroZdG4MyQaDAj89v+rBhCcxrCOASIRCgkIwPb/qwYQoQQSAhgCIAFCHQoDGKcXShYKFAQoUPvFDXUjTrH0RLDOM41cZ+b2UgB6DAj89v+rBhCbxrCOAQ=="},{"b64Body":"Cg8KCQjA9v+rBhCvBBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46IwoWIhQEKFD7xQ11I06x9ESwzjONXGfm9hCAkvQBIgSAfc2m","b64Record":"CiUIFiIDGKcXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAcbZGhMp83Yn3OgDLa0pmv1pSrQTwIe2YRzuTS2RqRV/sH2dlhKeVZS1kQGqtKK2saDAj89v+rBhCbidmPAyIPCgkIwPb/qwYQrwQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYpxcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxioF3IHCgMYpxcQAnIHCgMYqBcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjA9v+rBhCvBBICGAIgAUI4GiISIL5OuJHMbHuKW6q03/r9iyR20YhoZ+T+hBxnPHDIZIHeQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKgXEjCFVo0E2Xn75XlaM0QFlGXTVSR9PlV/7ZKArF5IFGSKDAZdC6zFwifaYMhWMplle2UaDAj89v+rBhCcidmPAyIRCgkIwPb/qwYQrwQSAhgCIAFCHQoDGKgXShYKFBswka+jnLXR7w7m353D3Bp/ucrKUgB6DAj89v+rBhCbidmPAw=="}]},"CanInternallyCallAliasedAddressesOnlyViaCreate2Address":{"placeholderNum":2985,"encodedItems":[{"b64Body":"Cg8KCQjF9v+rBhC/BBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiBxdqvBhDgo+mYARptCiISIBDvHbTDVp16lo2FCSMhfWFEreNP7phPvyqZM4A5SLXPCiM6IQPkZBeoPy2Ngg4JLNcdPTndcA5QBBFCmHKjiTymvRp0ngoiEiD8Q9am4Z9e8/TnK6tI2efbHonhSq+3zEc9gJ82SO1SaiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKoXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBssgttcB/mjfM0J/WyzL4EsOHAX8BTywlXHGCnV5CTdoYMPZaQhLTais+2oBudNoIaDAiB9/+rBhCz18KhASIPCgkIxfb/qwYQvwQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjF9v+rBhDDBBICGAISAhgDGJ/ztjwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBhh0KAxiqFyL+HDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDcxZjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzEyNDQ3OTJmMTQ2MTAwM2I1NzgwNjM3MGFlZGEzNjE0NjEwMDZiNTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDI2NDU2NWI2MTAwODc1NjViNjA0MDUxNjEwMDYyOTE5MDYxMDJhMDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwODU2MDA0ODAzNjAzODEwMTkwNjEwMDgwOTE5MDYxMDJmMTU2NWI2MTAxMDQ1NjViMDA1YjYwMDA4MDgyOTA1MDgwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzBiNzExM2ZjNjA0MDUxODE2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjAyMDYwNDA1MTgwODMwMzgxODY1YWZhMTU4MDE1NjEwMGQ4NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjA0MDUxM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTAwZmM5MTkwNjEwMzRhNTY1YjkxNTA1MDkxOTA1MDU2NWI2MDAwNjBmZjYwZjgxYjMwODM2MDQwNTE4MDYwMjAwMTYxMDExZDkwNjEwMWY0NTY1YjYwMjA4MjAxODEwMzgyNTI2MDFmMTk2MDFmODIwMTE2NjA0MDUyNTA2MDQwNTE2MDIwMDE2MTAxNDE5MTkwNjEwM2YxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTZhOTQ5MzkyOTE5MDYxMDRiZTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDYwMDAxYzkwNTA2MDAwODI2MDQwNTE2MTAxOTQ5MDYxMDFmNDU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAxYjQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDFlZjU3NjAwMDgwZmQ1YjUwNTA1MDU2NWI2MTAxZGQ4MDYxMDUwZDgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDIzMTgyNjEwMjA2NTY1YjkwNTA5MTkwNTA1NjViNjEwMjQxODE2MTAyMjY1NjViODExNDYxMDI0YzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDI1ZTgxNjEwMjM4NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI3YTU3NjEwMjc5NjEwMjAxNTY1YjViNjAwMDYxMDI4ODg0ODI4NTAxNjEwMjRmNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDI5YTgxNjEwMjA2NTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAyYjU2MDAwODMwMTg0NjEwMjkxNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMmNlODE2MTAyYmI1NjViODExNDYxMDJkOTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJlYjgxNjEwMmM1NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDMwNzU3NjEwMzA2NjEwMjAxNTY1YjViNjAwMDYxMDMxNTg0ODI4NTAxNjEwMmRjNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDMyNzgxNjEwMjA2NTY1YjgxMTQ2MTAzMzI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAzNDQ4MTYxMDMxZTU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAzNjA1NzYxMDM1ZjYxMDIwMTU2NWI1YjYwMDA2MTAzNmU4NDgyODUwMTYxMDMzNTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwM2FiNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMzkwNTY1YjgzODExMTE1NjEwM2JhNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTAzY2I4MjYxMDM3NzU2NWI2MTAzZDU4MTg1NjEwMzgyNTY1YjkzNTA2MTAzZTU4MTg1NjAyMDg2MDE2MTAzOGQ1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDNmZDgyODQ2MTAzYzA1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDQ0ZjYxMDQ0YTgyNjEwNDA4NTY1YjYxMDQzNDU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0NmQ4MjYxMDQ1NTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0N2Y4MjYxMDQ2MjU2NWI5MDUwOTE5MDUwNTY1YjYxMDQ5NzYxMDQ5MjgyNjEwMjI2NTY1YjYxMDQ3NDU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDRiODYxMDRiMzgyNjEwMmJiNTY1YjYxMDQ5ZDU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwNGNhODI4NzYxMDQzZTU2NWI2MDAxODIwMTkxNTA2MTA0ZGE4Mjg2NjEwNDg2NTY1YjYwMTQ4MjAxOTE1MDYxMDRlYTgyODU2MTA0YTc1NjViNjAyMDgyMDE5MTUwNjEwNGZhODI4NDYxMDRhNzU2NWI2MDIwODIwMTkxNTA4MTkwNTA5NTk0NTA1MDUwNTA1MDU2ZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MTAxYmQ4MDYxMDAyMDYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwNDM2MTA2MTAwMzY1NzYwMDAzNTYwZTAxYzgwNjMwYjcxMTNmYzE0NjEwMDNiNTc4MDYzODA3ZGNkYTYxNDYxMDA1OTU3NWI2MDAwODBmZDViNjEwMDQzNjEwMDYzNTY1YjYwNDA1MTYxMDA1MDkxOTA2MTAxMTA1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDYxNjEwMDZiNTY1YjAwNWI2MDAwMzA5MDUwOTA1NjViNjA0MDUxNjEwMDc3OTA2MTAwZDU1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDA5MzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDU2NWI2MDVjODA2MTAxMmM4MzM5MDE5MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjEwMTBhODE2MTAwZTE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDEyNTYwMDA4MzAxODQ2MTAxMDE1NjViOTI5MTUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwM2Y4MDYwMWQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTI2MDAwODBmZGZlYTI2NDY5NzA2NjczNTgyMjEyMjA0YmNlMjQ5ZjkyMmMwZjNjOTQzODUyY2EwYzA0M2FlMGExN2Y5NTM4OWFiYzJjNmNlNWVjNjFjZjYyOWMxZjRiNjQ3MzZmNmM2MzQzMDAwODBjMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwZjgwZjM4MmEwOWFkZmU0ZDVmNzc1MzczNjNhYTkyYjViMzBlNjM3MzQ2NWQwNzkzMDEyZDQyM2FhNmYzZTlmNTY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMGJmNmI5ZDBmNjRlYTc4MWMzMTdhNzljZGEyOTU3OTZiYzVjMTZmYWVjZGE3NDg3MGE3YjA2OWNjMjk1MGNhOTI2NDczNmY2YzYzNDMwMDA4MGMwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw0yW5m5vUi2oMtUQ5RuTX3nBDKlGKAKj25YLsimmlOmy0K2LELI0KofsyD2bfhUpwGgwIgff/qwYQk+/uogMiDwoJCMX2/6sGEMMEEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjG9v+rBhDFBBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKoXGiISIL0w7ZLnG3w3PEPeJdEtoIZgDki7qwtrOP+tacCrNEv4IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCWmv6c3rX8sPuRR9YXvL/M/13odoMu2kcL30wYA6Ag9k0zdt1mgHxPd/oFFogu28gaDAiC9/+rBhDruYyrASIPCgkIxvb/qwYQxQQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQtQQCgMYqxcSnw5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjEkR5LxRhADtXgGNwrto2FGEAa1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQJkVlthAIdWW2BAUWEAYpGQYQKgVltgQFGAkQOQ81thAIVgBIA2A4EBkGEAgJGQYQLxVlthAQRWWwBbYACAgpBQgHP//////////////////////////xZjC3ET/GBAUYFj/////xZg4BuBUmAEAWAgYEBRgIMDgYZa+hWAFWEA2Fc9YACAPj1gAP1bUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhAPyRkGEDSlZbkVBQkZBQVltgAGD/YPgbMINgQFGAYCABYQEdkGEB9FZbYCCCAYEDglJgHxlgH4IBFmBAUlBgQFFgIAFhAUGRkGED8VZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGBAUWAgAWEBapSTkpGQYQS+VltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgYAAckFBgAIJgQFFhAZSQYQH0VluBkGBAUYCRA5BgAPWQUIAVgBVhAbRXPWAAgD49YAD9W1CQUIFz//////////////////////////8WgXP//////////////////////////xYUYQHvV2AAgP1bUFBQVlthAd2AYQUNgzkBkFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQIxgmECBlZbkFCRkFBWW2ECQYFhAiZWW4EUYQJMV2AAgP1bUFZbYACBNZBQYQJegWECOFZbkpFQUFZbYABgIIKEAxIVYQJ6V2ECeWECAVZbW2AAYQKIhIKFAWECT1ZbkVBQkpFQUFZbYQKagWECBlZbglJQUFZbYABgIIIBkFBhArVgAIMBhGECkVZbkpFQUFZbYACBkFCRkFBWW2ECzoFhArtWW4EUYQLZV2AAgP1bUFZbYACBNZBQYQLrgWECxVZbkpFQUFZbYABgIIKEAxIVYQMHV2EDBmECAVZbW2AAYQMVhIKFAWEC3FZbkVBQkpFQUFZbYQMngWECBlZbgRRhAzJXYACA/VtQVltgAIFRkFBhA0SBYQMeVluSkVBQVltgAGAggoQDEhVhA2BXYQNfYQIBVltbYABhA26EgoUBYQM1VluRUFCSkVBQVltgAIFRkFCRkFBWW2AAgZBQkpFQUFZbYABbg4EQFWEDq1eAggFRgYQBUmAggQGQUGEDkFZbg4ERFWEDuldgAISEAVJbUFBQUFZbYABhA8uCYQN3VlthA9WBhWEDglZbk1BhA+WBhWAghgFhA41WW4CEAZFQUJKRUFBWW2AAYQP9goRhA8BWW5FQgZBQkpFQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQRPYQRKgmEECFZbYQQ0VluCUlBQVltgAIFgYBuQUJGQUFZbYABhBG2CYQRVVluQUJGQUFZbYABhBH+CYQRiVluQUJGQUFZbYQSXYQSSgmECJlZbYQR0VluCUlBQVltgAIGQUJGQUFZbYQS4YQSzgmECu1ZbYQSdVluCUlBQVltgAGEEyoKHYQQ+VltgAYIBkVBhBNqChmEEhlZbYBSCAZFQYQTqgoVhBKdWW2AgggGRUGEE+oKEYQSnVltgIIIBkVCBkFCVlFBQUFBQVv5ggGBAUjSAFWEAEFdgAID9W1BhAb2AYQAgYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhADZXYAA1YOAcgGMLcRP8FGEAO1eAY4B9zaYUYQBZV1tgAID9W2EAQ2EAY1ZbYEBRYQBQkZBhARBWW2BAUYCRA5DzW2EAYWEAa1ZbAFtgADCQUJBWW2BAUWEAd5BhANVWW2BAUYCRA5BgAPCAFYAVYQCTVz1gAIA+PWAA/VtQYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQVltgXIBhASyDOQGQVltgAHP//////////////////////////4IWkFCRkFBWW2EBCoFhAOFWW4JSUFBWW2AAYCCCAZBQYQElYACDAYRhAQFWW5KRUFBW/mCAYEBSNIAVYA9XYACA/VtQYD+AYB1gADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiBLziSfkiwPPJQ4UsoMBDrgoX+VOJq8LGzl7GHPYpwfS2Rzb2xjQwAIDAAzomRpcGZzWCISIPgPOCoJrf5NX3dTc2OqkrWzDmNzRl0HkwEtQjqm8+n1ZHNvbGNDAAgMADOiZGlwZnNYIhIgv2udD2TqeBwxennNopV5a8XBb67Np0hwp7BpzClQypJkc29sY0MACAwAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYqxdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAC6tyBwoDGKsXEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjG9v+rBhDHBBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGKsXEICS9AEiJHCu2jaqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGKsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC/of1JQ/mWAOFx/K35vtfm5/cyjPdwnWDN0vHdkc2Yyz5o3DM6hTiNRr0fedVwnJsaDAiC9/+rBhCrz9isAyIPCgkIxvb/qwYQxwQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYqxcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxisF3IHCgMYqxcQAnIHCgMYrBcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjG9v+rBhDHBBICGAIgAUI4GiISIL0w7ZLnG3w3PEPeJdEtoIZgDki7qwtrOP+tacCrNEv4QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKwXEjAgi74jJX2yqgUVahUhdasF6g6gyPlOjobBjoC3fWiyGK5uWV8v4mCsMzTL4EeQrQMaDAiC9/+rBhCsz9isAyIRCgkIxvb/qwYQxwQSAhgCIAFCHQoDGKwXShYKFKhgDWxlvdxuite/SuzffQOKkNNZUgB6DAiC9/+rBhCrz9isAw=="},{"b64Body":"Cg8KCQjH9v+rBhDVBBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYqxcQoI0GIiQSRHkvAAAAAAAAAAAAAAAAqGANbGW93G6K179K7N99A4qQ01k=","b64Record":"CiUIFiIDGKsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBfO5MKf+zBjfROsM5yc4pYVwYIIIxiaLb43s2vZ8zdQllXnUkPwvMkvfio7uDxh1gaDAiD9/+rBhCD5rC1ASIPCgkIx/b/qwYQ1QQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOq4CCgMYqxcSIAAAAAAAAAAAAAAAAKhgDWxlvdxuite/SuzffQOKkNNZIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiA8QRSFgoJCgIYAhD/rbUFCgkKAhhiEICutQU="},{"b64Body":"Cg8KCQjH9v+rBhDXBBICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjovCgMYqxcQoI0GIiQSRHkvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC6w=","b64Record":"CiUIHSIDGKsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZ8+wQP5D+284LXFNbcMJziAHPRzWaNSw+ePb8cbW1B0Y4LYbp5tbTL8gYeybPKyoaDAiD9/+rBhDb8dO2AyIPCgkIx/b/qwYQ1wQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDgrLEDOh4aGElOVkFMSURfU09MSURJVFlfQUREUkVTUyigjQZSFgoJCgIYAhC/2eIGCgkKAhhiEMDZ4gY="}]},"Create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed":{"placeholderNum":2989,"encodedItems":[{"b64Body":"Cg8KCQjM9v+rBhDrBBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiIxdqvBhDQ5dm3ARptCiISIBCThyCzhah1Ogj4lTX912+MEHXK+qK7H347wZlQiUzMCiM6IQJO+Y/rOcTpHWrGX4dUPzQbX7+ockWVpf2TvsuGglvnUQoiEiCPSALy3t9/arm4BZ4HX3HFdxT71YY1rIJ9ae5xGjOp2CIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGK4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAwihktJ6A0p+3AdRTtq6iONyRl/ZMzCBz6rIzHO1WKhh5kGTXVrg8Lu/QIUdxPYI8aDAiI9/+rBhC7jPPHASIPCgkIzPb/qwYQ6wQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjM9v+rBhDvBBICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxiuFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGNiZTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzY0MTVlOTUyMTQ2MTAwM2I1NzgwNjM3YTY5ZmY1NzE0NjEwMDU3NTc1YjYwMDA4MGZkNWI2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDIzNDU2NWI2MTAwNzM1NjViMDA1YjYxMDA3MTYwMDQ4MDM2MDM4MTAxOTA2MTAwNmM5MTkwNjEwMmJmNTY1YjYxMDE3NzU2NWIwMDViNjAwMDYwMmE5MDUwNjAwMDYwZmY2MGY4MWIzMDg0NjA0MDUxODA2MDIwMDE2MTAwOTI5MDYxMDFlYzU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwODU2MDQwNTE2MDIwMDE2MTAwYjg5MjkxOTA2MTAzYTQ1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDQwNTE2MDIwMDE2MTAwZTE5NDkzOTI5MTkwNjEwNDgyNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjgwNTE5MDYwMjAwMTIwNjAwMDFjOTA1MDYwMDA4MzgzNjA0MDUxNjEwMTBjOTA2MTAxZWM1NjViNjEwMTE2OTE5MDYxMDRkZjU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAxMzY1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDE3MTU3NjAwMDgwZmQ1YjUwNTA1MDUwNTY1YjYwMDA4MjkwNTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMwY2E3ZTY4NjgzNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwMWI1OTE5MDYxMDUwOTU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMWNmNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMWUzNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTY1YjYxMDc2NDgwNjEwNTI1ODMzOTAxOTA1NjViNjAwMDgwZmQ1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjExODE2MTAxZmU1NjViODExNDYxMDIxYzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDIyZTgxNjEwMjA4NTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDI0YTU3NjEwMjQ5NjEwMWY5NTY1YjViNjAwMDYxMDI1ODg0ODI4NTAxNjEwMjFmNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMjhjODI2MTAyNjE1NjViOTA1MDkxOTA1MDU2NWI2MTAyOWM4MTYxMDI4MTU2NWI4MTE0NjEwMmE3NTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMmI5ODE2MTAyOTM1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyZDY1NzYxMDJkNTYxMDFmOTU2NWI1YjYwMDA2MTAyZTQ4NTgyODYwMTYxMDJhYTU2NWI5MjUwNTA2MDIwNjEwMmY1ODU4Mjg2MDE2MTAyMWY1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMzMzNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMzE4NTY1YjgzODExMTE1NjEwMzQyNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTAzNTM4MjYxMDJmZjU2NWI2MTAzNWQ4MTg1NjEwMzBhNTY1YjkzNTA2MTAzNmQ4MTg1NjAyMDg2MDE2MTAzMTU1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDM5ZTYxMDM5OTgyNjEwMzc5NTY1YjYxMDM4MzU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwM2IwODI4NTYxMDM0ODU2NWI5MTUwNjEwM2JjODI4NDYxMDM4ZDU2NWI2MDIwODIwMTkxNTA4MTkwNTA5MzkyNTA1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDQxMzYxMDQwZTgyNjEwM2NjNTY1YjYxMDNmODU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0MzE4MjYxMDQxOTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA0NDM4MjYxMDQyNjU2NWI5MDUwOTE5MDUwNTY1YjYxMDQ1YjYxMDQ1NjgyNjEwMjgxNTY1YjYxMDQzODU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDQ3YzYxMDQ3NzgyNjEwMWZlNTY1YjYxMDQ2MTU2NWI4MjUyNTA1MDU2NWI2MDAwNjEwNDhlODI4NzYxMDQwMjU2NWI2MDAxODIwMTkxNTA2MTA0OWU4Mjg2NjEwNDRhNTY1YjYwMTQ4MjAxOTE1MDYxMDRhZTgyODU2MTA0NmI1NjViNjAyMDgyMDE5MTUwNjEwNGJlODI4NDYxMDQ2YjU2NWI2MDIwODIwMTkxNTA4MTkwNTA5NTk0NTA1MDUwNTA1MDU2NWI2MTA0ZDk4MTYxMDM3OTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNGY0NjAwMDgzMDE4NDYxMDRkMDU2NWI5MjkxNTA1MDU2NWI2MTA1MDM4MTYxMDFmZTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNTFlNjAwMDgzMDE4NDYxMDRmYTU2NWI5MjkxNTA1MDU2ZmU2MDgwNjA0MDUyNjA0MDUxNjEwNzY0MzgwMzgwNjEwNzY0ODMzOTgxODEwMTYwNDA1MjgxMDE5MDYxMDAyNTkxOTA2MTAwNmQ1NjViODA2MDAwODE5MDU1NTA1MDYxMDA5YTU2NWI2MDAwODBmZDViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAwNGE4MTYxMDAzNzU2NWI4MTE0NjEwMDU1NTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwMDY3ODE2MTAwNDE1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMDgzNTc2MTAwODI2MTAwMzI1NjViNWI2MDAwNjEwMDkxODQ4Mjg1MDE2MTAwNTg1NjViOTE1MDUwOTI5MTUwNTA1NjViNjEwNmJiODA2MTAwYTk2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMDIxYmY3MmMxNDYxMDA1MTU3ODA2MzAyZWQxOGRkMTQ2MTAwNmY1NzgwNjMwY2E3ZTY4NjE0NjEwMDhkNTc4MDYzNDUyMDE4OTYxNDYxMDBhOTU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGM1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAyNTc1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGNiNTY1YjYwNDA1MTYxMDA4NDkxOTA2MTAyNTc1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGE3NjAwNDgwMzYwMzgxMDE5MDYxMDBhMjkxOTA2MTAyYWQ1NjViNjEwMGQ0NTY1YjAwNWI2MTAwYzM2MDA0ODAzNjAzODEwMTkwNjEwMGJlOTE5MDYxMDJhZDU2NWI2MTAxNGU1NjViMDA1YjYwMDA1NDgxNTY1YjYwMDA4MDU0OTA1MDkwNTY1YjgwMzA2MDAwNTQ2MDQwNTE2MTAwZTU5MDYxMDIzMTU2NWI2MTAwZjA5MjkxOTA2MTAzMWI1NjViODE5MDYwNDA1MTgwOTEwMzkwNjAwMGY1OTA1MDgwMTU4MDE1NjEwMTEwNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA3ZjdmZjUxYzM3ZjdhNWQ3MjFlMzg4YzU2NDU2N2U5MGFlNDBiZmFhNzdhZGNlNjFmNzU1ZDUzMGE2ZGZmZjYzYzE2MDAwNTQ2MDQwNTE2MTAxNDM5MTkwNjEwMjU3NTY1YjYwNDA1MTgwOTEwMzkwYTE1MDU2NWI2MDAwODEzMDYwMDA1NDYwNDA1MTYxMDE2MTkwNjEwMjMxNTY1YjYxMDE2YzkyOTE5MDYxMDMxYjU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAxOGM1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzNzM3YmMzYzk2MDQwNTE4MTYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwMWQ3NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwMWViNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwODEzMDYwMDA1NDYwNDA1MTYxMDIwMDkwNjEwMjMxNTY1YjYxMDIwYjkyOTE5MDYxMDMxYjU2NWI4MTkwNjA0MDUxODA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw5g8x6BYdrJyVHl27vRvYQm6djFJznQuv9XZ7bgbwjFAbd/XQbhqZ7eCx1ulx45dsGgwIiPf/qwYQu9nFrAMiDwoJCMz2/6sGEO8EEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjN9v+rBhD1BBICGAISAhgDGOuF5jciAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBxBMSAxiuFyK8EzkxMDM5MDYwMDBmNTkwNTA4MDE1ODAxNTYxMDIyYjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDU2NWI2MTAzNDE4MDYxMDM0NTgzMzkwMTkwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjUxODE2MTAyM2U1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDI2YzYwMDA4MzAxODQ2MTAyNDg1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjhhODE2MTAyNzc1NjViODExNDYxMDI5NTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJhNzgxNjEwMjgxNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDJjMzU3NjEwMmMyNjEwMjcyNTY1YjViNjAwMDYxMDJkMTg0ODI4NTAxNjEwMjk4NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMzA1ODI2MTAyZGE1NjViOTA1MDkxOTA1MDU2NWI2MTAzMTU4MTYxMDJmYTU2NWI4MjUyNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwMzMwNjAwMDgzMDE4NTYxMDMwYzU2NWI2MTAzM2Q2MDIwODMwMTg0NjEwMjQ4NTY1YjkzOTI1MDUwNTA1NmZlNjA4MDYwNDA1MjYwNDA1MTYxMDM0MTM4MDM4MDYxMDM0MTgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMjU5MTkwNjEwMTBjNTY1YjgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxODE5MDU1NTA1MDUwNjEwMTRjNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDBhMzgyNjEwMDc4NTY1YjkwNTA5MTkwNTA1NjViNjEwMGIzODE2MTAwOTg1NjViODExNDYxMDBiZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDBkMDgxNjEwMGFhNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMGU5ODE2MTAwZDY1NjViODExNDYxMDBmNDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDEwNjgxNjEwMGUwNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMTIzNTc2MTAxMjI2MTAwNzM1NjViNWI2MDAwNjEwMTMxODU4Mjg2MDE2MTAwYzE1NjViOTI1MDUwNjAyMDYxMDE0Mjg1ODI4NjAxNjEwMGY3NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMWU2ODA2MTAxNWI2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA1MTU3ODA2MzczN2JjM2M5MTQ2MTAwNmY1NzgwNjM4ZGE1Y2I1YjE0NjEwMDc5NTc4MDYzYzI5ODU1NzgxNDYxMDA5NzU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGI1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAxMzk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGJkNTY1YjAwNWI2MTAwODE2MTAwZjY1NjViNjA0MDUxNjEwMDhlOTE5MDYxMDE5NTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwOWY2MTAxMWE1NjViNjA0MDUxNjEwMGFjOTE5MDYxMDEzOTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNDc5MDUwOTA1NjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1NjViNjAwMTU0ODE1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAxMzM4MTYxMDEyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTRlNjAwMDgzMDE4NDYxMDEyYTU2NWI5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDE3ZjgyNjEwMTU0NTY1YjkwNTA5MTkwNTA1NjViNjEwMThmODE2MTAxNzQ1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDFhYTYwMDA4MzAxODQ2MTAxODY1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjA2ODg1ZTllYTc3NjQyMGZkNjFiOTU3NGE3MmU1ZjA1ZGFlOTQ5MWE5OTNjNjFkMDgwZmZkZmE3NzlhYWFhMTRkNjQ3MzZmNmM2MzQzMDAwODBjMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwMGVjYzhmOWQ3OGQxMTE3ZGM4YjAyZGI0YzNlNGIxNmZlOTg3YmQwZmY0MGM2YzdmNzIwZWE5ZGNlZWY3NTg4YzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDc1ZjE0MTZjZDRjZjg3OWZjOWExNjkwOTgyYjg1ZTVkNjI0NTg2MTlmMWRmN2UyY2EyNmNiMTFlN2Y3M2ViMTQ2NDczNmY2YzYzNDMwMDA4MGMwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwrHfZ7oI0Vow5gnMCMZBqnMENrhkbSkHZ65sytw5NLj+sF+6pOdmsKO9bwxRBlDT5GgwIiff/qwYQs5fx0AEiDwoJCM32/6sGEPUEEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjN9v+rBhD3BBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGK4XGiISICcaOWwTiKqCf49H0muqXBxnN/Qew9frxe2+7N7bMWGFIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGK8XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBdv1Za1fr/X64Yreu7mu31vkZW1h1Emlq5rYDKd3SDvZmpYQA46r+CP5+7nRlP33saDAiJ9/+rBhCr94C3AyIPCgkIzfb/qwYQ9wQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQvMbCgMYrxcSvhlggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjZBXpUhRhADtXgGN6af9XFGEAV1dbYACA/VthAFVgBIA2A4EBkGEAUJGQYQI0VlthAHNWWwBbYQBxYASANgOBAZBhAGyRkGECv1ZbYQF3VlsAW2AAYCqQUGAAYP9g+BswhGBAUYBgIAFhAJKQYQHsVltgIIIBgQOCUmAfGWAfggEWYEBSUIVgQFFgIAFhALiSkZBhA6RWW2BAUWAggYMDA4FSkGBAUoBRkGAgASBgQFFgIAFhAOGUk5KRkGEEglZbYEBRYCCBgwMDgVKQYEBSgFGQYCABIGAAHJBQYACDg2BAUWEBDJBhAexWW2EBFpGQYQTfVluBkGBAUYCRA5BgAPWQUIAVgBVhATZXPWAAgD49YAD9W1CQUIFz//////////////////////////8WgXP//////////////////////////xYUYQFxV2AAgP1bUFBQUFZbYACCkFCAc///////////////////////////FmMMp+aGg2BAUYJj/////xZg4BuBUmAEAWEBtZGQYQUJVltgAGBAUYCDA4FgAIeAOxWAFWEBz1dgAID9W1Ba8RWAFWEB41c9YACAPj1gAP1bUFBQUFBQUFZbYQdkgGEFJYM5AZBWW2AAgP1bYACBkFCRkFBWW2ECEYFhAf5WW4EUYQIcV2AAgP1bUFZbYACBNZBQYQIugWECCFZbkpFQUFZbYABgIIKEAxIVYQJKV2ECSWEB+VZbW2AAYQJYhIKFAWECH1ZbkVBQkpFQUFZbYABz//////////////////////////+CFpBQkZBQVltgAGECjIJhAmFWW5BQkZBQVlthApyBYQKBVluBFGECp1dgAID9W1BWW2AAgTWQUGECuYFhApNWW5KRUFBWW2AAgGBAg4UDEhVhAtZXYQLVYQH5VltbYABhAuSFgoYBYQKqVluSUFBgIGEC9YWChgFhAh9WW5FQUJJQkpBQVltgAIFRkFCRkFBWW2AAgZBQkpFQUFZbYABbg4EQFWEDM1eAggFRgYQBUmAggQGQUGEDGFZbg4ERFWEDQldgAISEAVJbUFBQUFZbYABhA1OCYQL/VlthA12BhWEDClZbk1BhA22BhWAghgFhAxVWW4CEAZFQUJKRUFBWW2AAgZBQkZBQVltgAIGQUJGQUFZbYQOeYQOZgmEDeVZbYQODVluCUlBQVltgAGEDsIKFYQNIVluRUGEDvIKEYQONVltgIIIBkVCBkFCTklBQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQQTYQQOgmEDzFZbYQP4VluCUlBQVltgAIFgYBuQUJGQUFZbYABhBDGCYQQZVluQUJGQUFZbYABhBEOCYQQmVluQUJGQUFZbYQRbYQRWgmECgVZbYQQ4VluCUlBQVltgAIGQUJGQUFZbYQR8YQR3gmEB/lZbYQRhVluCUlBQVltgAGEEjoKHYQQCVltgAYIBkVBhBJ6ChmEESlZbYBSCAZFQYQSugoVhBGtWW2AgggGRUGEEvoKEYQRrVltgIIIBkVCBkFCVlFBQUFBQVlthBNmBYQN5VluCUlBQVltgAGAgggGQUGEE9GAAgwGEYQTQVluSkVBQVlthBQOBYQH+VluCUlBQVltgAGAgggGQUGEFHmAAgwGEYQT6VluSkVBQVv5ggGBAUmBAUWEHZDgDgGEHZIM5gYEBYEBSgQGQYQAlkZBhAG1WW4BgAIGQVVBQYQCaVltgAID9W2AAgZBQkZBQVlthAEqBYQA3VluBFGEAVVdgAID9W1BWW2AAgVGQUGEAZ4FhAEFWW5KRUFBWW2AAYCCChAMSFWEAg1dhAIJhADJWW1tgAGEAkYSChQFhAFhWW5FQUJKRUFBWW2EGu4BhAKlgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYwIb9ywUYQBRV4BjAu0Y3RRhAG9XgGMMp+aGFGEAjVeAY0UgGJYUYQCpV1tgAID9W2EAWWEAxVZbYEBRYQBmkZBhAldWW2BAUYCRA5DzW2EAd2EAy1ZbYEBRYQCEkZBhAldWW2BAUYCRA5DzW2EAp2AEgDYDgQGQYQCikZBhAq1WW2EA1FZbAFthAMNgBIA2A4EBkGEAvpGQYQKtVlthAU5WWwBbYABUgVZbYACAVJBQkFZbgDBgAFRgQFFhAOWQYQIxVlthAPCSkZBhAxtWW4GQYEBRgJEDkGAA9ZBQgBWAFWEBEFc9YACAPj1gAP1bUFB/f/UcN/el1yHjiMVkVn6QrkC/qnetzmH3VdUwpt//Y8FgAFRgQFFhAUORkGECV1ZbYEBRgJEDkKFQVltgAIEwYABUYEBRYQFhkGECMVZbYQFskpGQYQMbVluBkGBAUYCRA5BgAPWQUIAVgBVhAYxXPWAAgD49YAD9W1CQUIBz//////////////////////////8WY3N7w8lgQFGBY/////8WYOAbgVJgBAFgAGBAUYCDA4FgAIeAOxWAFWEB11dgAID9W1Ba8RWAFWEB61c9YACAPj1gAP1bUFBQUIEwYABUYEBRYQIAkGECMVZbYQILkpGQYQMbVluBkGBAUYCRA5BgAPWQUIAVgBVhAitXPWAAgD49YAD9W1BQUFBWW2EDQYBhA0WDOQGQVltgAIGQUJGQUFZbYQJRgWECPlZbglJQUFZbYABgIIIBkFBhAmxgAIMBhGECSFZbkpFQUFZbYACA/VtgAIGQUJGQUFZbYQKKgWECd1ZbgRRhApVXYACA/VtQVltgAIE1kFBhAqeBYQKBVluSkVBQVltgAGAggoQDEhVhAsNXYQLCYQJyVltbYABhAtGEgoUBYQKYVluRUFCSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQMFgmEC2lZbkFCRkFBWW2EDFYFhAvpWW4JSUFBWW2AAYECCAZBQYQMwYACDAYVhAwxWW2EDPWAggwGEYQJIVluTklBQUFb+YIBgQFJgQFFhA0E4A4BhA0GDOYGBAWBAUoEBkGEAJZGQYQEMVluBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFMVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhAKOCYQB4VluQUJGQUFZbYQCzgWEAmFZbgRRhAL5XYACA/VtQVltgAIFRkFBhANCBYQCqVluSkVBQVltgAIGQUJGQUFZbYQDpgWEA1lZbgRRhAPRXYACA/VtQVltgAIFRkFBhAQaBYQDgVluSkVBQVltgAIBgQIOFAxIVYQEjV2EBImEAc1ZbW2AAYQExhYKGAWEAwVZbklBQYCBhAUKFgoYBYQD3VluRUFCSUJKQUFZbYQHmgGEBW2AAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIGiF6ep3ZCD9YblXSnLl8F2ulJGpk8YdCA/9+neaqqFNZHNvbGNDAAgMADOiZGlwZnNYIhIgDsyPnXjREX3IsC20w+Sxb+mHvQ/0DGx/cg6p3O73WIxkc29sY0MACAwAM6JkaXBmc1giEiB18UFs1M+Hn8mhaQmCuF5dYkWGGfHffiyibLEef3PrFGRzb2xjQwAIDAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxivF0oWChQAAAAAAAAAAAAAAAAAAAAAAAALr3IHCgMYrxcQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQjO9v+rBhD5BBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGK8XEICS9AEiJGQV6VKqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGK8XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAlhg8WDiHMNDyB7rCorkYNRAd5XfcSI1zYRYuDrI8nSB7fnn3Y0HnJjTg7HX22EoYaDAiK9/+rBhC7ptjbASIPCgkIzvb/qwYQ+QQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYrxcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxiwF3IHCgMYrxcQAnIHCgMYsBcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjO9v+rBhD5BBICGAIgAUI4GiISICcaOWwTiKqCf49H0muqXBxnN/Qew9frxe2+7N7bMWGFQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGLAXEjA8avmXB23/IWU5dYhTUd/J5vekWQaDreVrYPv+fIJ/cZ2MVJlbNjA7V8eJQU4qYkYaDAiK9/+rBhC8ptjbASIRCgkIzvb/qwYQ+QQSAhgCIAFCHQoDGLAXShYKFKEDAaKxv4SUMo0u8V/S21dn0UtBUgB6DAiK9/+rBhC7ptjbAQ=="},{"b64Body":"Cg8KCQjO9v+rBhD/BBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46QwoWIhShAwGisb+ElDKNLvFf0ttXZ9FLQRCAkvQBIiQMp+aGqrvM3e7/ABGqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABE=","b64Record":"CiUIFiIDGLAXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCicinjyTwWlsYZQ6YNLpqsCewxtwFzmneGYxpgtz/TlY2nD3IgNbAES6tP4BWsqogaDAiK9/+rBhDz8rLAAyIPCgkIzvb/qwYQ/wQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOvMECgMYsBcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAQAAAAAAAAAAAAAAAAAAAAKICowwEyzAIKAxiwFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAgAAABAAAAAAAAAAAAAAAAAAAAAaIH/1HDf3pdch44jFZFZ+kK5Av6p3rc5h91XVMKbf/2PBIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKjoDGLEXcgcKAxiwFxACcgcKAxixFxABUhgKCgoCGAIQ/6/W2AEKCgoCGGIQgLDW2AE="},{"b64Body":"ChEKCQjO9v+rBhD/BBICGAIgAUI4GiISICcaOWwTiKqCf49H0muqXBxnN/Qew9frxe2+7N7bMWGFQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGLEXEjAuSvtl9P6W1Ullf7i3VOGvACoxkecrBOl6csallsvApEQ9Wpr/+oyuZGGafQexh6caDAiK9/+rBhD08rLAAyIRCgkIzvb/qwYQ/wQSAhgCIAFCHQoDGLEXShYKFMFVg4uHhrv8AnmKVibGM/US7T9ZUgB6DAiK9/+rBhDz8rLAAw=="},{"b64Body":"Cg8KCQjP9v+rBhCJBRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoiChYiFMFVg4uHhrv8AnmKVibGM/US7T9ZEKCNBiIEc3vDyQ==","b64Record":"CiUIFiIDGLEXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC6J2jWvGA0QhHX6gYJGty+0ODKE6FDxJfNhkJp/pR9FI41K9qEasnxzf5x1ZD+pg4aDAiL9/+rBhDzrsLkASIPCgkIz/b/qwYQiQUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA19oCOowCCgMYsRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDxBFIWCgkKAhgCEP+ttQUKCQoCGGIQgK61BQ=="},{"b64Body":"Cg8KCQjP9v+rBhCRBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46UAoDGK8XEICS9AEiRHpp/1cAAAAAAAAAAAAAAAChAwGisb+ElDKNLvFf0ttXZ9FLQaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEaq7zN3u/wAR","b64Record":"CiUIFiIDGK8XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDEz8S95fUHuEaU5bxtmlrJNB3PLhKGADuquaN48UDr0RGre16lf5llATUQlpS/HyYaCwiM9/+rBhD7xuoIIg8KCQjP9v+rBhCRBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICYq2w68wQKAxivFyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAgAAABAAAAAAAAAAAAAAAAAAAAAogKjDATLMAgoDGLAXEoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAACAAAAEAAAAAAAAAAAAAAAAAAAABogf/UcN/el1yHjiMVkVn6QrkC/qnetzmH3VdUwpt//Y8EiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqOgMYshdyBwoDGLAXEANyBwoDGLIXEAFSGAoKCgIYAhD/r9bYAQoKCgIYYhCAsNbYAQ=="},{"b64Body":"ChEKCQjP9v+rBhCRBRICGAIgAUI4GiISICcaOWwTiKqCf49H0muqXBxnN/Qew9frxe2+7N7bMWGFQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGLIXEjDhP2X5lGmrhVrLY+qBBDOrd/Xf0vHcbzbQj0O1KOpT9CRic9zbUS/MbT8/3sG+msIaCwiM9/+rBhD8xuoIIhEKCQjP9v+rBhCRBRICGAIgAUIdCgMYshdKFgoUwVWDi4eGu/wCeYpWJsYz9RLtP1lSAHoLCIz3/6sGEPvG6gg="}]},"canUseAliasesInPrecompilesAndContractKeys":{"placeholderNum":2995,"encodedItems":[{"b64Body":"Cg8KCQjU9v+rBhClBRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIC8kodSFp0UAz3dWEot2QnkcxXn0B4bVSwMOBwLPpFM4EICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGLQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBEYc+m0RGSBnQcjppIcpahG7ybKG0iyV1cOg51ZnBgbIE8LwfJwjXF+9LbU0BRjA0aDAiQ9/+rBhDzw9naASIPCgkI1Pb/qwYQpQUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxi0FxCAqNa5Bw=="},{"b64Body":"Cg8KCQjU9v+rBhCnBRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiQxdqvBhDQ4o/KAxptCiISILhO7bWWSBVLTtK1TZfIGsEu8xbm5EzHr05CdIcHx8F5CiM6IQMYhghpw6XQPmp3qFG3OMWdjPqL2flyHnAvPLOIt+91XgoiEiAqeb/lHeR0ZjARk/6qiBQs92bxikXH7pes1CaRAsOjnSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGLUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAYSQvmHoRRs4dz5eQuCeNUwGmYi7Cavm2aScTkufmmwCwVn01kHzMD+wNY535hLk8aCgiR9/+rBhCT80ciDwoJCNT2/6sGEKcFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjV9v+rBhCrBRICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxi1FyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMjgwMjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzQyNWRiZjk2MTQ2MTAwNWM1NzgwNjM2N2Q3MzFkMzE0NjEwMDhjNTc4MDYzNzA3YzUwMTkxNDYxMDBiYzU3ODA2M2FiZjdiZmQ4MTQ2MTAwZDg1NzgwNjNjYTk3MjE2MTE0NjEwMGY0NTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDlhMTU2NWI2MTAxMTA1NjViNjA0MDUxNjEwMDgzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwYTY2MDA0ODAzNjAzODEwMTkwNjEwMGExOTE5MDYxMDlhMTU2NWI2MTAxNDY1NjViNjA0MDUxNjEwMGIzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZDY2MDA0ODAzNjAzODEwMTkwNjEwMGQxOTE5MDYxMGExNTU2NWI2MTAxN2M1NjViMDA1YjYxMDBmMjYwMDQ4MDM2MDM4MTAxOTA2MTAwZWQ5MTkwNjEwYTc4NTY1YjYxMDI1ZjU2NWIwMDViNjEwMTBlNjAwNDgwMzYwMzgxMDE5MDYxMDEwOTkxOTA2MTBhMTU1NjViNjEwM2E5NTY1YjAwNWI2MDAwNjEwMTNlODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNDhjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTc0ODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNWFhNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTg4MzA4MzYxMDZjODU2NWI5MDUwNjAxNjYwMDMwYjgxMTQ2MTAxZDA1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAxYzc5MDYxMGIwMjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDk0MjU0MDM4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDIyOTkxOTA2MTBiMzE1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDI0MzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDI1NzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwZmY2MGY4MWIzMDgzNjA0MDUxODA2MDIwMDE2MTAyNzg5MDYxMDhmODU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwNjA0MDUxNjAyMDAxNjEwMjljOTE5MDYxMGJjNjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDYwNDA1MTYwMjAwMTYxMDJjNTk0OTM5MjkxOTA2MTBjOTM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDAwMWM5MDUwODE2MDQwNTE2MTAyZWQ5MDYxMDhmODU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAzMGQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDNhNTU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDYxMDNiNTMwODM2MTA3ZTA1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwM2ZkNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwM2Y0OTA2MTBkMmQ1NjViNjA0MDUxODA5MTAzOTBmZDViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2VhNThjZTIxODM2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTA0NTY5MTkwNjEwYjMxNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA0NzA1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA0ODQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZWNhMzY5MTc2MGUwMWI4ODg4ODg4ODYwNDA1MTYwMjQwMTYxMDRjOTk0OTM5MjkxOTA2MTBkNWM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTMzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNTcwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTc1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA1ODY1NzYwMTU2MTA1OWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA1OWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5NDkzNTA1MDUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM1Y2ZjOTAxMTYwZTAxYjg4ODg4ODg4NjA0MDUxNjAyNDAxNjEwNWU3OTQ5MzkyOTE5MDYxMGQ1YzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA2NTE5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2OGU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2OTM1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDZhNDU3NjAxNTYxMDZiOTU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDZiODkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzA5OTc5NGU4NjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDcwMTkyOTE5MDYxMGUwNzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA3NmI5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA3YTg1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA3YWQ1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDdiZTU3NjAxNTYxMDdkMzU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDdkMjkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwwQS1duN7FXWNQWSyFIzoHEHABnI35c8kMA8nIs8XkylGQi9kaqsWNaP3uPWEv7JMGgwIkff/qwYQ0/HI5QEiDwoJCNX2/6sGEKsFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjV9v+rBhCxBRICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxi1FyKAIDViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA4MTk5MjkxOTA2MTBlMDc1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwODgzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwOGMwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwOGM1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA4ZDY1NzYwMTU2MTA4ZWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA4ZWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTE5OWM4MDYxMGUzMTgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDkzNTgyNjEwOTBhNTY1YjkwNTA5MTkwNTA1NjViNjEwOTQ1ODE2MTA5MmE1NjViODExNDYxMDk1MDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk2MjgxNjEwOTNjNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTYwMDcwYjkwNTA5MTkwNTA1NjViNjEwOTdlODE2MTA5Njg1NjViODExNDYxMDk4OTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk5YjgxNjEwOTc1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwOWI4NTc2MTA5Yjc2MTA5MDU1NjViNWI2MDAwNjEwOWM2ODU4Mjg2MDE2MTA5NTM1NjViOTI1MDUwNjAyMDYxMDlkNzg1ODI4NjAxNjEwOThjNTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTA5ZjQ4MTYxMDllMTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwYTBmNjAwMDgzMDE4NDYxMDllYjU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhMmI1NzYxMGEyYTYxMDkwNTU2NWI1YjYwMDA2MTBhMzk4NDgyODUwMTYxMDk1MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMGE1NTgxNjEwYTQyNTY1YjgxMTQ2MTBhNjA1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTBhNzI4MTYxMGE0YzU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhOGU1NzYxMGE4ZDYxMDkwNTU2NWI1YjYwMDA2MTBhOWM4NDgyODUwMTYxMGE2MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0ZTY1NzY2NTcyMjA2NTZlNjQ3MzIwNzc2NTZjNmMyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBhZWM2MDEwODM2MTBhYTU1NjViOTE1MDYxMGFmNzgyNjEwYWI2NTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGIxYjgxNjEwYWRmNTY1YjkwNTA5MTkwNTA1NjViNjEwYjJiODE2MTA5MmE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMGI0NjYwMDA4MzAxODQ2MTBiMjI1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMGI4MDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMGI2NTU2NWI4MzgxMTExNTYxMGI4ZjU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwYmEwODI2MTBiNGM1NjViNjEwYmFhODE4NTYxMGI1NzU2NWI5MzUwNjEwYmJhODE4NTYwMjA4NjAxNjEwYjYyNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiZDI4Mjg0NjEwYjk1NTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwN2ZmZjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjMjQ2MTBjMWY4MjYxMGJkZDU2NWI2MTBjMDk1NjViODI1MjUwNTA1NjViNjAwMDgxNjA2MDFiOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzQyODI2MTBjMmE1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzU0ODI2MTBjMzc1NjViOTA1MDkxOTA1MDU2NWI2MTBjNmM2MTBjNjc4MjYxMDkyYTU2NWI2MTBjNDk1NjViODI1MjUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjOGQ2MTBjODg4MjYxMGE0MjU2NWI2MTBjNzI1NjViODI1MjUwNTA1NjViNjAwMDYxMGM5ZjgyODc2MTBjMTM1NjViNjAwMTgyMDE5MTUwNjEwY2FmODI4NjYxMGM1YjU2NWI2MDE0ODIwMTkxNTA2MTBjYmY4Mjg1NjEwYzdjNTY1YjYwMjA4MjAxOTE1MDYxMGNjZjgyODQ2MTBjN2M1NjViNjAyMDgyMDE5MTUwODE5MDUwOTU5NDUwNTA1MDUwNTA1NjViN2Y1NzY1NmM2YzJjMjA0OTIwNmU2NTc2NjU3MjIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBkMTc2MDBlODM2MTBhYTU1NjViOTE1MDYxMGQyMjgyNjEwY2UxNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGQ0NjgxNjEwZDBhNTY1YjkwNTA5MTkwNTA1NjViNjEwZDU2ODE2MTA5Njg1NjViODI1MjUwNTA1NjViNjAwMDYwODA4MjAxOTA1MDYxMGQ3MTYwMDA4MzAxODc2MTBiMjI1NjViNjEwZDdlNjAyMDgzMDE4NjYxMGIyMjU2NWI2MTBkOGI2MDQwODMwMTg1NjEwYjIyNTY1YjYxMGQ5ODYwNjA4MzAxODQ2MTBkNGQ1NjViOTU5NDUwNTA1MDUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTBkYjc4MTYxMGRhMTU2NWI4MTE0NjEwZGMyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZGQ0ODE2MTBkYWU1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZGYwNTc2MTBkZWY2MTA5MDU1NjViNWI2MDAwNjEwZGZlODQ4Mjg1MDE2MTBkYzU1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMGUxYzYwMDA4MzAxODU2MTBiMjI1NjViNjEwZTI5NjAyMDgzMDE4NDYxMGIyMjU2NWI5MzkyNTA1MDUwNTZmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTk3YzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzBhMjg0Y2I2MTQ2MTAwNTE1NzgwNjM0ODRhOTVhOTE0NjEwMDZkNTc4MDYzZDk0MjU0MDMxNDYxMDA4OTU3ODA2M2VhNThjZTIxMTQ2MTAwYTU1NzViNjAwMDgwZmQ1YjYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwOTM5NTY1YjYxMDBjMTU2NWIwMDViNjEwMDg3NjAwNDgwMzYwMzgxMDE5MDYxMDA4MjkxOTA2MTA5Mzk1NjViNjEwMTI1NTY1YjAwNWI2MTAwYTM2MDA0ODAzNjAzODEwMTkwNjEwMDllOTE5MDYxMDk5NTU2NWI2MTAyMzY1NjViMDA1YjYxMDBiZjYwMDQ4MDM2MDM4MTAxOTA2MTAwYmE5MTkwNjEwOTk1NTY1YjYxMDI4ZTU2NWIwMDViNjAwMDgwNjAwMDYxMDBkMjg1NjAwMDg2NjEwMmU2NTY1YjkyNTA5MjUwOTI1MDYwMTY2MDAzMGI4MzE0NjEwMTFlNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMTE1OTA2MTBhMWY1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDUwNTA1MDU2NWI2MDAwNjA0MDUxNjEwMTMzOTA2MTA2OGU1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMwYTI4NGNiNjYwZTAxYjg0ODQ2MDQwNTE2MDI0MDE2MTAxODQ5MjkxOTA2MTBiOTg1NjViNjA0MDUxNjAyMDgxODMwMzAzODE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwwB5i8G+jtx32bBCQLegj6/C2OrT94eMW2dz3ieXeB/zmUsknf8pG0gKYciUwvcX4GgsIkvf/qwYQ09/0CCIPCgkI1fb/qwYQsQUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjW9v+rBhC3BRICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxi1FyKAIDUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxZWU5MTkwNjEwYzA0NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMDIyOTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDIyZTU2NWI2MDYwOTE1MDViNTA1MDUwNTA1MDUwNTY1YjYwMDA2MTAyNDIzMDgzNjEwNDVlNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDI4YTU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTYxMDI4MTkwNjEwYzY3NTY1YjYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1NjViNjAwMDYxMDI5YTMwODM2MTA1NzY1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwMmUyNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMmQ5OTA2MTBjZDM1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDU2NWI2MDAwODA2MDYwNjAwMDgwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzI3OGUwYjg4NjBlMDFiODk4OTg5NjA0MDUxNjAyNDAxNjEwMzI0OTM5MjkxOTA2MTBkMTY1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMzhlOTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwM2NiNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwM2QwNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA0MmM1NzYwMTU2MDAwODA2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAzZjg1NzYxMDNmNzYxMDcyMzU2NWI1YjYwNDA1MTkwODA4MjUyODA2MDIwMDI2MDIwMDE4MjAxNjA0MDUyODAxNTYxMDQyNjU3ODE2MDIwMDE2MDIwODIwMjgwMzY4MzM3ODA4MjAxOTE1MDUwOTA1MDViNTA2MTA0NDE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA0NDA5MTkwNjEwZWIyNTY1YjViODI2MDAzMGI5MjUwODA5NTUwODE5NjUwODI5NzUwNTA1MDUwNTA1MDkzNTA5MzUwOTM5MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDk5Nzk0ZTg2MGUwMWI4Njg2NjA0MDUxNjAyNDAxNjEwNDk3OTI5MTkwNjEwZjIxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE5MTY2MDIwODIwMTgwNTE3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODM4MTgzMTYxNzgzNTI1MDUwNTA1MDYwNDA1MTYxMDUwMTkxOTA2MTBjMDQ1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg2NWFmMTkxNTA1MDNkODA2MDAwODExNDYxMDUzZTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDU0MzU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgxNjEwNTU0NTc2MDE1NjEwNTY5NTY1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwNTY4OTE5MDYxMGY0YTU2NWI1YjYwMDMwYjkyNTA1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA1YWY5MjkxOTA2MTBmMjE1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNjE5OTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNjU2NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNjViNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA2NmM1NzYwMTU2MTA2ODE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA2ODA5MTkwNjEwZjRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTA5Y2Y4MDYxMGY3ODgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDZkYTgyNjEwNmFmNTY1YjkwNTA5MTkwNTA1NjViNjEwNmVhODE2MTA2Y2Y1NjViODExNDYxMDZmNTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDcwNzgxNjEwNmUxNTY1YjkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMDc1YjgyNjEwNzEyNTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMDc3YTU3NjEwNzc5NjEwNzIzNTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMDc4ZDYxMDY5YjU2NWI5MDUwNjEwNzk5ODI4MjYxMDc1MjU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdiOTU3NjEwN2I4NjEwNzIzNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdlZjU3NjEwN2VlNjEwNzIzNTY1YjViNjEwN2Y4ODI2MTA3MTI1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI4MjgxODMzNzYwMDA4MzgzMDE1MjUwNTA1MDU2NWI2MDAwNjEwODI3NjEwODIyODQ2MTA3ZDQ1NjViNjEwNzgzNTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMDg0MzU3NjEwODQyNjEwN2NmNTY1YjViNjEwODRlODQ4Mjg1NjEwODA1NTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwODZiNTc2MTA4NmE2MTA3MGQ1NjViNWI4MTM1NjEwODdiODQ4MjYwMjA4NjAxNjEwODE0NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA4OTc2MTA4OTI4NDYxMDc5ZTU2NWI2MTA3ODM1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjAyMDg0MDI4MzAxODU4MTExMTU2MTA4YmE1NzYxMDhiOTYxMDdjYTU2NWI1YjgzNWI4MTgxMTAxNTYxMDkwMTU3ODAzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDhkZjU3NjEwOGRlNjEwNzBkNTY1YjViODA4NjAxNjEwOGVjODk4MjYxMDg1NjU2NWI4NTUyNjAyMDg1MDE5NDUwNTA1MDYwMjA4MTAxOTA1MDYxMDhiYzU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA5MjA1NzYxMDkxZjYxMDcwZDU2NWI1YjgxMzU2MTA5MzA4NDgyNjAyMDg2MDE2MTA4ODQ1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTA5NTA1NzYxMDk0ZjYxMDZhNTU2NWI1YjYwMDA2MTA5NWU4NTgyODYwMTYxMDZmODU2NWI5MjUwNTA2MDIwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTdmNTc2MTA5N2U2MTA2YWE1NjViNWI2MTA5OGI4NTgyODYwMTYxMDkwYjU2NWI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw/QOiEbDp8ZrLZgBx+FKjeBUav9mlvfc1kVBqmw1/Sn43ZXJqOLX2AqIunHAAgJ7IGgwIkvf/qwYQ846F7gEiDwoJCNb2/6sGELcFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjX9v+rBhC9BRICGAISAhgDGPbR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxi1FyKAIDkyNTA5MjkwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwOWFiNTc2MTA5YWE2MTA2YTU1NjViNWI2MDAwNjEwOWI5ODQ4Mjg1MDE2MTA2Zjg1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjdmNDM2MTZlMjc3NDIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTA5NjAwNjgzNjEwOWMyNTY1YjkxNTA2MTBhMTQ4MjYxMDlkMzU2NWI2MDIwODIwMTkwNTA5MTkwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTBhMzg4MTYxMDlmYzU2NWI5MDUwOTE5MDUwNTY1YjYxMGE0ODgxNjEwNmNmNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwYWI0NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwYTk5NTY1YjgzODExMTE1NjEwYWMzNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTBhZDQ4MjYxMGE3YTU2NWI2MTBhZGU4MTg1NjEwYTg1NTY1YjkzNTA2MTBhZWU4MTg1NjAyMDg2MDE2MTBhOTY1NjViNjEwYWY3ODE2MTA3MTI1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiMGU4MzgzNjEwYWM5NTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTBiMmU4MjYxMGE0ZTU2NWI2MTBiMzg4MTg1NjEwYTU5NTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMGI0YTg1NjEwYTZhNTY1YjgwNjAwMDViODU4MTEwMTU2MTBiODY1Nzg0ODQwMzg5NTI4MTUxNjEwYjY3ODU4MjYxMGIwMjU2NWI5NDUwNjEwYjcyODM2MTBiMTY1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwYjRlNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTBiYWQ2MDAwODMwMTg1NjEwYTNmNTY1YjgxODEwMzYwMjA4MzAxNTI2MTBiYmY4MTg0NjEwYjIzNTY1YjkwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwYmRlODI2MTBhN2E1NjViNjEwYmU4ODE4NTYxMGJjODU2NWI5MzUwNjEwYmY4ODE4NTYwMjA4NjAxNjEwYTk2NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBjMTA4Mjg0NjEwYmQzNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI3ZjUzNmYyMDc1NmU2NjYxNjk3MjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGM1MTYwMDk4MzYxMDljMjU2NWI5MTUwNjEwYzVjODI2MTBjMWI1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwYzgwODE2MTBjNDQ1NjViOTA1MDkxOTA1MDU2NWI3ZjQ5NzQyNzczMjA3NTZlNjg2NTYxNzI2NDIwNmY2NjJlMmUyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGNiZDYwMTI4MzYxMDljMjU2NWI5MTUwNjEwY2M4ODI2MTBjODc1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwY2VjODE2MTBjYjA1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjEwZDEwODE2MTBjZjM1NjViODI1MjUwNTA1NjViNjAwMDYwNjA4MjAxOTA1MDYxMGQyYjYwMDA4MzAxODY2MTBhM2Y1NjViNjEwZDM4NjAyMDgzMDE4NTYxMGQwNzU2NWI4MTgxMDM2MDQwODMwMTUyNjEwZDRhODE4NDYxMGIyMzU2NWI5MDUwOTQ5MzUwNTA1MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjEwZDZhODE2MTBkNTQ1NjViODExNDYxMGQ3NTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMGQ4NzgxNjEwZDYxNTY1YjkyOTE1MDUwNTY1YjYxMGQ5NjgxNjEwY2YzNTY1YjgxMTQ2MTBkYTE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTBkYjM4MTYxMGQ4ZDU2NWI5MjkxNTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwZGQ0NTc2MTBkZDM2MTA3MjM1NjViNWI2MDIwODIwMjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBkZjg4MTYxMGRlNTU2NWI4MTE0NjEwZTAzNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZTE1ODE2MTBkZWY1NjViOTI5MTUwNTA1NjViNjAwMDYxMGUyZTYxMGUyOTg0NjEwZGI5NTY1YjYxMDc4MzU2NWI5MDUwODA4MzgyNTI2MDIwODIwMTkwNTA2MDIwODQwMjgzMDE4NTgxMTExNTYxMGU1MTU3NjEwZTUwNjEwN2NhNTY1YjViODM1YjgxODExMDE1NjEwZTdhNTc4MDYxMGU2Njg4ODI2MTBlMDY1NjViODQ1MjYwMjA4NDAxOTM1MDUwNjAyMDgxMDE5MDUwNjEwZTUzNTY1YjUwNTA1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMGU5OTU3NjEwZTk4NjEwNzBkNTY1YjViODE1MTYxMGVhOTg0ODI2MDIwODYwMTYxMGUxYjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTBlY2I1NzYxMGVjYTYxMDZhNTU2NWI1YjYwMDA2MTBlZDk4NjgyODcwMTYxMGQ3ODU2NWI5MzUwNTA2MDIwNjEwZWVhODY4Mjg3MDE2MTBkYTQ1NjViOTI1MDUwNjA0MDg0MDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGYwYjU3NjEwZjBhNjEwNmFhNTY1YjViNjEwZjE3ODY4Mjg3MDE2MTBlODQ1NjViOTE1MDUwOTI1MDkyNTA5MjU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwZjM2NjAwMDgzMDE4NTYxMGEzZjU2NWI2MTBmNDM2MDIwODMwMTg0NjEwYTNmNTY1YjkzOTI1MDUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZjYwNTc2MTBmNWY2MTA2YTU1NjViNWI2MDAwNjEwZjZlODQ4Mjg1MDE2MTBkNzg1NjViOTE1MDUwOTI5MTUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwOWFmODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDJiNTc2MDAwMzU2MGUwMWM4MDYzMGEyODRjYjYxNDYxMDAzMDU3NWI2MDAwODBmZDViNjEwMDRhNjAwNDgwMzYwMzgxMDE5MDYxMDA0NTkxOTA2MTA0YzY1NjViNjEwMDRjNTY1YjAwNWI2MDAwODA2MDAwNjEwMDVkODU2MDAwODY2MTAwYjA1NjViOTI1MDkyNTA5MjUwNjAxNjYwMDMwYjgzMTQ2MTAwYTk1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAwYTA5MDYxMDU3ZjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA4MDYwNjA2MDAwODA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMjc4ZTBiODg2MGUwMWI4OTg5ODk2MDQwNTE2MDI0MDE2MTAwZWU5MzkyOTE5MDYxMDcxYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxNTg5MTkwNjEwNzk1NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxOTU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxOWE1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDFmNjU3NjAxNTYwMDA4MDY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDFjMjU3NjEwMWMxNjEwMmIwNTY1YjViNjA0MDUxOTA4MDgyNTI4MDYwMjAwMjYwMjAwMTgyMDE2MDQwNTI4MDE1NjEwMWYwNTc4MTYwMjAwMTYwMjA4MjAyODAzNjgzMzc4MDgyMDE5MTUwNTA5MDUwNWI1MDYxMDIwYjU2NWI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwZbxu2yS7mqaKMkvQtIfdMvyM8W7SKefCZ23eXqR8bu0R+WD0UJQj5KkcMselrdPaGgsIk/f/qwYQm5O+EiIPCgkI1/b/qwYQvQUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjX9v+rBhDDBRICGAISAhgDGPbR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxi1FyKAIDgwODA2MDIwMDE5MDUxODEwMTkwNjEwMjBhOTE5MDYxMDkwYTU2NWI1YjgyNjAwMzBiOTI1MDgwOTU1MDgxOTY1MDgyOTc1MDUwNTA1MDUwNTA5MzUwOTM1MDkzOTA1MDU2NWI2MDAwNjA0MDUxOTA1MDkwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAyNjc4MjYxMDIzYzU2NWI5MDUwOTE5MDUwNTY1YjYxMDI3NzgxNjEwMjVjNTY1YjgxMTQ2MTAyODI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyOTQ4MTYxMDI2ZTU2NWI5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTAyZTg4MjYxMDI5ZjU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTAzMDc1NzYxMDMwNjYxMDJiMDU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTAzMWE2MTAyMjg1NjViOTA1MDYxMDMyNjgyODI2MTAyZGY1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzNDY1NzYxMDM0NTYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzN2M1NzYxMDM3YjYxMDJiMDU2NWI1YjYxMDM4NTgyNjEwMjlmNTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDNiNDYxMDNhZjg0NjEwMzYxNTY1YjYxMDMxMDU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTAzZDA1NzYxMDNjZjYxMDM1YzU2NWI1YjYxMDNkYjg0ODI4NTYxMDM5MjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDNmODU3NjEwM2Y3NjEwMjlhNTY1YjViODEzNTYxMDQwODg0ODI2MDIwODYwMTYxMDNhMTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNDI0NjEwNDFmODQ2MTAzMmI1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwNDQ3NTc2MTA0NDY2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA0OGU1NzgwMzU2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTA0NmM1NzYxMDQ2YjYxMDI5YTU2NWI1YjgwODYwMTYxMDQ3OTg5ODI2MTAzZTM1NjViODU1MjYwMjA4NTAxOTQ1MDUwNTA2MDIwODEwMTkwNTA2MTA0NDk1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwNGFkNTc2MTA0YWM2MTAyOWE1NjViNWI4MTM1NjEwNGJkODQ4MjYwMjA4NjAxNjEwNDExNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwNGRkNTc2MTA0ZGM2MTAyMzI1NjViNWI2MDAwNjEwNGViODU4Mjg2MDE2MTAyODU1NjViOTI1MDUwNjAyMDgzMDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDUwYzU3NjEwNTBiNjEwMjM3NTY1YjViNjEwNTE4ODU4Mjg2MDE2MTA0OTg1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0MzYxNmUyNzc0MjA2NTc2NjU2ZTIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTA1Njk2MDBiODM2MTA1MjI1NjViOTE1MDYxMDU3NDgyNjEwNTMzNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDU5ODgxNjEwNTVjNTY1YjkwNTA5MTkwNTA1NjViNjEwNWE4ODE2MTAyNWM1NjViODI1MjUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYxMDVjYjgxNjEwNWFlNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNjM3NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwNjFjNTY1YjgzODExMTE1NjEwNjQ2NTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA2NTc4MjYxMDVmZDU2NWI2MTA2NjE4MTg1NjEwNjA4NTY1YjkzNTA2MTA2NzE4MTg1NjAyMDg2MDE2MTA2MTk1NjViNjEwNjdhODE2MTAyOWY1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA2OTE4MzgzNjEwNjRjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YjE4MjYxMDVkMTU2NWI2MTA2YmI4MTg1NjEwNWRjNTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMDZjZDg1NjEwNWVkNTY1YjgwNjAwMDViODU4MTEwMTU2MTA3MDk1Nzg0ODQwMzg5NTI4MTUxNjEwNmVhODU4MjYxMDY4NTU2NWI5NDUwNjEwNmY1ODM2MTA2OTk1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwNmQxNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDYwODIwMTkwNTA2MTA3MzA2MDAwODMwMTg2NjEwNTlmNTY1YjYxMDczZDYwMjA4MzAxODU2MTA1YzI1NjViODE4MTAzNjA0MDgzMDE1MjYxMDc0ZjgxODQ2MTA2YTY1NjViOTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDc2ZjgyNjEwNWZkNTY1YjYxMDc3OTgxODU2MTA3NTk1NjViOTM1MDYxMDc4OTgxODU2MDIwODYwMTYxMDYxOTU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwN2ExODI4NDYxMDc2NDU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTA3YzI4MTYxMDdhYzU2NWI4MTE0NjEwN2NkNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwN2RmODE2MTA3Yjk1NjViOTI5MTUwNTA1NjViNjEwN2VlODE2MTA1YWU1NjViODExNDYxMDdmOTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDgwYjgxNjEwN2U1NTY1YjkyOTE1MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTA4MmM1NzYxMDgyYjYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDg1MDgxNjEwODNkNTY1YjgxMTQ2MTA4NWI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTA4NmQ4MTYxMDg0NzU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwODg2NjEwODgxODQ2MTA4MTE1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwOGE5NTc2MTA4YTg2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA4ZDI1NzgwNjEwOGJlODg4MjYxMDg1ZTU2NWI4NDUyNjAyMDg0MDE5MzUwNTA2MDIwODEwMTkwNTA2MTA4YWI1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwOGYxNTc2MTA4ZjA2MTAyOWE1NjViNWI4MTUxNjEwOTAxODQ4MjYwMjA4NjAxNjEwODczNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwMDA2MDYwODQ4NjAzMTIxNTYxMDkyMzU3NjEwOTIyNjEwMjMyNTY1YjViNjAwMDYxMDkzMTg2ODI4NzAxNjEwN2QwNTY1YjkzNTA1MDYwMjA2MTA5NDI4NjgyODcwMTYxMDdmYzU2NWI5MjUwNTA2MDQwODQwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTYzNTc2MTA5NjI2MTAyMzc1NjViNWI2MTA5NmY4NjgyODcwMTYxMDhkYzU2NWI5MTUwNTA5MjUwOTI1MDkyNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYTNkZmY5MDJkODg2Y2ZmNDUzZTY2MWQyODAzODIwYWUyNmFiNGY0NWE0OGZhMzE5NTBiZDAzMWYwOGQzZWJhYzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDU1MDQ1OTE4MjMwMjQ4NTQ0MjA2NThkNDk0NThiNGFlZDg0ZWYyYWZhZTI4OTg5ZTAzMTU2ODdmNTk5MTRiM2Y2NDczNmY2YzYzNDMwMDA4MGMwMDMzYTI2NDY5NzA2NjczNTgyMjEyMjA5OTA0NDBlYTM0MmY5YzgzMTE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwjLc/AdaqV+gurZUFtJj8yfJc6mQsqVJ9eAPI4OgHPmeJAqAE8IzgLVgcsrcGBE+rGgwIk/f/qwYQ2+Kk9wEiDwoJCNf2/6sGEMMFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjY9v+rBhDJBRICGAISAhgDGImGlS0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBSxIDGLUXIkRhYTkzOTZkOTY0YmMyM2RjYjU2MjY2MjVmNTBjMGYyZDZmM2U3ODA4NDMzY2U2NjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwRpIoqkNxkhTuvaMc2rr0MTTs7zWEhBzxsxo2Q8KX9mcivWFRWBWlESZWqu5dw98yGgsIlPf/qwYQs9TeGyIPCgkI2Pb/qwYQyQUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjY9v+rBhDLBRICGAISAhgDGJ/Xt6UBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CIQoDGLUXIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC1DpXyvpPBGDuwVDcE9cTSXhb0RiyAitudm4tQfj4qI2i0VBbzqKlFr2cuIPs4CC4aDAiU9/+rBhCr6OuYAiIPCgkI2Pb/qwYQywUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCC+JAHQrdSCgMYthcSglBggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjQl2/lhRhAFxXgGNn1zHTFGEAjFeAY3B8UBkUYQC8V4Bjq/e/2BRhANhXgGPKlyFhFGEA9FdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQmhVlthARBWW2BAUWEAg5GQYQn6VltgQFGAkQOQ81thAKZgBIA2A4EBkGEAoZGQYQmhVlthAUZWW2BAUWEAs5GQYQn6VltgQFGAkQOQ81thANZgBIA2A4EBkGEA0ZGQYQoVVlthAXxWWwBbYQDyYASANgOBAZBhAO2RkGEKeFZbYQJfVlsAW2EBDmAEgDYDgQGQYQEJkZBhChVWW2EDqVZbAFtgAGEBPoMwYACAVJBhAQAKkARz//////////////////////////8WhWEEjFZbkFCSkVBQVltgAGEBdIMwYACAVJBhAQAKkARz//////////////////////////8WhWEFqlZbkFCSkVBQVltgAGEBiDCDYQbIVluQUGAWYAMLgRRhAdBXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhAceQYQsCVltgQFGAkQOQ/VtgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY9lCVAODYEBRgmP/////FmDgG4FSYAQBYQIpkZBhCzFWW2AAYEBRgIMDgWAAh4A7FYAVYQJDV2AAgP1bUFrxFYAVYQJXVz1gAIA+PWAA/VtQUFBQUFBWW2AAYP9g+Bswg2BAUYBgIAFhAniQYQj4VltgIIIBgQOCUmAfGWAfggEWYEBSUGBAUWAgAWECnJGQYQvGVltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgYEBRYCABYQLFlJOSkZBhDJNWW2BAUWAggYMDA4FSkGBAUoBRkGAgASBgAByQUIFgQFFhAu2QYQj4VluBkGBAUYCRA5BgAPWQUIAVgBVhAw1XPWAAgD49YAD9W1BgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAc///////////////////////////FmAAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xYUYQOlV2AAgP1bUFBWW2AAYQO1MINhB+BWW5BQYBZgAwuBFGED/VdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWED9JBhDS1WW2BAUYCRA5D9W2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZj6ljOIYNgQFGCY/////8WYOAbgVJgBAFhBFaRkGELMVZbYABgQFGAgwOBYACHgDsVgBVhBHBXYACA/VtQWvEVgBVhBIRXPWAAgD49YAD9W1BQUFBQUFZbYACAYABhAWdz//////////////////////////8WY+yjaRdg4BuIiIiIYEBRYCQBYQTJlJOSkZBhDVxWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFM5GQYQvGVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEFcFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFdVZbYGCRUFtQkVCRUIFhBYZXYBVhBZtWW4CAYCABkFGBAZBhBZqRkGEN2lZbW2ADC5JQUFCUk1BQUFBWW2AAgGAAYQFnc///////////////////////////FmNc/JARYOAbiIiIiGBAUWAkAWEF55STkpGQYQ1cVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhBlGRkGELxlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhBo5XYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBpNWW2BgkVBbUJFQkVCBYQakV2AVYQa5VluAgGAgAZBRgQGQYQa4kZBhDdpWW1tgAwuSUFBQlJNQUFBQVltgAIBgAGEBZ3P//////////////////////////xZjCZeU6GDgG4aGYEBRYCQBYQcBkpGQYQ4HVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhB2uRkGELxlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhB6hXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hB61WW2BgkVBbUJFQkVCBYQe+V2AVYQfTVluAgGAgAZBRgQGQYQfSkZBhDdpWW1tgAwuSUFBQkpFQUFZbYACAYABhAWdz//////////////////////////8WY0kUa95g4BuGhmBAUWAkAWEIGZKRkGEOB1ZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQiDkZBhC8ZWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQjAV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQjFVltgYJFQW1CRUJFQgWEI1ldgFWEI61ZbgIBgIAGQUYEBkGEI6pGQYQ3aVltbYAMLklBQUJKRUFBWW2EZnIBhDjGDOQGQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhCTWCYQkKVluQUJGQUFZbYQlFgWEJKlZbgRRhCVBXYACA/VtQVltgAIE1kFBhCWKBYQk8VluSkVBQVltgAIFgBwuQUJGQUFZbYQl+gWEJaFZbgRRhCYlXYACA/VtQVltgAIE1kFBhCZuBYQl1VluSkVBQVltgAIBgQIOFAxIVYQm4V2EJt2EJBVZbW2AAYQnGhYKGAWEJU1ZbklBQYCBhCdeFgoYBYQmMVluRUFCSUJKQUFZbYACBkFCRkFBWW2EJ9IFhCeFWW4JSUFBWW2AAYCCCAZBQYQoPYACDAYRhCetWW5KRUFBWW2AAYCCChAMSFWEKK1dhCiphCQVWW1tgAGEKOYSChQFhCVNWW5FQUJKRUFBWW2AAgZBQkZBQVlthClWBYQpCVluBFGEKYFdgAID9W1BWW2AAgTWQUGEKcoFhCkxWW5KRUFBWW2AAYCCChAMSFWEKjldhCo1hCQVWW1tgAGEKnISChQFhCmNWW5FQUJKRUFBWW2AAgoJSYCCCAZBQkpFQUFZbf05ldmVyIGVuZHMgd2VsbC4AAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGEK7GAQg2EKpVZbkVBhCveCYQq2VltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhCxuBYQrfVluQUJGQUFZbYQsrgWEJKlZbglJQUFZbYABgIIIBkFBhC0ZgAIMBhGELIlZbkpFQUFZbYACBUZBQkZBQVltgAIGQUJKRUFBWW2AAW4OBEBVhC4BXgIIBUYGEAVJgIIEBkFBhC2VWW4OBERVhC49XYACEhAFSW1BQUFBWW2AAYQuggmELTFZbYQuqgYVhC1dWW5NQYQu6gYVgIIYBYQtiVluAhAGRUFCSkVBQVltgAGEL0oKEYQuVVluRUIGQUJKRUFBWW2AAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAghaQUJGQUFZbYACBkFCRkFBWW2EMJGEMH4JhC91WW2EMCVZbglJQUFZbYACBYGAbkFCRkFBWW2AAYQxCgmEMKlZbkFCRkFBWW2AAYQxUgmEMN1ZbkFCRkFBWW2EMbGEMZ4JhCSpWW2EMSVZbglJQUFZbYACBkFCRkFBWW2EMjWEMiIJhCkJWW2EMclZbglJQUFZbYABhDJ+Ch2EME1ZbYAGCAZFQYQyvgoZhDFtWW2AUggGRUGEMv4KFYQx8VltgIIIBkVBhDM+ChGEMfFZbYCCCAZFQgZBQlZRQUFBQUFZbf1dlbGwsIEkgbmV2ZXIhAAAAAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGENF2AOg2EKpVZbkVBhDSKCYQzhVltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhDUaBYQ0KVluQUJGQUFZbYQ1WgWEJaFZbglJQUFZbYABggIIBkFBhDXFgAIMBh2ELIlZbYQ1+YCCDAYZhCyJWW2ENi2BAgwGFYQsiVlthDZhgYIMBhGENTVZblZRQUFBQUFZbYACBYAMLkFCRkFBWW2ENt4FhDaFWW4EUYQ3CV2AAgP1bUFZbYACBUZBQYQ3UgWENrlZbkpFQUFZbYABgIIKEAxIVYQ3wV2EN72EJBVZbW2AAYQ3+hIKFAWENxVZbkVBQkpFQUFZbYABgQIIBkFBhDhxgAIMBhWELIlZbYQ4pYCCDAYRhCyJWW5OSUFBQVv5ggGBAUjSAFWEAEFdgAID9W1BhGXyAYQAgYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMKKEy2FGEAUVeAY0hKlakUYQBtV4Bj2UJUAxRhAIlXgGPqWM4hFGEApVdbYACA/VthAGtgBIA2A4EBkGEAZpGQYQk5VlthAMFWWwBbYQCHYASANgOBAZBhAIKRkGEJOVZbYQElVlsAW2EAo2AEgDYDgQGQYQCekZBhCZVWW2ECNlZbAFthAL9gBIA2A4EBkGEAupGQYQmVVlthAo5WWwBbYACAYABhANKFYACGYQLmVluSUJJQklBgFmADC4MUYQEeV2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBYQEVkGEKH1ZbYEBRgJEDkP1bUFBQUFBWW2AAYEBRYQEzkGEGjlZbYEBRgJEDkGAA8IAVgBVhAU9XPWAAgD49YAD9W1CQUIBz//////////////////////////8WYwooTLZg4BuEhGBAUWAkAWEBhJKRkGELmFZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQHukZBhDARWW2AAYEBRgIMDgYVa9JFQUD2AYACBFGECKVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmECLlZbYGCRUFtQUFBQUFBWW2AAYQJCMINhBF5WW5BQYBZgAwuBFGECildgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWECgZBhDGdWW2BAUYCRA5D9W1BQVltgAGECmjCDYQV2VluQUGAWYAMLgRRhAuJXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhAtmQYQzTVltgQFGAkQOQ/VtQUFZbYACAYGBgAIBhAWdz//////////////////////////8WYyeOC4hg4BuJiYlgQFFgJAFhAySTkpGQYQ0WVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhA46RkGEMBFZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhA8tXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hA9BWW2BgkVBbUJFQkVCBYQQsV2AVYACAZ///////////gREVYQP4V2ED92EHI1ZbW2BAUZCAglKAYCACYCABggFgQFKAFWEEJleBYCABYCCCAoA2gzeAggGRUFCQUFtQYQRBVluAgGAgAZBRgQGQYQRAkZBhDrJWW1uCYAMLklCAlVCBllCCl1BQUFBQUJNQk1CTkFBWW2AAgGAAYQFnc///////////////////////////FmMJl5ToYOAbhoZgQFFgJAFhBJeSkZBhDyFWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFAZGQYQwEVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEFPldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFQ1ZbYGCRUFtQkVCRUIFhBVRXYBVhBWlWW4CAYCABkFGBAZBhBWiRkGEPSlZbW2ADC5JQUFCSkVBQVltgAIBgAGEBZ3P//////////////////////////xZjSRRr3mDgG4aGYEBRYCQBYQWvkpGQYQ8hVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhBhmRkGEMBFZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhBlZXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBltWW2BgkVBbUJFQkVCBYQZsV2AVYQaBVluAgGAgAZBRgQGQYQaAkZBhD0pWW1tgAwuSUFBQkpFQUFZbYQnPgGEPeIM5AZBWW2AAYEBRkFCQVltgAID9W2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEG2oJhBq9WW5BQkZBQVlthBuqBYQbPVluBFGEG9VdgAID9W1BWW2AAgTWQUGEHB4FhBuFWW5KRUFBWW2AAgP1bYABgHxlgH4MBFpBQkZBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EHW4JhBxJWW4EBgYEQZ///////////ghEXFWEHeldhB3lhByNWW1uAYEBSUFBQVltgAGEHjWEGm1ZbkFBhB5mCgmEHUlZbkZBQVltgAGf//////////4IRFWEHuVdhB7hhByNWW1tgIIICkFBgIIEBkFCRkFBWW2AAgP1bYACA/VtgAGf//////////4IRFWEH71dhB+5hByNWW1thB/iCYQcSVluQUGAggQGQUJGQUFZbgoGDN2AAg4MBUlBQUFZbYABhCCdhCCKEYQfUVlthB4NWW5BQgoFSYCCBAYSEhAERFWEIQ1dhCEJhB89WW1thCE6EgoVhCAVWW1CTklBQUFZbYACCYB+DARJhCGtXYQhqYQcNVltbgTVhCHuEgmAghgFhCBRWW5FQUJKRUFBWW2AAYQiXYQiShGEHnlZbYQeDVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQi6V2EIuWEHylZbW4NbgYEQFWEJAVeANWf//////////4ERFWEI31dhCN5hBw1WW1uAhgFhCOyJgmEIVlZbhVJgIIUBlFBQUGAggQGQUGEIvFZbUFBQk5JQUFBWW2AAgmAfgwESYQkgV2EJH2EHDVZbW4E1YQkwhIJgIIYBYQiEVluRUFCSkVBQVltgAIBgQIOFAxIVYQlQV2EJT2EGpVZbW2AAYQlehYKGAWEG+FZbklBQYCCDATVn//////////+BERVhCX9XYQl+YQaqVltbYQmLhYKGAWEJC1ZbkVBQklCSkFBWW2AAYCCChAMSFWEJq1dhCaphBqVWW1tgAGEJuYSChQFhBvhWW5FQUJKRUFBWW2AAgoJSYCCCAZBQkpFQUFZbf0Nhbid0IQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGEKCWAGg2EJwlZbkVBhChSCYQnTVltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhCjiBYQn8VluQUJGQUFZbYQpIgWEGz1ZbglJQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAgZBQYCCCAZBQkZBQVltgAIFRkFCRkFBWW2AAgoJSYCCCAZBQkpFQUFZbYABbg4EQFWEKtFeAggFRgYQBUmAggQGQUGEKmVZbg4ERFWEKw1dgAISEAVJbUFBQUFZbYABhCtSCYQp6VlthCt6BhWEKhVZbk1BhCu6BhWAghgFhCpZWW2EK94FhBxJWW4QBkVBQkpFQUFZbYABhCw6Dg2EKyVZbkFCSkVBQVltgAGAgggGQUJGQUFZbYABhCy6CYQpOVlthCziBhWEKWVZbk1CDYCCCAoUBYQtKhWEKalZbgGAAW4WBEBVhC4ZXhIQDiVKBUWELZ4WCYQsCVluUUGELcoNhCxZWW5JQYCCKAZlQUGABgQGQUGELTlZbUIKXUIeVUFBQUFBQkpFQUFZbYABgQIIBkFBhC61gAIMBhWEKP1ZbgYEDYCCDAVJhC7+BhGELI1ZbkFCTklBQUFZbYACBkFCSkVBQVltgAGEL3oJhCnpWW2EL6IGFYQvIVluTUGEL+IGFYCCGAWEKllZbgIQBkVBQkpFQUFZbYABhDBCChGEL01ZbkVCBkFCSkVBQVlt/U28gdW5mYWlyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQxRYAmDYQnCVluRUGEMXIJhDBtWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEMgIFhDERWW5BQkZBQVlt/SXQncyB1bmhlYXJkIG9mLi4uAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQy9YBKDYQnCVluRUGEMyIJhDIdWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEM7IFhDLBWW5BQkZBQVltgAGf//////////4IWkFCRkFBWW2ENEIFhDPNWW4JSUFBWW2AAYGCCAZBQYQ0rYACDAYZhCj9WW2ENOGAggwGFYQ0HVluBgQNgQIMBUmENSoGEYQsjVluQUJSTUFBQUFZbYACBYAMLkFCRkFBWW2ENaoFhDVRWW4EUYQ11V2AAgP1bUFZbYACBUZBQYQ2HgWENYVZbkpFQUFZbYQ2WgWEM81ZbgRRhDaFXYACA/VtQVltgAIFRkFBhDbOBYQ2NVluSkVBQVltgAGf//////////4IRFWEN1FdhDdNhByNWW1tgIIICkFBgIIEBkFCRkFBWW2AAgZBQkZBQVlthDfiBYQ3lVluBFGEOA1dgAID9W1BWW2AAgVGQUGEOFYFhDe9WW5KRUFBWW2AAYQ4uYQ4phGENuVZbYQeDVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQ5RV2EOUGEHylZbW4NbgYEQFWEOeleAYQ5miIJhDgZWW4RSYCCEAZNQUGAggQGQUGEOU1ZbUFBQk5JQUFBWW2AAgmAfgwESYQ6ZV2EOmGEHDVZbW4FRYQ6phIJgIIYBYQ4bVluRUFCSkVBQVltgAIBgAGBghIYDEhVhDstXYQ7KYQalVltbYABhDtmGgocBYQ14VluTUFBgIGEO6oaChwFhDaRWW5JQUGBAhAFRZ///////////gREVYQ8LV2EPCmEGqlZbW2EPF4aChwFhDoRWW5FQUJJQklCSVltgAGBAggGQUGEPNmAAgwGFYQo/VlthD0NgIIMBhGEKP1Zbk5JQUFBWW2AAYCCChAMSFWEPYFdhD19hBqVWW1tgAGEPboSChQFhDXhWW5FQUJKRUFBW/mCAYEBSNIAVYQAQV2AAgP1bUGEJr4BhACBgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEAK1dgADVg4ByAYwooTLYUYQAwV1tgAID9W2EASmAEgDYDgQGQYQBFkZBhBMZWW2EATFZbAFtgAIBgAGEAXYVgAIZhALBWW5JQklCSUGAWYAMLgxRhAKlXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhAKCQYQV/VltgQFGAkQOQ/VtQUFBQUFZbYACAYGBgAIBhAWdz//////////////////////////8WYyeOC4hg4BuJiYlgQFFgJAFhAO6TkpGQYQcbVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAViRkGEHlVZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhAZVXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hAZpWW2BgkVBbUJFQkVCBYQH2V2AVYACAZ///////////gREVYQHCV2EBwWECsFZbW2BAUZCAglKAYCACYCABggFgQFKAFWEB8FeBYCABYCCCAoA2gzeAggGRUFCQUFtQYQILVluAgGAgAZBRgQGQYQIKkZBhCQpWW1uCYAMLklCAlVCBllCCl1BQUFBQUJNQk1CTkFBWW2AAYEBRkFCQVltgAID9W2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGECZ4JhAjxWW5BQkZBQVlthAneBYQJcVluBFGECgldgAID9W1BWW2AAgTWQUGEClIFhAm5WW5KRUFBWW2AAgP1bYABgHxlgH4MBFpBQkZBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EC6IJhAp9WW4EBgYEQZ///////////ghEXFWEDB1dhAwZhArBWW1uAYEBSUFBQVltgAGEDGmECKFZbkFBhAyaCgmEC31ZbkZBQVltgAGf//////////4IRFWEDRldhA0VhArBWW1tgIIICkFBgIIEBkFCRkFBWW2AAgP1bYACA/VtgAGf//////////4IRFWEDfFdhA3thArBWW1thA4WCYQKfVluQUGAggQGQUJGQUFZbgoGDN2AAg4MBUlBQUFZbYABhA7RhA6+EYQNhVlthAxBWW5BQgoFSYCCBAYSEhAERFWED0FdhA89hA1xWW1thA9uEgoVhA5JWW1CTklBQUFZbYACCYB+DARJhA/hXYQP3YQKaVltbgTVhBAiEgmAghgFhA6FWW5FQUJKRUFBWW2AAYQQkYQQfhGEDK1ZbYQMQVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQRHV2EERmEDV1ZbW4NbgYEQFWEEjleANWf//////////4ERFWEEbFdhBGthAppWW1uAhgFhBHmJgmED41ZbhVJgIIUBlFBQUGAggQGQUGEESVZbUFBQk5JQUFBWW2AAgmAfgwESYQStV2EErGECmlZbW4E1YQS9hIJgIIYBYQQRVluRUFCSkVBQVltgAIBgQIOFAxIVYQTdV2EE3GECMlZbW2AAYQTrhYKGAWEChVZbklBQYCCDATVn//////////+BERVhBQxXYQULYQI3VltbYQUYhYKGAWEEmFZbkVBQklCSkFBWW2AAgoJSYCCCAZBQkpFQUFZbf0Nhbid0IGV2ZW4hAAAAAAAAAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGEFaWALg2EFIlZbkVBhBXSCYQUzVltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhBZiBYQVcVluQUJGQUFZbYQWogWECXFZbglJQUFZbYABn//////////+CFpBQkZBQVlthBcuBYQWuVluCUlBQVltgAIFRkFCRkFBWW2AAgoJSYCCCAZBQkpFQUFZbYACBkFBgIIIBkFCRkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQY3V4CCAVGBhAFSYCCBAZBQYQYcVluDgREVYQZGV2AAhIQBUltQUFBQVltgAGEGV4JhBf1WW2EGYYGFYQYIVluTUGEGcYGFYCCGAWEGGVZbYQZ6gWECn1ZbhAGRUFCSkVBQVltgAGEGkYODYQZMVluQUJKRUFBWW2AAYCCCAZBQkZBQVltgAGEGsYJhBdFWW2EGu4GFYQXcVluTUINgIIIChQFhBs2FYQXtVluAYABbhYEQFWEHCVeEhAOJUoFRYQbqhYJhBoVWW5RQYQb1g2EGmVZbklBgIIoBmVBQYAGBAZBQYQbRVltQgpdQh5VQUFBQUFCSkVBQVltgAGBgggGQUGEHMGAAgwGGYQWfVlthBz1gIIMBhWEFwlZbgYEDYECDAVJhB0+BhGEGplZbkFCUk1BQUFBWW2AAgZBQkpFQUFZbYABhB2+CYQX9VlthB3mBhWEHWVZbk1BhB4mBhWAghgFhBhlWW4CEAZFQUJKRUFBWW2AAYQehgoRhB2RWW5FQgZBQkpFQUFZbYACBYAMLkFCRkFBWW2EHwoFhB6xWW4EUYQfNV2AAgP1bUFZbYACBUZBQYQffgWEHuVZbkpFQUFZbYQfugWEFrlZbgRRhB/lXYACA/VtQVltgAIFRkFBhCAuBYQflVluSkVBQVltgAGf//////////4IRFWEILFdhCCthArBWW1tgIIICkFBgIIEBkFCRkFBWW2AAgZBQkZBQVlthCFCBYQg9VluBFGEIW1dgAID9W1BWW2AAgVGQUGEIbYFhCEdWW5KRUFBWW2AAYQiGYQiBhGEIEVZbYQMQVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQipV2EIqGEDV1ZbW4NbgYEQFWEI0leAYQi+iIJhCF5WW4RSYCCEAZNQUGAggQGQUGEIq1ZbUFBQk5JQUFBWW2AAgmAfgwESYQjxV2EI8GECmlZbW4FRYQkBhIJgIIYBYQhzVluRUFCSkVBQVltgAIBgAGBghIYDEhVhCSNXYQkiYQIyVltbYABhCTGGgocBYQfQVluTUFBgIGEJQoaChwFhB/xWW5JQUGBAhAFRZ///////////gREVYQljV2EJYmECN1ZbW2EJb4aChwFhCNxWW5FQUJJQklCSVv6iZGlwZnNYIhIgo9/5AtiGz/RT5mHSgDggriarT0Wkj6MZUL0DHwjT66xkc29sY0MACAwAM6JkaXBmc1giEiBVBFkYIwJIVEIGWNSUWLSu2E7yr64omJ4DFWh/WZFLP2Rzb2xjQwAIDAAzomRpcGZzWCISIJkEQOo0L5yDEaqTltlkvCPctWJmJfUMDy1vPngIQzzmZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKO7tDDoDGLYXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAu2cgcKAxi2FxABUhYKCQoCGAIQg/ChDgoJCgIYYhCE8KEO"},{"b64Body":"Cg8KCQjZ9v+rBhDNBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGLYXEICS9AEiJKv3v9iqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCClVnopzwcKYQEwnsoP7qzus3kuU75T1lBRe3WqINOeCOFWehonq4hwXkpU4QkedcaCwiV9/+rBhC73dkgIg8KCQjZ9v+rBhDNBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICYq2w6pAIKAxi2FyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogKjDAToDGLcXcgcKAxi2FxACcgcKAxi3FxABUhgKCgoCGAIQ/6/W2AEKCgoCGGIQgLDW2AE="},{"b64Body":"ChEKCQjZ9v+rBhDNBRICGAIgAUIUQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGLcXEjBoDesnH46No4N8+jrML7rILPlMTYeKhXtlDrlT0+SCKzgoOFw7EbFHqpjZP6DNOYYaCwiV9/+rBhC83dkgIhEKCQjZ9v+rBhDNBRICGAIgAUIdCgMYtxdKFgoUHvKvlZunnPSo0Mx6IcWSRTAMFv1SAHoLCJX3/6sGELvd2SA="},{"b64Body":"Cg8KCQjZ9v+rBhDTBRICGAISAhgDGPHv7egCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAS8KDWZ1bmdpYmxlVG9rZW4SCFNYRkhKVkNGIOgHKgMYtBdqDAiVxdqvBhDQnYGGAg==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGLgXEjCnCte3McmKvHlnLklbuwCZBoxdO6u7B3laXWpxi/yVocWfGC1oO1niHcMAyhcCgU4aDAiV9/+rBhCDt8aiAiIPCgkI2fb/qwYQ0wUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWg8KAxi4FxIICgMYtBcQ0A9yCgoDGLgXEgMYtBc="},{"b64Body":"Cg8KCQja9v+rBhDVBRICGAISAhgDGMXCtfsCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAXkKEG5vbkZ1bmdpYmxlVG9rZW4SCE9FSk5TQkdYKgMYtBcyIhIg/Sr37g8SrYgxFPzMO+ofHaHTNWjn7iuQlqb3X9QCqxdSIhIg/Sr37g8SrYgxFPzMO+ofHaHTNWjn7iuQlqb3X9QCqxdqCwiWxdqvBhCw0PchiAEB","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGLkXEjBQE3WHFUgcMP/3IDhKvIQjMFXopEl1OfCAELZXV2zh7YsbXw/OXSGbM4iSg7O+uX0aCwiW9/+rBhDTlf0pIg8KCQja9v+rBhDVBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgByCgoDGLkXEgMYtBc="},{"b64Body":"Cg8KCQja9v+rBhDbBRICGAISAhgDGNPtlwgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjqoCEAoDGLkXGglQUklDRUxFU1M=","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1gBcgEBEjDeljNUBRujGMS2UJs8yOSBcJn4XW5vS6EfEWcd5cj+KNmpZQAKV2eOP4YCiFHpGl4aDAiW9/+rBhDrhOirAiIPCgkI2vb/qwYQ2wUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWhIKAxi5FxoLCgIYABIDGLQXGAE="},{"b64Body":"Cg8KCQjb9v+rBhDjBRICGAISAhgDGI6FUyICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOogIfCgMYuRdKGAoWIhQe8q+Vm6ec9KjQzHohxZJFMAwW/Q==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwub1sOL5kHz7v6VMAYQw6qwRW+r1PHIc2TBg7ucL8NyOq6HYLGl/4z1c62LbssugLGgsIl/f/qwYQw7eNNCIPCgkI2/b/qwYQ4wUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjb9v+rBhDlBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGLYXEICS9AEiJMqXIWEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuA==","b64Record":"CiUIFiIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBNFrfOIoC+M56rs2YNjdBT7F0oBqWo6os4dtA8GHGZmFQmcGdk0P2Upgb+E3W7k0caDAiX9/+rBhCDv7S1AiIPCgkI2/b/qwYQ5QUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOo0CCgMYthcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFSGAoKCgIYAhD/r9bYAQoKCgIYYhCAsNbYAQ=="},{"b64Body":"ChEKCQjb9v+rBhDlBRICGAIgAcICCgoDGLYXEgMYuBc=","b64Record":"CgIIFhIw+/FZvCyUq+AVSSbSbldwbZploL0AiloHwZjR63YrR0HVPM9vjtolAiPSUbBvB8dRGgwIl/f/qwYQhL+0tQIiEQoJCNv2/6sGEOUFEgIYAiABOnsKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYo4v0qUPvl7gFiREkUa94AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALtgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu4agMYthdSAHoMCJf3/6sGEIO/tLUC"},{"b64Body":"ChEKCQjb9v+rBhDlBRICGAIgAsICCgoDGLcXEgMYuBc=","b64Record":"CgIIFhIwCWzGPkQpQZ02MrS+gREYzTtOgB3uaMwzHLTw4Fil5/9SKIWFAHlhdqP02vgP0SYfGgwIl/f/qwYQhb+0tQIiEQoJCNv2/6sGEOUFEgIYAiACOnsKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYo4v0qULP9wAFiREkUa94AAAAAAAAAAAAAAAAe8q+Vm6ec9KjQzHohxZJFMAwW/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu4agMYtxdSAHoMCJf3/6sGEIO/tLUC"},{"b64Body":"Cg8KCQjc9v+rBhDnBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGLYXEICS9AEiJMqXIWEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQ==","b64Record":"CiUIFiIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCjFMhePPUuFnudkUmjr3t3MZgVaK2C0QfXPyffbjwf5GNzn7B8jRGP4YLZ7xRjrM0aCwiY9/+rBhDb8IY9Ig8KCQjc9v+rBhDnBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICYq2w6jQIKAxi2FyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogKjDAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjc9v+rBhDnBRICGAIgAcICCgoDGLYXEgMYuRc=","b64Record":"CgIIFhIwybKEhhc22ttRnAKEZ2RUTJLb5N1/1ycM4+raFpHZWk6pANpJz+jktOFSQMeEEI0+GgsImPf/qwYQ3PCGPSIRCgkI3Pb/qwYQ5wUSAhgCIAE6ewoDGOcCEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFiji/SpQ++XuAWJESRRr3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7lqAxi2F1IAegsImPf/qwYQ2/CGPQ=="},{"b64Body":"ChEKCQjc9v+rBhDnBRICGAIgAsICCgoDGLcXEgMYuRc=","b64Record":"CgIIFhIwPwH5fiwenKq91c1+DpzQCDqiw4q5uK9DREWEf5klUlNnlRocSvqgvcmIJ2y5E8WOGgsImPf/qwYQ3fCGPSIRCgkI3Pb/qwYQ5wUSAhgCIAI6ewoDGOcCEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFiji/SpQs/3AAWJESRRr3gAAAAAAAAAAAAAAAB7yr5Wbp5z0qNDMeiHFkkUwDBb9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7lqAxi3F1IAegsImPf/qwYQ2/CGPQ=="},{"b64Body":"Cg8KCQjc9v+rBhDpBRICGAISAhgDGID4vgEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnIwEhkKAxi4FxIICgMYtBcQxwESCAoDGLYXEMgBEhMKAxi5FxoMCgMYtBcSAxi2FxgB","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwnXMs4j3FQ/HkRzQMN12AGofCxTNWRS/yD0DEj0SrcKM5U49rJawy/ybDRU/JKHa6GgwImPf/qwYQi/PovgIiDwoJCNz2/6sGEOkFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAFoZCgMYuBcSCAoDGLQXEMcBEggKAxi2FxDIAVoTCgMYuRcaDAoDGLQXEgMYthcYAQ=="},{"b64Body":"Cg8KCQjd9v+rBhDrBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46UAoDGLYXEICS9AEiREJdv5YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABk","b64Record":"CiUIFiIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDdCN1o1K9viwEqhIZ6SHqUOH7Mj2t+9ydjfb8dDApa9m/Pko5pF/D7WK8DVrmIf5MaCwiZ9/+rBhCLn/5GIg8KCQjd9v+rBhDrBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICYq2w6rwIKAxi2FxIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFSGAoKCgIYAhD/r9bYAQoKCgIYYhCAsNbYAQ=="},{"b64Body":"ChEKCQjd9v+rBhDrBRICGAIgAXIbEhkKAxi4FxIICgMYthcQxwESCAoDGLcXEMgB","b64Record":"CgIIFhIwIeLdK6b228zZ3NsAfScaLI+D9cNoBGrhtGYo0JEOw5rNNpHVJMMXykHyqSbFrPGTGgsImff/qwYQjJ/+RiIRCgkI3fb/qwYQ6wUSAhgCIAE6uwEKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYoumZQwtDuAWKEAeyjaRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu2AAAAAAAAAAAAAAAAHvKvlZunnPSo0Mx6IcWSRTAMFv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGoDGLYXUgBaGQoDGLgXEggKAxi2FxDHARIICgMYtxcQyAF6CwiZ9/+rBhCLn/5G"},{"b64Body":"Cg8KCQjd9v+rBhDtBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46UAoDGLYXEICS9AEiRGfXMdMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB","b64Record":"CiUIFiIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBV997xftWVBpiZWboU5eB4yhMUMIMIbzMuX3o6iAEOl+0+rkvNbRFV4bR+s+6Ajj8aDAiZ9/+rBhDbjsfIAiIPCgkI3fb/qwYQ7QUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOq8CCgMYthcSIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiAqMMBUhgKCgoCGAIQ/6/W2AEKCgoCGGIQgLDW2AE="},{"b64Body":"ChEKCQjd9v+rBhDtBRICGAIgAXIVEhMKAxi5FxoMCgMYthcSAxi3FxgB","b64Record":"CgIIFhIwMEhh9FlgImjm6WSw37J5FuxvNMJG3995AvWPqKTdZb2sZDhYbiDdCl7cwB9DG92sGgwImff/qwYQ3I7HyAIiEQoJCN32/6sGEO0FEgIYAiABOrsBCgMY5wISIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWKIVuUKzQ7gFihAFc/JARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALtgAAAAAAAAAAAAAAAB7yr5Wbp5z0qNDMeiHFkkUwDBb9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFqAxi2F1IAWhMKAxi5FxoMCgMYthcSAxi3FxgBegwImff/qwYQ247HyAI="},{"b64Body":"Cg8KCQje9v+rBhDvBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGLYXEICS9AEiJHB8UBkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuA==","b64Record":"CiUIISIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCdLNPvDneRmR7DfC1k5Q/BfAWGZuii6XtWz6stjaipkME3x3/EzFxLZR6aXsE9JHoaCwia9/+rBhCLlLdRIg8KCQje9v+rBhDvBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICYq2w60gEaygEweDA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDk1MzZmMjA3NTZlNjY2MTY5NzIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwKICowwFSGAoKCgIYAhD/r9bYAQoKCgIYYhCAsNbYAQ=="},{"b64Body":"ChEKCQje9v+rBhDvBRICGAIgAcoCCgoDGLYXEgMYuBc=","b64Record":"CgMImAISMAsbgEnUQKhWgxsfsTVGXMgQpHzMrSzaCULMrOEfzo68ZsekI2ib0S7wR9+pkPQtGBoLCJr3/6sGEIyUt1EiEQoJCN72/6sGEO8FEgIYAiABOnsKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYo4v0qUKfm7gFiRAmXlOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALtgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu4agMYthdSAHoLCJr3/6sGEIuUt1E="},{"b64Body":"ChEKCQje9v+rBhDvBRICGAIgAsoCCgoDGLcXEgMYuBc=","b64Record":"CgMIwwESMApvUihQopaOJ6a6WO4jDtH2T0rk7+Li+izQx6BLFTQpmK/MBO4G+Q2eA1OXLqEa8hoLCJr3/6sGEI2Ut1EiEQoJCN72/6sGEO8FEgIYAiACOqUBCgMY5wISIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDGihUUkFOU0FDVElPTl9SRVFVSVJFU19aRVJPX1RPS0VOX0JBTEFOQ0VTKOL9KlDz/cABYkQJl5ToAAAAAAAAAAAAAAAAHvKvlZunnPSo0Mx6IcWSRTAMFv0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuGoDGLcXUgB6Cwia9/+rBhCLlLdR"},{"b64Body":"Cg8KCQje9v+rBhDxBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGLYXEICS9AEiJHB8UBkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQ==","b64Record":"CiUIISIDGLYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDqpz7+fE8gF+tzZR4XOuMnWqogfKu4J+THsCQv2Q0R69bwoh0Tm3wqZMFHTjrWkXUaDAia9/+rBhD7vffSAiIPCgkI3vb/qwYQ8QUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOtIBGsoBMHgwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA5NTM2ZjIwNzU2ZTY2NjE2OTcyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCiAqMMBUhgKCgoCGAIQ/6/W2AEKCgoCGGIQgLDW2AE="},{"b64Body":"ChEKCQje9v+rBhDxBRICGAIgAcoCCgoDGLYXEgMYuRc=","b64Record":"CgMImAISMOnO3Cmtn7kuUTiDkUPWAeUedWuxLapy1nRvBnBKtn6oJS275f8A9SNXr57XUR8dVRoMCJr3/6sGEPy999ICIhEKCQje9v+rBhDxBRICGAIgATp7CgMY5wISIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWKOL9KlCn5u4BYkQJl5ToAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuWoDGLYXUgB6DAia9/+rBhD7vffSAg=="},{"b64Body":"ChEKCQje9v+rBhDxBRICGAIgAsoCCgoDGLcXEgMYuRc=","b64Record":"CgMI+wESMF/2oVVKFjAcAeAchKuhbcnEOmtZ/fdVCBpcogp7iVrgoajZu//ivgPyvS0/YTWm4BoMCJr3/6sGEP2999ICIhEKCQje9v+rBhDxBRICGAIgAjqUAQoDGOcCEiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+xoXQUNDT1VOVF9TVElMTF9PV05TX05GVFMo4v0qUPP9wAFiRAmXlOgAAAAAAAAAAAAAAAAe8q+Vm6ec9KjQzHohxZJFMAwW/QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu5agMYtxdSAHoMCJr3/6sGEPu999IC"},{"b64Body":"Cg8KCQjf9v+rBhDzBRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo465AEKFiIUHvKvlZunnPSo0Mx6IcWSRTAMFv0QgJL0ASLEAQooTLYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJV29SdEhsRXNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGLcXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDxjZqdFxjTOVU2r1UN+oyyuKYDFhkz4N4ZeBexXBGbmrTZwRPDu0PwgXuFNMFBmOsaCwib9/+rBhDrmeZZIg8KCQjf9v+rBhDzBRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICYq2w6jQIKAxi3FyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogKjDAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjf9v+rBhDzBRICGAIgAaoCEAoDGLkXGglXb1J0SGxFc1M=","b64Record":"CgcIFlgCcgECEjBRXFefsSek58HIws4qaEligrf/hxoRiMVb0pepiwGeK+MGoTGy+IJPfxzt76fi88saCwib9/+rBhDsmeZZIhEKCQjf9v+rBhDzBRICGAIgATqdAwoDGOcCEqABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAijamBFQtcvuAWLkASeOC4gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlXb1J0SGxFc1MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoDGLcXUgBaEgoDGLkXGgsKAhgAEgMYtBcYAnoLCJv3/6sGEOuZ5lk="},{"b64Body":"Cg8KCQjf9v+rBhD1BRICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo465AEKFiIUHvKvlZunnPSo0Mx6IcWSRTAMFv0QgJL0ASLEAUhKlakAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJV29SdEhsRXNTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGLcXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAnQSE9+mKskKJ7OYX96WR1RMbCLurHW3IYULvQu1kO/+OhdgscFw+o2HsbQ5jsimQaDAib9/+rBhCbofDbAiIPCgkI3/b/qwYQ9QUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYtxcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6Axi6F3IHCgMYtxcQAnIHCgMYuhcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjf9v+rBhD1BRICGAIgAUIUQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGLoXEjCmx6MUXoQ7/oPiTcDsDFKgW4W5WFznOsxlbDW5skNHvyT+hyMQne6mSsCe2LEQaVYaDAib9/+rBhCcofDbAiIRCgkI3/b/qwYQ9QUSAhgCIAFCHQoDGLoXShYKFGq8oUmNdsXR49EMpSgaM+bLFsc2UgB6DAib9/+rBhCbofDbAg=="},{"b64Body":"ChEKCQjf9v+rBhD1BRICGAIgAqoCEAoDGLkXGglXb1J0SGxFc1M=","b64Record":"CgMIxgISMGUxCTsnP+eP9c+rnHlRWJXA0R3FTys+wyB0SvUZosC4DMc+ArcL69oEQMnydsj9qRoMCJv3/6sGEJ2h8NsCIhEKCQjf9v+rBhD1BRICGAIgAjqrAwoDGOcCEoABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaLElOVkFMSURfRlVMTF9QUkVGSVhfU0lHTkFUVVJFX0ZPUl9QUkVDT01QSUxFKNqYEVCq0+gBYuQBJ44LiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVdvUnRIbEVzUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAagMYtxdSAHoMCJv3/6sGEJuh8NsC"},{"b64Body":"Cg8KCQjg9v+rBhCVBhICGAISAhgDGI6FUyICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOogIfCgMYuRdKGEIWIhQe8q+Vm6ec9KjQzHohxZJFMAwW/Q==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwIclHdNB1rKI+cg73LrSGxOeW2akTqtwlzJeEuaN0LlzS6msYLtfKTAwJTZ3OUqnEGgwInPf/qwYQy7SxgAEiDwoJCOD2/6sGEJUGEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjg9v+rBhCXBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo465AEKFiIUHvKvlZunnPSo0Mx6IcWSRTAMFv0QgJL0ASLEAUhKlakAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALuQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPV29SdEhsRXNTLi4uTk9UAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGLcXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB8UeyHMxPNHc+Gco4hKMXr83rvBvTSafVJaW/IlbJelUCx6lFer3sus9ZA/+Gwis0aDAic9/+rBhDj1LnlAiIPCgkI4Pb/qwYQlwYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMYtxcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6Axi7F3IHCgMYtxcQA3IHCgMYuxcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQjg9v+rBhCXBhICGAIgAUIUQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGLsXEjCCh6DITgYEKWdVEItsILdL8mIeR+8SzM7aVfkTMladV8Ofi9hR1kPzolwMTgj2RysaDAic9/+rBhDk1LnlAiIRCgkI4Pb/qwYQlwYSAhgCIAFCHQoDGLsXShYKFH13psALABbdmGi3VWaD8OAXr3D2UgB6DAic9/+rBhDj1LnlAg=="},{"b64Body":"ChEKCQjg9v+rBhCXBhICGAIgAqoCFgoDGLkXGg9Xb1J0SGxFc1MuLi5OT1Q=","b64Record":"CgcIFlgDcgEDEjBDecA5CzT/fq8wUgYLefMP0rs/PO0aMqhrxhhR2zaT6hVNVsUZMHfvjHW58HGDvFsaDAic9/+rBhDl1LnlAiIRCgkI4Pb/qwYQlwYSAhgCIAI6nQMKAxjnAhKgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMo2pgRUOXS6AFi5AEnjguIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPV29SdEhsRXNTLi4uTk9UAAAAAAAAAAAAAAAAAAAAAABqAxi3F1IAWhIKAxi5FxoLCgIYABIDGLQXGAN6DAic9/+rBhDj1LnlAg=="},{"b64Body":"Cg8KCQjh9v+rBhCfBhICGAISAhgDGPmXvwEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnJ9CicKCAoDGLQXELMKChsKFiIUAAAAAAAAAAAAAAAAAAAAAAAAC7cQtAoSKgoDGLgXEgcKAxi0FxALEhoKFiIUAAAAAAAAAAAAAAAAAAAAAAAAC7cQDBImCgMYuRcaHwoDGLQXEhYiFAAAAAAAAAAAAAAAAAAAAAAAAAu3GAI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwSv8qf8XY7HzXFRonaIk/K7FYPThTgdWVa1CW0wD/T1bjDwHxNRRuSSbifFjKakhyGgwInff/qwYQ84HoiQEiDwoJCOH2/6sGEJ8GEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SFAoICgMYtBcQswoKCAoDGLcXELQKWhcKAxi4FxIHCgMYtBcQCxIHCgMYtxcQDFoTCgMYuRcaDAoDGLQXEgMYtxcYAg=="}]},"InlineCreateCanFailSafely":{"placeholderNum":3004,"encodedItems":[{"b64Body":"Cg8KCQjl9v+rBhCzBhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAihxdqvBhCwpICQAhptCiISILukXTZP4of++wwc4Y8KgE3KBMocUS33j+45X+bbMRRrCiM6IQJISumB0x4638wP5RfAFXpy11rs+unDHGt1DuANxd4a8woiEiB879vEheZDm5Ks6iYyxRQXppWzi3MJe06MhLbmMkG9WSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGL0XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBeGIoymXbcJo3Ihw0h/8FEVw4EAqvUhFFvbmY440IGWjNMT7yrJyUCL4EbUrbUBk0aDAih9/+rBhCb3LeiAiIPCgkI5fb/qwYQswYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjm9v+rBhC3BhICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxi9FyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGE1ODgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwMzQ1NzYwMDAzNTYwZTAxYzgwNjM4MTg3MWNiYzE0NjEwMDM5NTc4MDYzOTRjYTJjYjUxNDYxMDA3NjU3ODA2MzljNGFlMmQwMTQ2MTAwYjM1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA0NTU3NjAwMDgwZmQ1YjUwNjEwMDYwNjAwNDgwMzYwMzgxMDE5MDYxMDA1YjkxOTA2MTAyNTY1NjViNjEwMGNmNTY1YjYwNDA1MTYxMDA2ZDkxOTA2MTAzMmY1NjViNjA0MDUxODA5MTAzOTBmMzViMzQ4MDE1NjEwMDgyNTc2MDAwODBmZDViNTA2MTAwOWQ2MDA0ODAzNjAzODEwMTkwNjEwMDk4OTE5MDYxMDQ4NjU2NWI2MTAxNDU1NjViNjA0MDUxNjEwMGFhOTE5MDYxMDRmMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwY2Q2MDA0ODAzNjAzODEwMTkwNjEwMGM4OTE5MDYxMDQ4NjU2NWI2MTAxOGY1NjViMDA1YjYwNjA2MDAwNjA0MDUxODA2MDIwMDE2MTAwZTM5MDYxMDFhMTU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDgwODQ4NDYwNDA1MTYwMjAwMTYxMDEwZDkyOTE5MDYxMDUxYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI2MDQwNTE2MDIwMDE2MTAxMmQ5MjkxOTA2MTA1ODA1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjBmZjYwZjgxYjMwODQ4NjgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTZhOTQ5MzkyOTE5MDYxMDY4NTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDYwMDAxYzkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTgzNTE2MDIwODUwMTM0ZjU5MDUwNTA1MDUwNTY1YjYxMDM0ZjgwNjEwNmQ0ODMzOTAxOTA1NjViNjAwMDYwNDA1MTkwNTA5MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMWVkODI2MTAxYzI1NjViOTA1MDkxOTA1MDU2NWI2MTAxZmQ4MTYxMDFlMjU2NWI4MTE0NjEwMjA4NTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjFhODE2MTAxZjQ1NjViOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAyMzM4MTYxMDIyMDU2NWI4MTE0NjEwMjNlNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjUwODE2MTAyMmE1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyNmQ1NzYxMDI2YzYxMDFiODU2NWI1YjYwMDA2MTAyN2I4NTgyODYwMTYxMDIwYjU2NWI5MjUwNTA2MDIwNjEwMjhjODU4Mjg2MDE2MTAyNDE1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMmQwNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMmI1NTY1YjgzODExMTE1NjEwMmRmNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MDFmMTk2MDFmODMwMTE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMzAxODI2MTAyOTY1NjViNjEwMzBiODE4NTYxMDJhMTU2NWI5MzUwNjEwMzFiODE4NTYwMjA4NjAxNjEwMmIyNTY1YjYxMDMyNDgxNjEwMmU1NTY1Yjg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDM0OTgxODQ2MTAyZjY1NjViOTA1MDkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMDM5MzgyNjEwMmU1NTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMDNiMjU3NjEwM2IxNjEwMzViNTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMDNjNTYxMDFhZTU2NWI5MDUwNjEwM2QxODI4MjYxMDM4YTU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDNmMTU3NjEwM2YwNjEwMzViNTY1YjViNjEwM2ZhODI2MTAyZTU1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI4MjgxODMzNzYwMDA4MzgzMDE1MjUwNTA1MDU2NWI2MDAwNjEwNDI5NjEwNDI0ODQ2MTAzZDY1NjViNjEwM2JiNTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMDQ0NTU3NjEwNDQ0NjEwMzU2NTY1YjViNjEwNDUwODQ4Mjg1NjEwNDA3NTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwNDZkNTc2MTA0NmM2MTAzNTE1NjViNWI4MTM1NjEwNDdkODQ4MjYwMjA4NjAxNjEwNDE2NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwNDlkNTc2MTA0OWM2MTAxYjg1NjViNWI2MDAwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwNGJiNTc2MTA0YmE2MTAxYmQ1NjViNWI2MTA0Yzc4NTgyODYwMTYxMDQ1ODU2NWI5MjUwNTA2MDIwNjEwNGQ4ODU4Mjg2MDE2MTAyNDE1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MTA0ZWI4MTYxMDFlMjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNTA2NjAwMDgzMDE4NDYxMDRlMjU2NWI5MjkxNTA1MDU2NWI2MTA1MTU4MTYxMDIyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwNTMwNjAwMDgzMDE4NTYxMDRlMjU2NWI2MTA1M2Q2MDIwODMwMTg0NjEwNTBjNTY1YjkzOTI1MDUwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA1NWE4MjYxMDI5NjU2NWI2MTA1NjQ4MTg1NjEwNTQ0NTY1YjkzNTA2MTA1NzQ4MTg1NjAyMDg2MDE2MTAyYjI1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDU4YzgyODU2MTA1NGY1NjViOTE1MDYxMDU5ODgyODQ2MTA1NGY1NjViOTE1MDgxOTA1MDkzOTI1MDUwNTA1NjViNjAwMDdmZmYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgyMTY5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNWViNjEwNWU2ODI2MTA1YTQ1NjViNjEwNWQwNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTYwNjAxYjkwNTA5MTkwNTA1NjViNjAwMDYxMDYwOTgyNjEwNWYxNTY1YjkwNTA5MTkwNTA1NjViNjAwMDYxMDYxYjgyNjEwNWZlNTY1YjkwNTA5MTkwNTA1NjViNjEwNjMzNjEwNjJlODI2MTAxZTI1NjViNjEwNjEwNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNjU0NjEwNjRmODI2MTAyMjA1NjViNjEwNjM5NTY1YjgyNTI1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTA2N2Y2MTA2N2E4MjYxMDY1YTU2NWI2MTA2NjQ1NjViODI1MjUwNTA1NjViNjAwMDYxMDY5MTgyODc2MTA1ZGE1NjViNjAwMTgyMDE5MTUwNjEwNmExODI4NjYxMDYyMjU2NWI2MDE0ODIwMTkxNTA2MTA2YjE4Mjg1NjEwNjQzNTY1YjYwMjA4MjAxOTE1MDYxMDZjMTgyODQ2MTA2NmU1NjViNjAyMDgyMDE5MTUwODE5MDUwOTU5NDUwNTA1MDUwNTA1NmZlNjA4MDYwNDA1MjYwNDA1MTYxMDM0ZjM4MDM4MDYxMDM0ZjgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMjU5MTkwNjEwMTFhNTY1YjYwMGE4MTExMTU2MTAwMzM1NzYwMDA4MGZkNWI4MTYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDYxMDE1YTU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAwYjE4MjYxMDA4NjU2NWI5MDUwOTE5MDUwNTY1YjYxMDBjMTgxNjEwMGE2NTY1YjgxMTQ2MTAwY2M1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAwZGU4MTYxMDBiODU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDBmNzgxNjEwMGU0NTY1YjgxMTQ2MTAxMDI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw21FlEDdeW1BkHuFHGgLYmQ01O9TINQlObJbzzwnMQ+/40F8xBZGqwlJkRZMr0YxLGgsIovf/qwYQ46eRKiIPCgkI5vb/qwYQtwYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjm9v+rBhC9BhICGAISAhgDGIjItDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB+AkSAxi9FyLwCTYxMDExNDgxNjEwMGVlNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMTMxNTc2MTAxMzA2MTAwODE1NjViNWI2MDAwNjEwMTNmODU4Mjg2MDE2MTAwY2Y1NjViOTI1MDUwNjAyMDYxMDE1MDg1ODI4NjAxNjEwMTA1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMWU2ODA2MTAxNjk2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA1MTU3ODA2MzczN2JjM2M5MTQ2MTAwNmY1NzgwNjM4ZGE1Y2I1YjE0NjEwMDc5NTc4MDYzYzI5ODU1NzgxNDYxMDA5NzU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGI1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAxMzk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGJkNTY1YjAwNWI2MTAwODE2MTAwZjY1NjViNjA0MDUxNjEwMDhlOTE5MDYxMDE5NTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwOWY2MTAxMWE1NjViNjA0MDUxNjEwMGFjOTE5MDYxMDEzOTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNDc5MDUwOTA1NjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1NjViNjAwMTU0ODE1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAxMzM4MTYxMDEyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTRlNjAwMDgzMDE4NDYxMDEyYTU2NWI5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDE3ZjgyNjEwMTU0NTY1YjkwNTA5MTkwNTA1NjViNjEwMThmODE2MTAxNzQ1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDFhYTYwMDA4MzAxODQ2MTAxODY1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjAzMDQ2YTZmYWRhOGRjMDkwNDc2YjA5OWZiYTlhODAzYzEwMDI1NWU5ZWJlNzBhMjZkZWU0YjYwZGQzNjE0OTQ4NjQ3MzZmNmM2MzQzMDAwODBjMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwMDZkMmI4NWU4OWM0NjlhYTg3MTE2MDg3NjFlMmIxZTI3NjI2MTJhMmQ3ZDQxMGRlMGU0OTgxMzc4MTNlNmVlNzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwzOz1+NTzsLQrn4KgTuk+LK2TvPbH+sC6R8XzcKikKqWqqZfRI33iTfyBNGHOmQoHGgwIovf/qwYQs7r4qwIiDwoJCOb2/6sGEL0GEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjn9v+rBhC/BhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGL0XGiISINOz5J5kv6R28LRWBeXASOq6EPHv7cvJxkXIIYCmAaIuIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDKwV6sVn7cvl4Bl6T49yvj1tnga6EeMp+7+m5G6fXtt0PcdnEhZeR/pKYa3oOjKdIaCwij9/+rBhCj2M4zIg8KCQjn9v+rBhC/BhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCjRcKAxi+FxLYFGCAYEBSYAQ2EGEANFdgADVg4ByAY4GHHLwUYQA5V4BjlMostRRhAHZXgGOcSuLQFGEAs1dbYACA/Vs0gBVhAEVXYACA/VtQYQBgYASANgOBAZBhAFuRkGECVlZbYQDPVltgQFFhAG2RkGEDL1ZbYEBRgJEDkPNbNIAVYQCCV2AAgP1bUGEAnWAEgDYDgQGQYQCYkZBhBIZWW2EBRVZbYEBRYQCqkZBhBPFWW2BAUYCRA5DzW2EAzWAEgDYDgQGQYQDIkZBhBIZWW2EBj1ZbAFtgYGAAYEBRgGAgAWEA45BhAaFWW2AgggGBA4JSYB8ZYB+CARZgQFJQkFCAhIRgQFFgIAFhAQ2SkZBhBRtWW2BAUWAggYMDA4FSkGBAUmBAUWAgAWEBLZKRkGEFgFZbYEBRYCCBgwMDgVKQYEBSkVBQkpFQUFZbYACAYP9g+BswhIaAUZBgIAEgYEBRYCABYQFqlJOSkZBhBoVWW2BAUWAggYMDA4FSkGBAUoBRkGAgASCQUIBgAByRUFCSkVBQVltgAIGDUWAghQE09ZBQUFBQVlthA0+AYQbUgzkBkFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQHtgmEBwlZbkFCRkFBWW2EB/YFhAeJWW4EUYQIIV2AAgP1bUFZbYACBNZBQYQIagWEB9FZbkpFQUFZbYACBkFCRkFBWW2ECM4FhAiBWW4EUYQI+V2AAgP1bUFZbYACBNZBQYQJQgWECKlZbkpFQUFZbYACAYECDhQMSFWECbVdhAmxhAbhWW1tgAGECe4WChgFhAgtWW5JQUGAgYQKMhYKGAWECQVZbkVBQklCSkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQLQV4CCAVGBhAFSYCCBAZBQYQK1VluDgREVYQLfV2AAhIQBUltQUFBQVltgAGAfGWAfgwEWkFCRkFBWW2AAYQMBgmECllZbYQMLgYVhAqFWW5NQYQMbgYVgIIYBYQKyVlthAySBYQLlVluEAZFQUJKRUFBWW2AAYCCCAZBQgYEDYACDAVJhA0mBhGEC9lZbkFCSkVBQVltgAID9W2AAgP1bf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VthA5OCYQLlVluBAYGBEGf//////////4IRFxVhA7JXYQOxYQNbVltbgGBAUlBQUFZbYABhA8VhAa5WW5BQYQPRgoJhA4pWW5GQUFZbYABn//////////+CERVhA/FXYQPwYQNbVltbYQP6gmEC5VZbkFBgIIEBkFCRkFBWW4KBgzdgAIODAVJQUFBWW2AAYQQpYQQkhGED1lZbYQO7VluQUIKBUmAggQGEhIQBERVhBEVXYQREYQNWVltbYQRQhIKFYQQHVltQk5JQUFBWW2AAgmAfgwESYQRtV2EEbGEDUVZbW4E1YQR9hIJgIIYBYQQWVluRUFCSkVBQVltgAIBgQIOFAxIVYQSdV2EEnGEBuFZbW2AAgwE1Z///////////gREVYQS7V2EEumEBvVZbW2EEx4WChgFhBFhWW5JQUGAgYQTYhYKGAWECQVZbkVBQklCSkFBWW2EE64FhAeJWW4JSUFBWW2AAYCCCAZBQYQUGYACDAYRhBOJWW5KRUFBWW2EFFYFhAiBWW4JSUFBWW2AAYECCAZBQYQUwYACDAYVhBOJWW2EFPWAggwGEYQUMVluTklBQUFZbYACBkFCSkVBQVltgAGEFWoJhApZWW2EFZIGFYQVEVluTUGEFdIGFYCCGAWECslZbgIQBkVBQkpFQUFZbYABhBYyChWEFT1ZbkVBhBZiChGEFT1ZbkVCBkFCTklBQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQXrYQXmgmEFpFZbYQXQVluCUlBQVltgAIFgYBuQUJGQUFZbYABhBgmCYQXxVluQUJGQUFZbYABhBhuCYQX+VluQUJGQUFZbYQYzYQYugmEB4lZbYQYQVluCUlBQVltgAIGQUJGQUFZbYQZUYQZPgmECIFZbYQY5VluCUlBQVltgAIGQUJGQUFZbYACBkFCRkFBWW2EGf2EGeoJhBlpWW2EGZFZbglJQUFZbYABhBpGCh2EF2lZbYAGCAZFQYQahgoZhBiJWW2AUggGRUGEGsYKFYQZDVltgIIIBkVBhBsGChGEGblZbYCCCAZFQgZBQlZRQUFBQUFb+YIBgQFJgQFFhA084A4BhA0+DOYGBAWBAUoEBkGEAJZGQYQEaVltgCoERFWEAM1dgAID9W4FgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAYAGBkFVQUFBhAVpWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEAsYJhAIZWW5BQkZBQVlthAMGBYQCmVluBFGEAzFdgAID9W1BWW2AAgVGQUGEA3oFhALhWW5KRUFBWW2AAgZBQkZBQVlthAPeBYQDkVluBFGEBAldgAID9W1BWW2AAgVGQUGEBFIFhAO5WW5KRUFBWW2AAgGBAg4UDEhVhATFXYQEwYQCBVltbYABhAT+FgoYBYQDPVluSUFBgIGEBUIWChgFhAQVWW5FQUJJQkpBQVlthAeaAYQFpYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMSBl/gFGEAUVeAY3N7w8kUYQBvV4BjjaXLWxRhAHlXgGPCmFV4FGEAl1dbYACA/VthAFlhALVWW2BAUWEAZpGQYQE5VltgQFGAkQOQ81thAHdhAL1WWwBbYQCBYQD2VltgQFFhAI6RkGEBlVZbYEBRgJEDkPNbYQCfYQEaVltgQFFhAKyRkGEBOVZbYEBRgJEDkPNbYABHkFCQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1tgAIBUkGEBAAqQBHP//////////////////////////xaBVltgAVSBVltgAIGQUJGQUFZbYQEzgWEBIFZbglJQUFZbYABgIIIBkFBhAU5gAIMBhGEBKlZbkpFQUFZbYABz//////////////////////////+CFpBQkZBQVltgAGEBf4JhAVRWW5BQkZBQVlthAY+BYQF0VluCUlBQVltgAGAgggGQUGEBqmAAgwGEYQGGVluSkVBQVv6iZGlwZnNYIhIgMEam+tqNwJBHawmfupqAPBACVenr5wom3uS2DdNhSUhkc29sY0MACAwAM6JkaXBmc1giEiAG0rheicRpqocRYIdh4rHidiYSotfUEN4OSYE3gT5u52Rzb2xjQwAIDAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6Axi+F0oWChQAAAAAAAAAAAAAAAAAAAAAAAALvnIHCgMYvhcQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQjn9v+rBhDFBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCcEbwVd11miSsPIIOk5D9Fel7opgjnMrrSn4KXPQOMklKTmDko2SX/AsQPj05ABvQaDAij9/+rBhCrpdS1AiIPCgkI5/b/qwYQxQYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEAJSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjn9v+rBhDLBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCEhZqSlkiD8Y64jSuJjhDQpyUVlTF/I6MdG68yv/91l2T5LQovzNVV36dRgeuXGxQaDAij9/+rBhCTrdS1AiIPCgkI5/b/qwYQywYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEANSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjn9v+rBhDGBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAz2tI1Y/dXYFCBe58div4QVBkURttcquyiG6CkNEF7EZP45nzETiq0MuJ8npgFM5EaDAij9/+rBhD7tNS1AiIPCgkI5/b/qwYQxgYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEARSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjn9v+rBhDPBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAH5K3r3EC5XsyZ7ptaY0lQ72RFybXAydNqD8Cg5cX/hmTxald0PRARICD1ZtKraG4aDAij9/+rBhDjvNS1AiIPCgkI5/b/qwYQzwYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEAVSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjn9v+rBhDHBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDPxDl31f1x52Zfpg1kJbLhgTpqZJ0+fbVGF8/I5+nBZweOKjVB0C72LK5RGwrkjt0aDAij9/+rBhDLxNS1AiIPCgkI5/b/qwYQxwYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEAZSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjn9v+rBhDOBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBzEdQUcufYPPC5ELYlkX9vfmWpWrDSV+DFay8252HSus1EuvkWJ1mj7OhnfRVpw7YaDAij9/+rBhCzzNS1AiIPCgkI5/b/qwYQzgYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEAdSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjn9v+rBhDKBhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxi+FxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGL4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjA9TMxtW8qTgb6lIoT7BJ8JiHKaMDpN7C7D8i7HAY4ndnWF6O0inyWk7Cbcpmli9j0aDAij9/+rBhCb1NS1AiIPCgkI5/b/qwYQygYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYvhcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGL4XEAhSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYvhcQpBM="},{"b64Body":"Cg8KCQjo9v+rBhDRBhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIObxrTctTUcWu8rtVUCAJ8kxpwMLz5ONGnolCKvL8FNvEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGMYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDpSNo8EukKIuLM9DxVWOIeHxG6KYxrApUL1IwmIAqIMBW/BZ0uGTr9Nr3u66FWzgMaCwik9/+rBhCzwPtZIg8KCQjo9v+rBhDRBhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/6fWuQcKCwoDGMYXEICo1rkH"}]},"InlineCreate2CanFailSafely":{"placeholderNum":3015,"encodedItems":[{"b64Body":"Cg8KCQjs9v+rBhDhBhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAioxdqvBhD4n+2vAhptCiISIPZg0aM7eI063Q6LO1IUuuGM6e9jVRD6l+SIqjlaXRadCiM6IQOktU3yZj4R/VK4hx728AhTFug05bw0Fb3GMmsNGN02gwoiEiAcDNxQFo+kbIp8z5WBeL7wSbUCWEnDhieRk3B8N/o4hyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGMgXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB/yvK6AH6ZMZvcehOKw8ng0njRKBXoM7VcP1KA7lkhjSat8P7PrBnt5dHTWAbgC/saDAio9/+rBhCzqKfIAiIPCgkI7Pb/qwYQ4QYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjt9v+rBhDlBhICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjIFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGE1ODgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwMzQ1NzYwMDAzNTYwZTAxYzgwNjM4MTg3MWNiYzE0NjEwMDM5NTc4MDYzOTRjYTJjYjUxNDYxMDA3NjU3ODA2MzljNGFlMmQwMTQ2MTAwYjM1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA0NTU3NjAwMDgwZmQ1YjUwNjEwMDYwNjAwNDgwMzYwMzgxMDE5MDYxMDA1YjkxOTA2MTAyNTY1NjViNjEwMGNmNTY1YjYwNDA1MTYxMDA2ZDkxOTA2MTAzMmY1NjViNjA0MDUxODA5MTAzOTBmMzViMzQ4MDE1NjEwMDgyNTc2MDAwODBmZDViNTA2MTAwOWQ2MDA0ODAzNjAzODEwMTkwNjEwMDk4OTE5MDYxMDQ4NjU2NWI2MTAxNDU1NjViNjA0MDUxNjEwMGFhOTE5MDYxMDRmMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwY2Q2MDA0ODAzNjAzODEwMTkwNjEwMGM4OTE5MDYxMDQ4NjU2NWI2MTAxOGY1NjViMDA1YjYwNjA2MDAwNjA0MDUxODA2MDIwMDE2MTAwZTM5MDYxMDFhMTU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDgwODQ4NDYwNDA1MTYwMjAwMTYxMDEwZDkyOTE5MDYxMDUxYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI2MDQwNTE2MDIwMDE2MTAxMmQ5MjkxOTA2MTA1ODA1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjBmZjYwZjgxYjMwODQ4NjgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTZhOTQ5MzkyOTE5MDYxMDY4NTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDYwMDAxYzkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTgzNTE2MDIwODUwMTM0ZjU5MDUwNTA1MDUwNTY1YjYxMDM0ZjgwNjEwNmQ0ODMzOTAxOTA1NjViNjAwMDYwNDA1MTkwNTA5MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMWVkODI2MTAxYzI1NjViOTA1MDkxOTA1MDU2NWI2MTAxZmQ4MTYxMDFlMjU2NWI4MTE0NjEwMjA4NTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjFhODE2MTAxZjQ1NjViOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAyMzM4MTYxMDIyMDU2NWI4MTE0NjEwMjNlNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwMjUwODE2MTAyMmE1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTAyNmQ1NzYxMDI2YzYxMDFiODU2NWI1YjYwMDA2MTAyN2I4NTgyODYwMTYxMDIwYjU2NWI5MjUwNTA2MDIwNjEwMjhjODU4Mjg2MDE2MTAyNDE1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMmQwNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMmI1NTY1YjgzODExMTE1NjEwMmRmNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MDFmMTk2MDFmODMwMTE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMzAxODI2MTAyOTY1NjViNjEwMzBiODE4NTYxMDJhMTU2NWI5MzUwNjEwMzFiODE4NTYwMjA4NjAxNjEwMmIyNTY1YjYxMDMyNDgxNjEwMmU1NTY1Yjg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDM0OTgxODQ2MTAyZjY1NjViOTA1MDkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMDM5MzgyNjEwMmU1NTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMDNiMjU3NjEwM2IxNjEwMzViNTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMDNjNTYxMDFhZTU2NWI5MDUwNjEwM2QxODI4MjYxMDM4YTU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDNmMTU3NjEwM2YwNjEwMzViNTY1YjViNjEwM2ZhODI2MTAyZTU1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI4MjgxODMzNzYwMDA4MzgzMDE1MjUwNTA1MDU2NWI2MDAwNjEwNDI5NjEwNDI0ODQ2MTAzZDY1NjViNjEwM2JiNTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMDQ0NTU3NjEwNDQ0NjEwMzU2NTY1YjViNjEwNDUwODQ4Mjg1NjEwNDA3NTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwNDZkNTc2MTA0NmM2MTAzNTE1NjViNWI4MTM1NjEwNDdkODQ4MjYwMjA4NjAxNjEwNDE2NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwNDlkNTc2MTA0OWM2MTAxYjg1NjViNWI2MDAwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwNGJiNTc2MTA0YmE2MTAxYmQ1NjViNWI2MTA0Yzc4NTgyODYwMTYxMDQ1ODU2NWI5MjUwNTA2MDIwNjEwNGQ4ODU4Mjg2MDE2MTAyNDE1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MTA0ZWI4MTYxMDFlMjU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwNTA2NjAwMDgzMDE4NDYxMDRlMjU2NWI5MjkxNTA1MDU2NWI2MTA1MTU4MTYxMDIyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwNTMwNjAwMDgzMDE4NTYxMDRlMjU2NWI2MTA1M2Q2MDIwODMwMTg0NjEwNTBjNTY1YjkzOTI1MDUwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA1NWE4MjYxMDI5NjU2NWI2MTA1NjQ4MTg1NjEwNTQ0NTY1YjkzNTA2MTA1NzQ4MTg1NjAyMDg2MDE2MTAyYjI1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDU4YzgyODU2MTA1NGY1NjViOTE1MDYxMDU5ODgyODQ2MTA1NGY1NjViOTE1MDgxOTA1MDkzOTI1MDUwNTA1NjViNjAwMDdmZmYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgyMTY5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNWViNjEwNWU2ODI2MTA1YTQ1NjViNjEwNWQwNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTYwNjAxYjkwNTA5MTkwNTA1NjViNjAwMDYxMDYwOTgyNjEwNWYxNTY1YjkwNTA5MTkwNTA1NjViNjAwMDYxMDYxYjgyNjEwNWZlNTY1YjkwNTA5MTkwNTA1NjViNjEwNjMzNjEwNjJlODI2MTAxZTI1NjViNjEwNjEwNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNjU0NjEwNjRmODI2MTAyMjA1NjViNjEwNjM5NTY1YjgyNTI1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTA2N2Y2MTA2N2E4MjYxMDY1YTU2NWI2MTA2NjQ1NjViODI1MjUwNTA1NjViNjAwMDYxMDY5MTgyODc2MTA1ZGE1NjViNjAwMTgyMDE5MTUwNjEwNmExODI4NjYxMDYyMjU2NWI2MDE0ODIwMTkxNTA2MTA2YjE4Mjg1NjEwNjQzNTY1YjYwMjA4MjAxOTE1MDYxMDZjMTgyODQ2MTA2NmU1NjViNjAyMDgyMDE5MTUwODE5MDUwOTU5NDUwNTA1MDUwNTA1NmZlNjA4MDYwNDA1MjYwNDA1MTYxMDM0ZjM4MDM4MDYxMDM0ZjgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMjU5MTkwNjEwMTFhNTY1YjYwMGE4MTExMTU2MTAwMzM1NzYwMDA4MGZkNWI4MTYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNTA1MDYxMDE1YTU2NWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAwYjE4MjYxMDA4NjU2NWI5MDUwOTE5MDUwNTY1YjYxMDBjMTgxNjEwMGE2NTY1YjgxMTQ2MTAwY2M1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTAwZGU4MTYxMDBiODU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDBmNzgxNjEwMGU0NTY1YjgxMTQ2MTAxMDI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw4h9XrElzcLQ8taDKVvNdx0UrjE5Qwaluy2nxRVPfrXgmmc4YZkFF6//21e56n/24GgsIqff/qwYQy93gUCIPCgkI7fb/qwYQ5QYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjt9v+rBhDrBhICGAISAhgDGIjItDIiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB+AkSAxjIFyLwCTYxMDExNDgxNjEwMGVlNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMTMxNTc2MTAxMzA2MTAwODE1NjViNWI2MDAwNjEwMTNmODU4Mjg2MDE2MTAwY2Y1NjViOTI1MDUwNjAyMDYxMDE1MDg1ODI4NjAxNjEwMTA1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMWU2ODA2MTAxNjk2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA1MTU3ODA2MzczN2JjM2M5MTQ2MTAwNmY1NzgwNjM4ZGE1Y2I1YjE0NjEwMDc5NTc4MDYzYzI5ODU1NzgxNDYxMDA5NzU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGI1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAxMzk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGJkNTY1YjAwNWI2MTAwODE2MTAwZjY1NjViNjA0MDUxNjEwMDhlOTE5MDYxMDE5NTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwOWY2MTAxMWE1NjViNjA0MDUxNjEwMGFjOTE5MDYxMDEzOTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNDc5MDUwOTA1NjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1NjViNjAwMTU0ODE1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAxMzM4MTYxMDEyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTRlNjAwMDgzMDE4NDYxMDEyYTU2NWI5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDE3ZjgyNjEwMTU0NTY1YjkwNTA5MTkwNTA1NjViNjEwMThmODE2MTAxNzQ1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDFhYTYwMDA4MzAxODQ2MTAxODY1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjAzMDQ2YTZmYWRhOGRjMDkwNDc2YjA5OWZiYTlhODAzYzEwMDI1NWU5ZWJlNzBhMjZkZWU0YjYwZGQzNjE0OTQ4NjQ3MzZmNmM2MzQzMDAwODBjMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwMDZkMmI4NWU4OWM0NjlhYTg3MTE2MDg3NjFlMmIxZTI3NjI2MTJhMmQ3ZDQxMGRlMGU0OTgxMzc4MTNlNmVlNzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwoPO0WAhuda6Yr7PDSZEB61/WcBRn4cMtkhq1MX9/UxvuDUdg0GEmyjc6DUF0ypzxGgwIqff/qwYQy7zW0QIiDwoJCO32/6sGEOsGEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQju9v+rBhDtBhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGMgXGiISICbPvjHL/lzvJoVXrI8STHg6Dz5Q5AyTXmZwpXWpqNWtIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBhpxovryBRVAPUD6B7TrJwMu8GyWEKHhQS1Kv11s8pK7bfO+N6rXzKlf7re5kklakaCwiq9/+rBhCr4I1aIg8KCQju9v+rBhDtBhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCjRcKAxjJFxLYFGCAYEBSYAQ2EGEANFdgADVg4ByAY4GHHLwUYQA5V4BjlMostRRhAHZXgGOcSuLQFGEAs1dbYACA/Vs0gBVhAEVXYACA/VtQYQBgYASANgOBAZBhAFuRkGECVlZbYQDPVltgQFFhAG2RkGEDL1ZbYEBRgJEDkPNbNIAVYQCCV2AAgP1bUGEAnWAEgDYDgQGQYQCYkZBhBIZWW2EBRVZbYEBRYQCqkZBhBPFWW2BAUYCRA5DzW2EAzWAEgDYDgQGQYQDIkZBhBIZWW2EBj1ZbAFtgYGAAYEBRgGAgAWEA45BhAaFWW2AgggGBA4JSYB8ZYB+CARZgQFJQkFCAhIRgQFFgIAFhAQ2SkZBhBRtWW2BAUWAggYMDA4FSkGBAUmBAUWAgAWEBLZKRkGEFgFZbYEBRYCCBgwMDgVKQYEBSkVBQkpFQUFZbYACAYP9g+BswhIaAUZBgIAEgYEBRYCABYQFqlJOSkZBhBoVWW2BAUWAggYMDA4FSkGBAUoBRkGAgASCQUIBgAByRUFCSkVBQVltgAIGDUWAghQE09ZBQUFBQVlthA0+AYQbUgzkBkFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQHtgmEBwlZbkFCRkFBWW2EB/YFhAeJWW4EUYQIIV2AAgP1bUFZbYACBNZBQYQIagWEB9FZbkpFQUFZbYACBkFCRkFBWW2ECM4FhAiBWW4EUYQI+V2AAgP1bUFZbYACBNZBQYQJQgWECKlZbkpFQUFZbYACAYECDhQMSFWECbVdhAmxhAbhWW1tgAGECe4WChgFhAgtWW5JQUGAgYQKMhYKGAWECQVZbkVBQklCSkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQLQV4CCAVGBhAFSYCCBAZBQYQK1VluDgREVYQLfV2AAhIQBUltQUFBQVltgAGAfGWAfgwEWkFCRkFBWW2AAYQMBgmECllZbYQMLgYVhAqFWW5NQYQMbgYVgIIYBYQKyVlthAySBYQLlVluEAZFQUJKRUFBWW2AAYCCCAZBQgYEDYACDAVJhA0mBhGEC9lZbkFCSkVBQVltgAID9W2AAgP1bf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VthA5OCYQLlVluBAYGBEGf//////////4IRFxVhA7JXYQOxYQNbVltbgGBAUlBQUFZbYABhA8VhAa5WW5BQYQPRgoJhA4pWW5GQUFZbYABn//////////+CERVhA/FXYQPwYQNbVltbYQP6gmEC5VZbkFBgIIEBkFCRkFBWW4KBgzdgAIODAVJQUFBWW2AAYQQpYQQkhGED1lZbYQO7VluQUIKBUmAggQGEhIQBERVhBEVXYQREYQNWVltbYQRQhIKFYQQHVltQk5JQUFBWW2AAgmAfgwESYQRtV2EEbGEDUVZbW4E1YQR9hIJgIIYBYQQWVluRUFCSkVBQVltgAIBgQIOFAxIVYQSdV2EEnGEBuFZbW2AAgwE1Z///////////gREVYQS7V2EEumEBvVZbW2EEx4WChgFhBFhWW5JQUGAgYQTYhYKGAWECQVZbkVBQklCSkFBWW2EE64FhAeJWW4JSUFBWW2AAYCCCAZBQYQUGYACDAYRhBOJWW5KRUFBWW2EFFYFhAiBWW4JSUFBWW2AAYECCAZBQYQUwYACDAYVhBOJWW2EFPWAggwGEYQUMVluTklBQUFZbYACBkFCSkVBQVltgAGEFWoJhApZWW2EFZIGFYQVEVluTUGEFdIGFYCCGAWECslZbgIQBkVBQkpFQUFZbYABhBYyChWEFT1ZbkVBhBZiChGEFT1ZbkVCBkFCTklBQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQXrYQXmgmEFpFZbYQXQVluCUlBQVltgAIFgYBuQUJGQUFZbYABhBgmCYQXxVluQUJGQUFZbYABhBhuCYQX+VluQUJGQUFZbYQYzYQYugmEB4lZbYQYQVluCUlBQVltgAIGQUJGQUFZbYQZUYQZPgmECIFZbYQY5VluCUlBQVltgAIGQUJGQUFZbYACBkFCRkFBWW2EGf2EGeoJhBlpWW2EGZFZbglJQUFZbYABhBpGCh2EF2lZbYAGCAZFQYQahgoZhBiJWW2AUggGRUGEGsYKFYQZDVltgIIIBkVBhBsGChGEGblZbYCCCAZFQgZBQlZRQUFBQUFb+YIBgQFJgQFFhA084A4BhA0+DOYGBAWBAUoEBkGEAJZGQYQEaVltgCoERFWEAM1dgAID9W4FgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAYAGBkFVQUFBhAVpWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEAsYJhAIZWW5BQkZBQVlthAMGBYQCmVluBFGEAzFdgAID9W1BWW2AAgVGQUGEA3oFhALhWW5KRUFBWW2AAgZBQkZBQVlthAPeBYQDkVluBFGEBAldgAID9W1BWW2AAgVGQUGEBFIFhAO5WW5KRUFBWW2AAgGBAg4UDEhVhATFXYQEwYQCBVltbYABhAT+FgoYBYQDPVluSUFBgIGEBUIWChgFhAQVWW5FQUJJQkpBQVlthAeaAYQFpYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMSBl/gFGEAUVeAY3N7w8kUYQBvV4BjjaXLWxRhAHlXgGPCmFV4FGEAl1dbYACA/VthAFlhALVWW2BAUWEAZpGQYQE5VltgQFGAkQOQ81thAHdhAL1WWwBbYQCBYQD2VltgQFFhAI6RkGEBlVZbYEBRgJEDkPNbYQCfYQEaVltgQFFhAKyRkGEBOVZbYEBRgJEDkPNbYABHkFCQVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1tgAIBUkGEBAAqQBHP//////////////////////////xaBVltgAVSBVltgAIGQUJGQUFZbYQEzgWEBIFZbglJQUFZbYABgIIIBkFBhAU5gAIMBhGEBKlZbkpFQUFZbYABz//////////////////////////+CFpBQkZBQVltgAGEBf4JhAVRWW5BQkZBQVlthAY+BYQF0VluCUlBQVltgAGAgggGQUGEBqmAAgwGEYQGGVluSkVBQVv6iZGlwZnNYIhIgMEam+tqNwJBHawmfupqAPBACVenr5wom3uS2DdNhSUhkc29sY0MACAwAM6JkaXBmc1giEiAG0rheicRpqocRYIdh4rHidiYSotfUEN4OSYE3gT5u52Rzb2xjQwAIDAAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAmgw6AxjJF0oWChQAAAAAAAAAAAAAAAAAAAAAAAALyXIHCgMYyRcQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"Cg8KCQju9v+rBhD2BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCw6u6e8ynpgW+UkpV+kJojRzOy5ax0N1vVkES1pdv7Or9a9XuAk88fsfw1LqDDCoIaDAiq9/+rBhCD2rbbAiIPCgkI7vb/qwYQ9gYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEAJSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQju9v+rBhD8BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDlYlTU+4JrDIXySnROy/Oj0f/gb1QPEce62Xg1nY2DX9MnZUffATd46vTno6GUBs8aDAiq9/+rBhDr4bbbAiIPCgkI7vb/qwYQ/AYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEANSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQju9v+rBhD9BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD8ki9H715tEDcUM3zF5kpf5NFaIfatTcws1PrLtwaUbZR1spVllwCzdq6DuLPUufEaDAiq9/+rBhDT6bbbAiIPCgkI7vb/qwYQ/QYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEARSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQju9v+rBhD1BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAoLeDCRe4NERilIX2ydFEumOe7UzkYxQn12TXe0yU7nomvQ2RzaFtdyOQA7tj2UzUaDAiq9/+rBhC78bbbAiIPCgkI7vb/qwYQ9QYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEAVSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQju9v+rBhD0BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBlv/1gGYebc/3Qquc3qt/mbgjRHYbxxZ27YBwA3Kay0ncFYFR4/GXgY2n74uZHYj4aDAiq9/+rBhCj+bbbAiIPCgkI7vb/qwYQ9AYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEAZSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQju9v+rBhD7BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAwHcq0AS+cOv06AI0uoXwnOSgeTUI1gBXmc3rFmGY+jKlS/icJFLLNHjFuAoN+cdwaDAiq9/+rBhCLgbfbAiIPCgkI7vb/qwYQ+wYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEAdSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQju9v+rBhD3BhICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjJFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj2CAYEBSYEBRYQNPOAOAYQNPgzmBgQFgQFKBAZBhACWRkGEBGlZbYAqBERVhADNXYACA/VuBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFaVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhALGCYQCGVluQUJGQUFZbYQDBgWEAplZbgRRhAMxXYACA/VtQVltgAIFRkFBhAN6BYQC4VluSkVBQVltgAIGQUJGQUFZbYQD3gWEA5FZbgRRhAQJXYACA/VtQVltgAIFRkFBhARSBYQDuVluSkVBQVltgAIBgQIOFAxIVYQExV2EBMGEAgVZbW2AAYQE/hYKGAWEAz1ZbklBQYCBhAVCFgoYBYQEFVluRUFCSUJKQUFZbYQHmgGEBaWAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIDBGpvrajcCQR2sJn7qagDwQAlXp6+cKJt7ktg3TYUlIZHNvbGNDAAgMADMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGMkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCn4qTsO1nb6Z4O6PK/xm9E9QXAfaE28HCRmnZS0Mckv3/s9TkpUF5rgz5Cyuvsah4aDAiq9/+rBhDziLfbAiIPCgkI7vb/qwYQ9wYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOpYCCgMYyRcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFyBwoDGMkXEAhSIgoKCgIYAhCjw9bYAQoKCgIYYhCAsNbYAQoICgMYyRcQpBM="},{"b64Body":"Cg8KCQjv9v+rBhD/BhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIKWpTv503xJLEdaDviFxENckrqeve1yPxbaubKg32v+XEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGNEXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCBdtx1ebt3ChjqBT6jH3r7OVk9yyT8jAuJXYv2XfPHmTUBqG9bnDTLdFxouQeJjZcaCwir9/+rBhC7/NZjIg8KCQjv9v+rBhD/BhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/6fWuQcKCwoDGNEXEICo1rkH"}]},"AllLogOpcodesResolveExpectedContractId":{"placeholderNum":3026,"encodedItems":[{"b64Body":"Cg8KCQjz9v+rBhCPBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAivxdqvBhC49oXPAhptCiISIJU7fCRt6LxTC3WuVcSxfm4DBDdhryeWIFBkFzxDaRCMCiM6IQO8xOKMds6cKd5vNliG+laZ2dudbeSXGrJKgTD1EIWosgoiEiCbXU1hwkotgSWN6ZypVSNG5deoTFZGIIcM8195DVVnNiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGNMXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDJ2fDCbkltZoRMhzRbm12ZDWjaIcMugSgXWGd3ONGcH98dIYdOsNL19PRwhKVfTLsaDAiv9/+rBhDLiJHSAiIPCgkI8/b/qwYQjwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj09v+rBhCTBxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjTFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDhhMzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYTU3NjAwMDM1NjBlMDFjODA2MjYxMGYyMzE0NjEwMDJmNTc1YjYwMDA4MGZkNWI2MTAwNDk2MDA0ODAzNjAzODEwMTkwNjEwMDQ0OTE5MDYxMDIxMjU2NWI2MTAwNGI1NjViMDA1YjdmMDEwMTAxMDEwMTAxMDEwMDEwMTAxMDEwMTAxMDEwMDEwMTAxMDEwMTAxMDEwMTExMDEwMTAxMDEwMTAxMDEwMTYwMDAxYjgxNjA0MDUxNjEwMDdjOTA2MTAwYWI1NjViNjEwMDg2OTE5MDYxMDJlMzU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAwYTY1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTY1YjYxMDU2ODgwNjEwMzA2ODMzOTAxOTA1NjViNjAwMDYwNDA1MTkwNTA5MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTAxMWY4MjYxMDBkNjU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTAxM2U1NzYxMDEzZDYxMDBlNzU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTAxNTE2MTAwYjg1NjViOTA1MDYxMDE1ZDgyODI2MTAxMTY1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAxN2Q1NzYxMDE3YzYxMDBlNzU2NWI1YjYxMDE4NjgyNjEwMGQ2NTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDFiNTYxMDFiMDg0NjEwMTYyNTY1YjYxMDE0NzU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTAxZDE1NzYxMDFkMDYxMDBkMTU2NWI1YjYxMDFkYzg0ODI4NTYxMDE5MzU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDFmOTU3NjEwMWY4NjEwMGNjNTY1YjViODEzNTYxMDIwOTg0ODI2MDIwODYwMTYxMDFhMjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyMjg1NzYxMDIyNzYxMDBjMjU2NWI1YjYwMDA4MjAxMzU2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAyNDY1NzYxMDI0NTYxMDBjNzU2NWI1YjYxMDI1Mjg0ODI4NTAxNjEwMWU0NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDViODM4MTEwMTU2MTAyOTU1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAyN2E1NjViODM4MTExMTU2MTAyYTQ1NzYwMDA4NDg0MDE1MjViNTA1MDUwNTA1NjViNjAwMDYxMDJiNTgyNjEwMjViNTY1YjYxMDJiZjgxODU2MTAyNjY1NjViOTM1MDYxMDJjZjgxODU2MDIwODYwMTYxMDI3NzU2NWI2MTAyZDg4MTYxMDBkNjU2NWI4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTAyZmQ4MTg0NjEwMmFhNTY1YjkwNTA5MjkxNTA1MDU2ZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MTA1NjgzODAzODA2MTA1Njg4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDMyOTE5MDYxMDI0NTU2NWI2MDAxODFhMDYwYWE2MDAxODJhMTYwYmI2MGFhNjAwMTgzYTI3ZjAyMDIwMjAyMDIwMjAyMDAyMDIwMjAyMDIwMjAyMDAyMDIwMjAyMDIwMjAyMDIyMjAyMDIwMjAyMDIwMjAyMDI2MDAwMWI4MTYwNDA1MTYxMDA3NTkwNjEwMGJhNTY1YjYxMDA3ZjkxOTA2MTAyZTM1NjViODE5MDYwNDA1MTgwOTEwMzkwNjAwMGY1OTA1MDgwMTU4MDE1NjEwMDlmNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTAzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYxMDI2MjgwNjEwMzA2ODMzOTAxOTA1NjViNjAwMDYwNDA1MTkwNTA5MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTAxMmU4MjYxMDBlNTU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTAxNGQ1NzYxMDE0YzYxMDBmNjU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTAxNjA2MTAwYzc1NjViOTA1MDYxMDE2YzgyODI2MTAxMjU1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAxOGM1NzYxMDE4YjYxMDBmNjU2NWI1YjYxMDE5NTgyNjEwMGU1NTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViNjAwMDViODM4MTEwMTU2MTAxYzA1NzgwODIwMTUxODE4NDAxNTI2MDIwODEwMTkwNTA2MTAxYTU1NjViODM4MTExMTU2MTAxY2Y1NzYwMDA4NDg0MDE1MjViNTA1MDUwNTA1NjViNjAwMDYxMDFlODYxMDFlMzg0NjEwMTcxNTY1YjYxMDE1NjU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTAyMDQ1NzYxMDIwMzYxMDBlMDU2NWI1YjYxMDIwZjg0ODI4NTYxMDFhMjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDIyYzU3NjEwMjJiNjEwMGRiNTY1YjViODE1MTYxMDIzYzg0ODI2MDIwODYwMTYxMDFkNTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyNWI1NzYxMDI1YTYxMDBkMTU2NWI1YjYwMDA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAyNzk1NzYxMDI3ODYxMDBkNjU2NWI1YjYxMDI4NTg0ODI4NTAxNjEwMjE3NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDJiNTgyNjEwMjhlNTY1YjYxMDJiZjgxODU2MTAyOTk1NjViOTM1MDYxMDJjZjgxODU2MDIwODYwMTYxMDFhMjU2NWI2MTAyZDg4MTYxMDBlNTU2NWI4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTAyZmQ4MTg0NjEwMmFhNTY1YjkwNTA5MjkxNTA1MDU2ZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MTAyNjIzODAzODA2MTAyNjI4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDMyOTE5MDYxMDFjYzU2NWI2MGNjNjBiYjYwYWE2MDAxODRhMzYwZGQ2MGNjNjBiYjYwYWE2MDAxODVhNDUwNjEwMjE1NTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA2MDFmMTk2MDFmODMwMTE2OTA1MDkxOTA1MDU2NWI3ZjRlNDg3YjcxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwNTI2MDQxNjAwNDUyNjAyNDYwMDBmZDViNjEwMGI1ODI2MTAwNmM1NjViODEwMTgxODExMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNzE1NjEwMGQ0NTc2MTAwZDM2MTAwN2Q1NjViNWI4MDYwNDA1MjUwNTA1MDU2NWI2MDAwNjEwMGU3NjEwMDRlNTY1YjkwNTA2MTAwZjM4MjgyNjEwMGFjNTY1YjkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwMTEzNTc2MTAxMTI2MTAwN2Q1NjViNWI2MTAxMWM4MjYxMDA2YzU2NWI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMTQ3NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMTJjNTY1YjgzODExMTE1NjEwMTU2NTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTAxNmY2MTAxNmE4NDYxMDBmODU2NWI2MTAwZGQ1NjViOTA1MDgyODE1MjYwMjA4MTAxODQ4NDg0MDExMTE1NjEwMThiNTc2MTAxOGE2MTAwNjc1NjViNWI2MTAxOTY4NDgyODU2MTAxMjk1NjViNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTAxYjM1NzYxMDFiMjYxMDA2MjU2NWI1YjgxNTE2MTAxYzM4NDgyNjAyMDg2MDE2MTAxNWM1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwh4d0DryklKgCS66K8k4W/C5+N2WjIqJoWMf9MaImM7bVZiemP9qXThfghLMhRktyGgsIsPf/qwYQ853rdyIPCgkI9Pb/qwYQkwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj09v+rBhCZBxICGAISAhgDGMPryS4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBjgMSAxjTFyKGAzEyMTU2MTAxZTI1NzYxMDFlMTYxMDA1ODU2NWI1YjYwMDA4MjAxNTE2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAyMDA1NzYxMDFmZjYxMDA1ZDU2NWI1YjYxMDIwYzg0ODI4NTAxNjEwMTllNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwM2Y4MDYxMDIyMzYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDA4MGZkZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGZiZmM0YWQ1M2ZmYzcwMjYxOWM4ZDNmYTg4NWRiZGMzNWZlMGQ3MGJiN2RhODFmZmUyYjYwNTI0NzE2MzFhYjY2NDczNmY2YzYzNDMwMDA4MGMwMDMzYTI2NDY5NzA2NjczNTgyMjEyMjAxMjZkODg1Yjc4MzA4MWZiMDNmNmE0YjMwMDQ5NTZhMzBjNDNkNDFjZjNjNzc0YzA2OGVkMmQwODMxYTE4MjVmNjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw8B1dTkjl5mZGzPrPqpWNpg2AuDGNqhxRcpvMycUClloR/RAjSftljh2cdhvrWTF9GgwIsPf/qwYQs7662wIiDwoJCPT2/6sGEJkHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj19v+rBhCbBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGNMXGiISIM4t3Z2118xdS0ZJTOdEyHVSiA653nrNFX1eOo9N7iBDIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGNQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC0p7WCMre32xu7eD2jtd0zqblRDcO9DxC2Lq2qioKeO0QnJjdtp/XpfbIcKWG5ukYaDAix9/+rBhCL5YyAASIPCgkI9fb/qwYQmwcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQtgTCgMY1BcSoxFggGBAUjSAFWEAEFdgAID9W1BgBDYQYQAqV2AANWDgHIBiYQ8jFGEAL1dbYACA/VthAElgBIA2A4EBkGEARJGQYQISVlthAEtWWwBbfwEBAQEBAQEAEBAQEBAQEAEBAQEBAQEBEQEBAQEBAQEBYAAbgWBAUWEAfJBhAKtWW2EAhpGQYQLjVluBkGBAUYCRA5BgAPWQUIAVgBVhAKZXPWAAgD49YAD9W1BQUFZbYQVogGEDBoM5AZBWW2AAYEBRkFCQVltgAID9W2AAgP1bYACA/VtgAID9W2AAYB8ZYB+DARaQUJGQUFZbf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VthAR+CYQDWVluBAYGBEGf//////////4IRFxVhAT5XYQE9YQDnVltbgGBAUlBQUFZbYABhAVFhALhWW5BQYQFdgoJhARZWW5GQUFZbYABn//////////+CERVhAX1XYQF8YQDnVltbYQGGgmEA1lZbkFBgIIEBkFCRkFBWW4KBgzdgAIODAVJQUFBWW2AAYQG1YQGwhGEBYlZbYQFHVluQUIKBUmAggQGEhIQBERVhAdFXYQHQYQDRVltbYQHchIKFYQGTVltQk5JQUFBWW2AAgmAfgwESYQH5V2EB+GEAzFZbW4E1YQIJhIJgIIYBYQGiVluRUFCSkVBQVltgAGAggoQDEhVhAihXYQInYQDCVltbYACCATVn//////////+BERVhAkZXYQJFYQDHVltbYQJShIKFAWEB5FZbkVBQkpFQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBVhApVXgIIBUYGEAVJgIIEBkFBhAnpWW4OBERVhAqRXYACEhAFSW1BQUFBWW2AAYQK1gmECW1ZbYQK/gYVhAmZWW5NQYQLPgYVgIIYBYQJ3VlthAtiBYQDWVluEAZFQUJKRUFBWW2AAYCCCAZBQgYEDYACDAVJhAv2BhGECqlZbkFCSkVBQVv5ggGBAUjSAFWEAEFdgAID9W1BgQFFhBWg4A4BhBWiDOYGBAWBAUoEBkGEAMpGQYQJFVltgAYGgYKpgAYKhYLtgqmABg6J/AgICAgICAgAgICAgICAgAgICAgICAgIiAgICAgICAgJgABuBYEBRYQB1kGEAulZbYQB/kZBhAuNWW4GQYEBRgJEDkGAA9ZBQgBWAFWEAn1c9YACAPj1gAP1bUFAzc///////////////////////////Fv9bYQJigGEDBoM5AZBWW2AAYEBRkFCQVltgAID9W2AAgP1bYACA/VtgAID9W2AAYB8ZYB+DARaQUJGQUFZbf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VthAS6CYQDlVluBAYGBEGf//////////4IRFxVhAU1XYQFMYQD2VltbgGBAUlBQUFZbYABhAWBhAMdWW5BQYQFsgoJhASVWW5GQUFZbYABn//////////+CERVhAYxXYQGLYQD2VltbYQGVgmEA5VZbkFBgIIEBkFCRkFBWW2AAW4OBEBVhAcBXgIIBUYGEAVJgIIEBkFBhAaVWW4OBERVhAc9XYACEhAFSW1BQUFBWW2AAYQHoYQHjhGEBcVZbYQFWVluQUIKBUmAggQGEhIQBERVhAgRXYQIDYQDgVltbYQIPhIKFYQGiVltQk5JQUFBWW2AAgmAfgwESYQIsV2ECK2EA21ZbW4FRYQI8hIJgIIYBYQHVVluRUFCSkVBQVltgAGAggoQDEhVhAltXYQJaYQDRVltbYACCAVFn//////////+BERVhAnlXYQJ4YQDWVltbYQKFhIKFAWECF1ZbkVBQkpFQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAYQK1gmECjlZbYQK/gYVhAplWW5NQYQLPgYVgIIYBYQGiVlthAtiBYQDlVluEAZFQUJKRUFBWW2AAYCCCAZBQgYEDYACDAVJhAv2BhGECqlZbkFCSkVBQVv5ggGBAUjSAFWEAEFdgAID9W1BgQFFhAmI4A4BhAmKDOYGBAWBAUoEBkGEAMpGQYQHMVltgzGC7YKpgAYSjYN1gzGC7YKpgAYWkUGECFVZbYABgQFGQUJBWW2AAgP1bYACA/VtgAID9W2AAgP1bYABgHxlgH4MBFpBQkZBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EAtYJhAGxWW4EBgYEQZ///////////ghEXFWEA1FdhANNhAH1WW1uAYEBSUFBQVltgAGEA52EATlZbkFBhAPOCgmEArFZbkZBQVltgAGf//////////4IRFWEBE1dhARJhAH1WW1thARyCYQBsVluQUGAggQGQUJGQUFZbYABbg4EQFWEBR1eAggFRgYQBUmAggQGQUGEBLFZbg4ERFWEBVldgAISEAVJbUFBQUFZbYABhAW9hAWqEYQD4VlthAN1WW5BQgoFSYCCBAYSEhAERFWEBi1dhAYphAGdWW1thAZaEgoVhASlWW1CTklBQUFZbYACCYB+DARJhAbNXYQGyYQBiVltbgVFhAcOEgmAghgFhAVxWW5FQUJKRUFBWW2AAYCCChAMSFWEB4ldhAeFhAFhWW1tgAIIBUWf//////////4ERFWECAFdhAf9hAF1WW1thAgyEgoUBYQGeVluRUFCSkVBQVltgP4BhAiNgADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiD7/ErVP/xwJhnI0/qIXb3DX+DXC7fagf/itgUkcWMatmRzb2xjQwAIDAAzomRpcGZzWCISIBJtiFt4MIH7A/akswBJVqMMQ9Qc88d0wGjtLQgxoYJfZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGNQXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAvUcgcKAxjUFxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQj19v+rBhCdBxICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46cAoDGNQXEICS9AEiZABhDyMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGNQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDmeMvSk0NhcFATEAlvzWraQINqIToK9G9QztFtmntKPsH70EE0fhHMkIaTfEJayCsaDAix9/+rBhD7jPjlAiIPCgkI9fb/qwYQnQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOsMPCgMY1BcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAACAAAAAAAAAAABAAAAIAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEAAAEAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEACAAAAAAAAAAQAAAAACAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwEyiwIKAxjVFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAQAyrQIKAxjVFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqIgEAMs8CCgMY1RcSgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqhogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsiAQAy8QIKAxjWFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAIAAAAAAAAAAAEAAAAgAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAABAAAAAAIAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuxogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwiAQAykwMKAxjWFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAIAAAAAAAAAAAEAAAAgAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAQAAAQAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAABAAAAAAIAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACqGiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuxogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwaIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADdIgEAOgMY1Rc6AxjWF3IHCgMY1BcQAnIHCgMY1hcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQj19v+rBhCdBxICGAIgAUI4GiISIM4t3Z2118xdS0ZJTOdEyHVSiA653nrNFX1eOo9N7iBDQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGNYXEjAbeHWy4ysvDEKyOsvwA0Qi333Wg+tfJ2g5uhQhRfd2xIgBXIF3y+KletNDDx0GZlwaDAix9/+rBhD8jPjlAiIRCgkI9fb/qwYQnQcSAhgCIAFCHQoDGNYXShYKFP+d7O7ELWK3W8enCiRH4nrAcyEvUgB6DAix9/+rBhD7jPjlAg=="},{"b64Body":"ChEKCQj19v+rBhCdBxICGAIgAkI4GiISIM4t3Z2118xdS0ZJTOdEyHVSiA653nrNFX1eOo9N7iBDQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGNUXEjAq8JGNc92oPLfSpfKbtuecyVi2ky4np8PMTB/BqGxFe2ddFa43vTHnQp6NjduP9igaDAix9/+rBhD9jPjlAiIRCgkI9fb/qwYQnQcSAhgCIAJCHQoDGNUXShYKFExjW/Dh0iSurYOxyi6Er7CjSPzmUgB6DAix9/+rBhD7jPjlAg=="}]},"Eip1014AliasIsPriorityInErcOwnerPrecompile":{"placeholderNum":3031,"encodedItems":[{"b64Body":"Cg8KCQj69v+rBhCxBxICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIPYvuHV/ijglIIxlybRt0mv11KkirSZ+5aQ/u44IsGkiEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGNgXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD8gL7mOUP/+V+7sfPxORupchraKbjyiypTwm/FdgbmC8rnjlBW6U2oXbJuIyvcDZoaDAi29/+rBhDTyKiTASIPCgkI+vb/qwYQsQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjYFxCAqNa5Bw=="},{"b64Body":"Cg8KCQj69v+rBhCzBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi2xdqvBhDo17DsAhptCiISIAyo2vTiafX3ztAKCS0Aa8KtpV4NQzbb/1QjvLvEhVumCiM6IQN35MB2x4XwQejNXuLR2rwuxIz2AZH8PEDISwD7sxrEvgoiEiBlbsuXChpTi/FXu/f3R/nX8Z81kBQGi3ZyRC6h4DlL8yIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGNkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDcunQXSiLB/AXjWO4pi5+MBuvN9Vd29otbsdqQBt6qqxYGnsS74cUCb9dFA4GcJ0UaDAi29/+rBhCjgYf4AiIPCgkI+vb/qwYQswcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj79v+rBhC3BxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjZFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTJhMjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDEwMDU3NjAwMDM1NjBlMDFjODA2M2E4NmUzNTc2MTE2MTAwOTc1NzgwNjNlNGRjMmFhNDExNjEwMDY2NTc4MDYzZTRkYzJhYTQxNDYxMDI1NTU3ODA2M2U5ZGM2Mzc1MTQ2MTAyNzE1NzgwNjNmNDlmNDBkYjE0NjEwMjhkNTc4MDYzZjc4ODhhZWMxNDYxMDJhOTU3NjEwMTAwNTY1YjgwNjNhODZlMzU3NjE0NjEwMWU1NTc4MDYzY2I2MGYxYmYxNDYxMDIwMTU3ODA2M2Q5ZmM0YjYxMTQ2MTAyMWQ1NzgwNjNlMWYyMWM2NzE0NjEwMjM5NTc2MTAxMDA1NjViODA2MzFmMjlkMmRjMTE2MTAwZDM1NzgwNjMxZjI5ZDJkYzE0NjEwMTc1NTc4MDYzMzY3NjA1Y2ExNDYxMDE5MTU3ODA2MzZiNDJiZjJmMTQ2MTAxYWQ1NzgwNjM5OGRjYTViZTE0NjEwMWM5NTc2MTAxMDA1NjViODA2MzAxOTg0ODkyMTQ2MTAxMDU1NzgwNjMwMzlkNmYxOTE0NjEwMTIxNTc4MDYzMDk4ZjIzNjYxNDYxMDEzZDU3ODA2MzE1ZGFjYmVhMTQ2MTAxNTk1NzViNjAwMDgwZmQ1YjYxMDExZjYwMDQ4MDM2MDM4MTAxOTA2MTAxMWE5MTkwNjEwYWQ3NTY1YjYxMDJjNTU2NWIwMDViNjEwMTNiNjAwNDgwMzYwMzgxMDE5MDYxMDEzNjkxOTA2MTBiOWY1NjViNjEwMzNkNTY1YjAwNWI2MTAxNTc2MDA0ODAzNjAzODEwMTkwNjEwMTUyOTE5MDYxMGMzOTU2NWI2MTAzYjg1NjViMDA1YjYxMDE3MzYwMDQ4MDM2MDM4MTAxOTA2MTAxNmU5MTkwNjEwYzc5NTY1YjYxMDQzNzU2NWIwMDViNjEwMThmNjAwNDgwMzYwMzgxMDE5MDYxMDE4YTkxOTA2MTBjMzk1NjViNjEwNGFjNTY1YjAwNWI2MTAxYWI2MDA0ODAzNjAzODEwMTkwNjEwMWE2OTE5MDYxMGQxODU2NWI2MTA1MmI1NjViMDA1YjYxMDFjNzYwMDQ4MDM2MDM4MTAxOTA2MTAxYzI5MTkwNjEwZDZiNTY1YjYxMDU5ZDU2NWIwMDViNjEwMWUzNjAwNDgwMzYwMzgxMDE5MDYxMDFkZTkxOTA2MTBjNzk1NjViNjEwNjFmNTY1YjAwNWI2MTAxZmY2MDA0ODAzNjAzODEwMTkwNjEwMWZhOTE5MDYxMGFkNzU2NWI2MTA2OGY1NjViMDA1YjYxMDIxYjYwMDQ4MDM2MDM4MTAxOTA2MTAyMTY5MTkwNjEwYzM5NTY1YjYxMDcwNzU2NWIwMDViNjEwMjM3NjAwNDgwMzYwMzgxMDE5MDYxMDIzMjkxOTA2MTBjNzk1NjViNjEwNzg2NTY1YjAwNWI2MTAyNTM2MDA0ODAzNjAzODEwMTkwNjEwMjRlOTE5MDYxMGQ2YjU2NWI2MTA3ZmI1NjViMDA1YjYxMDI2ZjYwMDQ4MDM2MDM4MTAxOTA2MTAyNmE5MTkwNjEwYWQ3NTY1YjYxMDg2ZDU2NWIwMDViNjEwMjhiNjAwNDgwMzYwMzgxMDE5MDYxMDI4NjkxOTA2MTBjMzk1NjViNjEwOGUwNTY1YjAwNWI2MTAyYTc2MDA0ODAzNjAzODEwMTkwNjEwMmEyOTE5MDYxMGRiZTU2NWI2MTA5NjQ1NjViMDA1YjYxMDJjMzYwMDQ4MDM2MDM4MTAxOTA2MTAyYmU5MTkwNjEwZTExNTY1YjYxMDllNjU2NWIwMDViODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDZmZGRlMDM2MDQwNTE4MTYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDAwNjA0MDUxODA4MzAzODE4NjVhZmExNTgwMTU2MTAzMTA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMDA4MjNlM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTAzMzk5MTkwNjEwZmFkNTY1YjUwNTA1NjViODU3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYjg4ZDRmZGU4Njg2ODY4Njg2NjA0MDUxODY2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwMzdlOTU5NDkzOTI5MTkwNjExMDYxNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTAzOTg1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTAzYWM1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTA1MDUwNTA1NjViODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDgxODEyZmM4MjYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDNmMTkxOTA2MTEwYWY1NjViNjAyMDYwNDA1MTgwODMwMzgxODY1YWZhMTU4MDE1NjEwNDBlNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjA0MDUxM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTA0MzI5MTkwNjExMGRmNTY1YjUwNTA1MDU2NWI4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMyM2I4NzJkZDg0ODQ4NDYwNDA1MTg0NjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDQ3NDkzOTI5MTkwNjExMTBjNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA0OGU1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA0YTI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTA1MDU2NWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM2MzUyMjExZTgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwNGU1OTE5MDYxMTBhZjU2NWI2MDIwNjA0MDUxODA4MzAzODE4NjVhZmExNTgwMTU2MTA1MDI1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMWYxOTYwMWY4MjAxMTY4MjAxODA2MDQwNTI1MDgxMDE5MDYxMDUyNjkxOTA2MTEwZGY1NjViNTA1MDUwNTY1YjgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2EyMmNiNDY1ODM4MzYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDU2NjkyOTE5MDYxMTE1MjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNTgwNTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNTk0NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTY1YjgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzJmNzQ1YzU5ODM4MzYwNDA1MTgzNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDVkODkyOTE5MDYxMTE3YjU2NWI2MDIwNjA0MDUxODA4MzAzODE4NjVhZmExNTgwMTU2MTA1ZjU1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMWYxOTYwMWY4MjAxMTY4MjAxODA2MDQwNTI1MDgxMDE5MDYxMDYxOTkxOTA2MTExYjk1NjViNTA1MDUwNTA1NjViODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMjNiODcyZGQ4NDg0ODQ2MDQwNTE4NDYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTA2NWM5MzkyOTE5MDYxMTEwYzU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODc4MDNiMTU4MDE1NjEwNjc2NTc2MDAwODBmZDViNTA1YWYxMTU4MDE1NjEwNjhhNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjYwMDA4MGZkNWI4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM5NWQ4OWI0MTYwNDA1MTgxNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYwMDA2MDQwNTE4MDgzMDM4MTg2NWFmYTE1ODAxNTYxMDZkYTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAwMDgyM2UzZDYwMWYxOTYwMWY4MjAxMTY4MjAxODA2MDQwNTI1MDgxMDE5MDYxMDcwMzkxOTA2MTBmYWQ1NjViNTA1MDU2NWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0ZjZjY2NlNzgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwNzQwOTE5MDYxMTBhZjU2NWI2MDIwNjA0MDUxODA4MzAzODE4NjVhZmExNTgwMTU2MTA3NWQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMWYxOTYwMWY4MjAxMTY4MjAxODA2MDQwNTI1MDgxMDE5MDYxMDc4MTkxOTA2MTExYjk1NjViNTA1MDUwNTY1YjgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzQyODQyZTBlODQ4NDg0NjA0MDUxODQ2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwN2MzOTM5MjkxOTA2MTExMGM1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDdkZDU3NjAwMDgwZmQ1YjUwNWE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwdDYSbvAb3YGwmiunrAbeuXkj3piO0ZWC92auMRskp7G5JoDwz7/M78q9BaRWhi6jGgwIt/f/qwYQ49j1nAEiDwoJCPv2/6sGELcHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj79v+rBhC9BxICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjZFyKAIGYxMTU4MDE1NjEwN2YxNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNTA1MDUwNTA1NjViODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDk1ZWE3YjM4MzgzNjA0MDUxODM2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwODM2OTI5MTkwNjExMTdiNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA4NTA1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA4NjQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTA1NjViODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMTgxNjBkZGQ2MDQwNTE4MTYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MDIwNjA0MDUxODA4MzAzODE4NjVhZmExNTgwMTU2MTA4Yjg1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMWYxOTYwMWY4MjAxMTY4MjAxODA2MDQwNTI1MDgxMDE5MDYxMDhkYzkxOTA2MTExYjk1NjViNTA1MDU2NWI4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNjODdiNTZkZDgyNjA0MDUxODI2M2ZmZmZmZmZmMTY2MGUwMWI4MTUyNjAwNDAxNjEwOTE5OTE5MDYxMTBhZjU2NWI2MDAwNjA0MDUxODA4MzAzODE4NjVhZmExNTgwMTU2MTA5MzY1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMDA4MjNlM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTA5NWY5MTkwNjEwZmFkNTY1YjUwNTA1MDU2NWI4MjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjNlOTg1ZTljNTgzODM2MDQwNTE4MzYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTA5OWY5MjkxOTA2MTExZTY1NjViNjAyMDYwNDA1MTgwODMwMzgxODY1YWZhMTU4MDE1NjEwOWJjNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1MDUwNjA0MDUxM2Q2MDFmMTk2MDFmODIwMTE2ODIwMTgwNjA0MDUyNTA4MTAxOTA2MTA5ZTA5MTkwNjExMjI0NTY1YjUwNTA1MDUwNTY1YjgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzcwYTA4MjMxODI2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTBhMWY5MTkwNjExMjUxNTY1YjYwMjA2MDQwNTE4MDgzMDM4MTg2NWFmYTE1ODAxNTYxMGEzYzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDYwNDA1MTNkNjAxZjE5NjAxZjgyMDExNjgyMDE4MDYwNDA1MjUwODEwMTkwNjEwYTYwOTE5MDYxMTFiOTU2NWI1MDUwNTA1NjViNjAwMDYwNDA1MTkwNTA5MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwYWE0ODI2MTBhNzk1NjViOTA1MDkxOTA1MDU2NWI2MTBhYjQ4MTYxMGE5OTU2NWI4MTE0NjEwYWJmNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwYWQxODE2MTBhYWI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwYWVkNTc2MTBhZWM2MTBhNmY1NjViNWI2MDAwNjEwYWZiODQ4Mjg1MDE2MTBhYzI1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBiMTc4MTYxMGIwNDU2NWI4MTE0NjEwYjIyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxMzU5MDUwNjEwYjM0ODE2MTBiMGU1NjViOTI5MTUwNTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDgwODM2MDFmODQwMTEyNjEwYjVmNTc2MTBiNWU2MTBiM2E1NjViNWI4MjM1OTA1MDY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGI3YzU3NjEwYjdiNjEwYjNmNTY1YjViNjAyMDgzMDE5MTUwODM2MDAxODIwMjgzMDExMTE1NjEwYjk4NTc2MTBiOTc2MTBiNDQ1NjViNWI5MjUwOTI5MDUwNTY1YjYwMDA4MDYwMDA4MDYwMDA4MDYwYTA4Nzg5MDMxMjE1NjEwYmJjNTc2MTBiYmI2MTBhNmY1NjViNWI2MDAwNjEwYmNhODk4MjhhMDE2MTBhYzI1NjViOTY1MDUwNjAyMDYxMGJkYjg5ODI4YTAxNjEwYWMyNTY1Yjk1NTA1MDYwNDA2MTBiZWM4OTgyOGEwMTYxMGFjMjU2NWI5NDUwNTA2MDYwNjEwYmZkODk4MjhhMDE2MTBiMjU1NjViOTM1MDUwNjA4MDg3MDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGMxZTU3NjEwYzFkNjEwYTc0NTY1YjViNjEwYzJhODk4MjhhMDE2MTBiNDk1NjViOTI1MDkyNTA1MDkyOTU1MDkyOTU1MDkyOTU1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTBjNTA1NzYxMGM0ZjYxMGE2ZjU2NWI1YjYwMDA2MTBjNWU4NTgyODYwMTYxMGFjMjU2NWI5MjUwNTA2MDIwNjEwYzZmODU4Mjg2MDE2MTBiMjU1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODA2MDAwODA2MDgwODU4NzAzMTIxNTYxMGM5MzU3NjEwYzkyNjEwYTZmNTY1YjViNjAwMDYxMGNhMTg3ODI4ODAxNjEwYWMyNTY1Yjk0NTA1MDYwMjA2MTBjYjI4NzgyODgwMTYxMGFjMjU2NWI5MzUwNTA2MDQwNjEwY2MzODc4Mjg4MDE2MTBhYzI1NjViOTI1MDUwNjA2MDYxMGNkNDg3ODI4ODAxNjEwYjI1NTY1YjkxNTA1MDkyOTU5MTk0NTA5MjUwNTY1YjYwMDA4MTE1MTU5MDUwOTE5MDUwNTY1YjYxMGNmNTgxNjEwY2UwNTY1YjgxMTQ2MTBkMDA1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTBkMTI4MTYxMGNlYzU2NWI5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTBkMzE1NzYxMGQzMDYxMGE2ZjU2NWI1YjYwMDA2MTBkM2Y4NjgyODcwMTYxMGFjMjU2NWI5MzUwNTA2MDIwNjEwZDUwODY4Mjg3MDE2MTBhYzI1NjViOTI1MDUwNjA0MDYxMGQ2MTg2ODI4NzAxNjEwZDAzNTY1YjkxNTA1MDkyNTA5MjUwOTI1NjViNjAwMDgwNjAwMDYwNjA4NDg2MDMxMjE1NjEwZDg0NTc2MTBkODM2MTBhNmY1NjViNWI2MDAwNjEwZDkyODY4Mjg3MDE2MTBhYzI1NjViOTM1MDUwNjAyMDYxMGRhMzg2ODI4NzAxNjEwYWMyNTY1YjkyNTA1MDYwNDA2MTBkYjQ4NjgyODcwMTYxMGIyNTU2NWI5MTUwNTA5MjUwOTI1MDkyNTY1YjYwMDA4MDYwMDA2MDYwODQ4NjAzMTIxNTYxMGRkNzU3NjEwZGQ2NjEwYTZmNTY1YjViNjAwMDYxMGRlNTg2ODI4NzAxNjEwYWMyNTY1YjkzNTA1MDYwMjA2MTBkZjY4NjgyODcwMTYxMGFjMjU2NWI5MjUwNTA2MDQwNjEwZTA3ODY4Mjg3MDE2MTBhYzI1NjViOTE1MDUwOTI1MDkyNTA5MjU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMGUyODU3NjEwZTI3NjEwYTZmNTY1YjViNjAwMDYxMGUzNjg1ODI4NjAxNjEwYWMyNTY1YjkyNTA1MDYwMjA2MTBlNDc4NTgyODYwMTYxMGFjMjU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA4MGZkNWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMGU5ZjgyNjEwZTU2NTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMGViZTU3NjEwZWJkNjEwZTY3NTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMGVkMTYxMGE2NTU2NWI5MDUwNjEwZWRkODI4MjYxMGU5NjU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMGVmZDU3NjEwZWZjNjEwZTY3NTY1YjViNjEwZjA2ODI2MTBlNTY1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMGYzMTU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMGYxNjU2NWI2MDAwODQ4NDAxNTI1MDUwNTA1MDU2NWI2MDAwNjEwZjUwNjEwZjRiODQ2MTBlZTI1NjViNjEwZWM3NTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMGY2YzU3NjEwZjZiNjEwZTUxNTY1YjViNjEwZjc3ODQ4Mjg1NjEwZjEzNTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwZjk0NTc2MTBmOTM2MTBiM2E1NjViNWI4MTUxNjEwZmE0ODQ4MjYwMjA4NjAxNjEwZjNkNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMGZjMzU3NjEwZmMyNjEwYTZmNTY1YjViNjAwMDgyMDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGZlMTU3NjEwZmUwNjEwYTc0NTY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwyBCzqEnZ3GMhEgvN+2DgEY3YfkMs05z4r11XVyWrO9+x5u+mU7sWTfDkx7VYkb3zGgwIt/f/qwYQ+8G8gQMiDwoJCPv2/6sGEL0HEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj89v+rBhDDBxICGAISAhgDGMLUhzMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBjAsSAxjZFyKECzViNWI2MTBmZWQ4NDgyODUwMTYxMGY3ZjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MTBmZmY4MTYxMGE5OTU2NWI4MjUyNTA1MDU2NWI2MTEwMGU4MTYxMGIwNDU2NWI4MjUyNTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMTA0MDgzODU2MTEwMTQ1NjViOTM1MDYxMTA0ZDgzODU4NDYxMTAyNTU2NWI2MTEwNTY4MzYxMGU1NjU2NWI4NDAxOTA1MDkzOTI1MDUwNTA1NjViNjAwMDYwODA4MjAxOTA1MDYxMTA3NjYwMDA4MzAxODg2MTBmZjY1NjViNjExMDgzNjAyMDgzMDE4NzYxMGZmNjU2NWI2MTEwOTA2MDQwODMwMTg2NjExMDA1NTY1YjgxODEwMzYwNjA4MzAxNTI2MTEwYTM4MTg0ODY2MTEwMzQ1NjViOTA1MDk2OTU1MDUwNTA1MDUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMTBjNDYwMDA4MzAxODQ2MTEwMDU1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwNjExMGQ5ODE2MTBhYWI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjExMGY1NTc2MTEwZjQ2MTBhNmY1NjViNWI2MDAwNjExMTAzODQ4Mjg1MDE2MTEwY2E1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNjA4MjAxOTA1MDYxMTEyMTYwMDA4MzAxODY2MTBmZjY1NjViNjExMTJlNjAyMDgzMDE4NTYxMGZmNjU2NWI2MTExM2I2MDQwODMwMTg0NjExMDA1NTY1Yjk0OTM1MDUwNTA1MDU2NWI2MTExNGM4MTYxMGNlMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjExMTY3NjAwMDgzMDE4NTYxMGZmNjU2NWI2MTExNzQ2MDIwODMwMTg0NjExMTQzNTY1YjkzOTI1MDUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMTE5MDYwMDA4MzAxODU2MTBmZjY1NjViNjExMTlkNjAyMDgzMDE4NDYxMTAwNTU2NWI5MzkyNTA1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMTFiMzgxNjEwYjBlNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMTFjZjU3NjExMWNlNjEwYTZmNTY1YjViNjAwMDYxMTFkZDg0ODI4NTAxNjExMWE0NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTExZmI2MDAwODMwMTg1NjEwZmY2NTY1YjYxMTIwODYwMjA4MzAxODQ2MTBmZjY1NjViOTM5MjUwNTA1MDU2NWI2MDAwODE1MTkwNTA2MTEyMWU4MTYxMGNlYzU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTEyM2E1NzYxMTIzOTYxMGE2ZjU2NWI1YjYwMDA2MTEyNDg4NDgyODUwMTYxMTIwZjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjExMjY2NjAwMDgzMDE4NDYxMGZmNjU2NWI5MjkxNTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGY3ZjYyNzZjN2Q3NTBkYzhkNzJhMzk4ZDExNmMwZTZlMzgzOTMxOGU4NzJlOWMzZTc5ZmU5MjcwNDA4ZTAwMTI2NDczNmY2YzYzNDMwMDA4MTAwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwquEiI9jVJwB3lJUMk1tOSb+HS/V+C+0E29R63eUqQwKZDczwrkACBGBx5Ie+LReeGgwIuPf/qwYQi8GjpgEiDwoJCPz2/6sGEMMHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj89v+rBhDFBxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi4xdqvBhD4/bf8AhptCiISIL6e7bDfOtgtqBr1zTEey5TG7VhJ4DmakeJlZCH70dA2CiM6IQIuTWSS7hCjbltZY6Frmz+eQsp8hvA3vTmRRE5o+YzOmQoiEiC0GAjxr6ti0LNmfZJ1h7W6p8e5N3iMKEKroOgavyfHxiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGNoXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAB6XCIHgE1un+PxZUhQ8wydkDCZKFFO91kU/foJPT6h+nw9OfmvHVetnu6jk4EB3oaDAi49/+rBhCr1pKLAyIPCgkI/Pb/qwYQxQcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj99v+rBhDJBxICGAISAhgDGIudjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjaFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMjgwMjgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA1NzU3NjAwMDM1NjBlMDFjODA2MzQyNWRiZjk2MTQ2MTAwNWM1NzgwNjM2N2Q3MzFkMzE0NjEwMDhjNTc4MDYzNzA3YzUwMTkxNDYxMDBiYzU3ODA2M2FiZjdiZmQ4MTQ2MTAwZDg1NzgwNjNjYTk3MjE2MTE0NjEwMGY0NTc1YjYwMDA4MGZkNWI2MTAwNzY2MDA0ODAzNjAzODEwMTkwNjEwMDcxOTE5MDYxMDlhMTU2NWI2MTAxMTA1NjViNjA0MDUxNjEwMDgzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwYTY2MDA0ODAzNjAzODEwMTkwNjEwMGExOTE5MDYxMDlhMTU2NWI2MTAxNDY1NjViNjA0MDUxNjEwMGIzOTE5MDYxMDlmYTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwZDY2MDA0ODAzNjAzODEwMTkwNjEwMGQxOTE5MDYxMGExNTU2NWI2MTAxN2M1NjViMDA1YjYxMDBmMjYwMDQ4MDM2MDM4MTAxOTA2MTAwZWQ5MTkwNjEwYTc4NTY1YjYxMDI1ZjU2NWIwMDViNjEwMTBlNjAwNDgwMzYwMzgxMDE5MDYxMDEwOTkxOTA2MTBhMTU1NjViNjEwM2E5NTY1YjAwNWI2MDAwNjEwMTNlODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNDhjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTc0ODMzMDYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjg1NjEwNWFhNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwMTg4MzA4MzYxMDZjODU2NWI5MDUwNjAxNjYwMDMwYjgxMTQ2MTAxZDA1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAxYzc5MDYxMGIwMjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI2MDAwODA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZDk0MjU0MDM4MzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDIyOTkxOTA2MTBiMzE1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDI0MzU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDI1NzU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTA1NjViNjAwMDYwZmY2MGY4MWIzMDgzNjA0MDUxODA2MDIwMDE2MTAyNzg5MDYxMDhmODU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwNjA0MDUxNjAyMDAxNjEwMjljOTE5MDYxMGJjNjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDYwNDA1MTYwMjAwMTYxMDJjNTk0OTM5MjkxOTA2MTBjOTM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyODA1MTkwNjAyMDAxMjA2MDAwMWM5MDUwODE2MDQwNTE2MTAyZWQ5MDYxMDhmODU2NWI4MTkwNjA0MDUxODA5MTAzOTA2MDAwZjU5MDUwODAxNTgwMTU2MTAzMGQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAwODA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYxNDYxMDNhNTU3NjAwMDgwZmQ1YjUwNTA1NjViNjAwMDYxMDNiNTMwODM2MTA3ZTA1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwM2ZkNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwM2Y0OTA2MTBkMmQ1NjViNjA0MDUxODA5MTAzOTBmZDViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2M2VhNThjZTIxODM2MDQwNTE4MjYzZmZmZmZmZmYxNjYwZTAxYjgxNTI2MDA0MDE2MTA0NTY5MTkwNjEwYjMxNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTA0NzA1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTA0ODQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA1MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzZWNhMzY5MTc2MGUwMWI4ODg4ODg4ODYwNDA1MTYwMjQwMTYxMDRjOTk0OTM5MjkxOTA2MTBkNWM1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNTMzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNTcwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNTc1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA1ODY1NzYwMTU2MTA1OWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA1OWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5NDkzNTA1MDUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM1Y2ZjOTAxMTYwZTAxYjg4ODg4ODg4NjA0MDUxNjAyNDAxNjEwNWU3OTQ5MzkyOTE5MDYxMGQ1YzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA2NTE5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA2OGU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA2OTM1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDZhNDU3NjAxNTYxMDZiOTU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDZiODkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzA5OTc5NGU4NjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDcwMTkyOTE5MDYxMGUwNzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTA3NmI5MTkwNjEwYmM2NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTA3YTg1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTA3YWQ1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDdiZTU3NjAxNTYxMDdkMzU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDdkMjkxOTA2MTBkZGE1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwL+Mj4bE8EdrF5JN2genC1AL83AEvD4h1b/yp1ssTQ05YyTYBqrQU2JKGl5Ky9rFSGgwIuff/qwYQu+P4kgEiDwoJCP32/6sGEMkHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj99v+rBhDPBxICGAISAhgDGPjR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjaFyKAIDViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA4MTk5MjkxOTA2MTBlMDc1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwODgzOTE5MDYxMGJjNjU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwOGMwNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwOGM1NTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA4ZDY1NzYwMTU2MTA4ZWI1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA4ZWE5MTkwNjEwZGRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTE5OWM4MDYxMGUzMTgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDkzNTgyNjEwOTBhNTY1YjkwNTA5MTkwNTA1NjViNjEwOTQ1ODE2MTA5MmE1NjViODExNDYxMDk1MDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk2MjgxNjEwOTNjNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTYwMDcwYjkwNTA5MTkwNTA1NjViNjEwOTdlODE2MTA5Njg1NjViODExNDYxMDk4OTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDk5YjgxNjEwOTc1NTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwOWI4NTc2MTA5Yjc2MTA5MDU1NjViNWI2MDAwNjEwOWM2ODU4Mjg2MDE2MTA5NTM1NjViOTI1MDUwNjAyMDYxMDlkNzg1ODI4NjAxNjEwOThjNTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTA5ZjQ4MTYxMDllMTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwYTBmNjAwMDgzMDE4NDYxMDllYjU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhMmI1NzYxMGEyYTYxMDkwNTU2NWI1YjYwMDA2MTBhMzk4NDgyODUwMTYxMDk1MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMGE1NTgxNjEwYTQyNTY1YjgxMTQ2MTBhNjA1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTBhNzI4MTYxMGE0YzU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTBhOGU1NzYxMGE4ZDYxMDkwNTU2NWI1YjYwMDA2MTBhOWM4NDgyODUwMTYxMGE2MzU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0ZTY1NzY2NTcyMjA2NTZlNjQ3MzIwNzc2NTZjNmMyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBhZWM2MDEwODM2MTBhYTU1NjViOTE1MDYxMGFmNzgyNjEwYWI2NTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGIxYjgxNjEwYWRmNTY1YjkwNTA5MTkwNTA1NjViNjEwYjJiODE2MTA5MmE1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMGI0NjYwMDA4MzAxODQ2MTBiMjI1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMGI4MDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMGI2NTU2NWI4MzgxMTExNTYxMGI4ZjU3NjAwMDg0ODQwMTUyNWI1MDUwNTA1MDU2NWI2MDAwNjEwYmEwODI2MTBiNGM1NjViNjEwYmFhODE4NTYxMGI1NzU2NWI5MzUwNjEwYmJhODE4NTYwMjA4NjAxNjEwYjYyNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiZDI4Mjg0NjEwYjk1NTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwN2ZmZjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODIxNjkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjMjQ2MTBjMWY4MjYxMGJkZDU2NWI2MTBjMDk1NjViODI1MjUwNTA1NjViNjAwMDgxNjA2MDFiOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzQyODI2MTBjMmE1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjEwYzU0ODI2MTBjMzc1NjViOTA1MDkxOTA1MDU2NWI2MTBjNmM2MTBjNjc4MjYxMDkyYTU2NWI2MTBjNDk1NjViODI1MjUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBjOGQ2MTBjODg4MjYxMGE0MjU2NWI2MTBjNzI1NjViODI1MjUwNTA1NjViNjAwMDYxMGM5ZjgyODc2MTBjMTM1NjViNjAwMTgyMDE5MTUwNjEwY2FmODI4NjYxMGM1YjU2NWI2MDE0ODIwMTkxNTA2MTBjYmY4Mjg1NjEwYzdjNTY1YjYwMjA4MjAxOTE1MDYxMGNjZjgyODQ2MTBjN2M1NjViNjAyMDgyMDE5MTUwODE5MDUwOTU5NDUwNTA1MDUwNTA1NjViN2Y1NzY1NmM2YzJjMjA0OTIwNmU2NTc2NjU3MjIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTBkMTc2MDBlODM2MTBhYTU1NjViOTE1MDYxMGQyMjgyNjEwY2UxNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMGQ0NjgxNjEwZDBhNTY1YjkwNTA5MTkwNTA1NjViNjEwZDU2ODE2MTA5Njg1NjViODI1MjUwNTA1NjViNjAwMDYwODA4MjAxOTA1MDYxMGQ3MTYwMDA4MzAxODc2MTBiMjI1NjViNjEwZDdlNjAyMDgzMDE4NjYxMGIyMjU2NWI2MTBkOGI2MDQwODMwMTg1NjEwYjIyNTY1YjYxMGQ5ODYwNjA4MzAxODQ2MTBkNGQ1NjViOTU5NDUwNTA1MDUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTBkYjc4MTYxMGRhMTU2NWI4MTE0NjEwZGMyNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZGQ0ODE2MTBkYWU1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZGYwNTc2MTBkZWY2MTA5MDU1NjViNWI2MDAwNjEwZGZlODQ4Mjg1MDE2MTBkYzU1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMGUxYzYwMDA4MzAxODU2MTBiMjI1NjViNjEwZTI5NjAyMDgzMDE4NDYxMGIyMjU2NWI5MzkyNTA1MDUwNTZmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMTk3YzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDA0YzU3NjAwMDM1NjBlMDFjODA2MzBhMjg0Y2I2MTQ2MTAwNTE1NzgwNjM0ODRhOTVhOTE0NjEwMDZkNTc4MDYzZDk0MjU0MDMxNDYxMDA4OTU3ODA2M2VhNThjZTIxMTQ2MTAwYTU1NzViNjAwMDgwZmQ1YjYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwOTM5NTY1YjYxMDBjMTU2NWIwMDViNjEwMDg3NjAwNDgwMzYwMzgxMDE5MDYxMDA4MjkxOTA2MTA5Mzk1NjViNjEwMTI1NTY1YjAwNWI2MTAwYTM2MDA0ODAzNjAzODEwMTkwNjEwMDllOTE5MDYxMDk5NTU2NWI2MTAyMzY1NjViMDA1YjYxMDBiZjYwMDQ4MDM2MDM4MTAxOTA2MTAwYmE5MTkwNjEwOTk1NTY1YjYxMDI4ZTU2NWIwMDViNjAwMDgwNjAwMDYxMDBkMjg1NjAwMDg2NjEwMmU2NTY1YjkyNTA5MjUwOTI1MDYwMTY2MDAzMGI4MzE0NjEwMTFlNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMTE1OTA2MTBhMWY1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDUwNTA1MDU2NWI2MDAwNjA0MDUxNjEwMTMzOTA2MTA2OGU1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE0ZjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA4MDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjMwYTI4NGNiNjYwZTAxYjg0ODQ2MDQwNTE2MDI0MDE2MTAxODQ5MjkxOTA2MTBiOTg1NjViNjA0MDUxNjAyMDgxODMwMzAzODE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwIZnEwcT3q7+i3F+OSYEHfsMIfG0SRv/n8DlQdyVamTE9CR4MMZ/MxdI6ZG2RJ4PMGgwIuff/qwYQm5yXlQMiDwoJCP32/6sGEM8HEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj+9v+rBhDVBxICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjaFyKAIDUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxZWU5MTkwNjEwYzA0NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTg1NWFmNDkxNTA1MDNkODA2MDAwODExNDYxMDIyOTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDIyZTU2NWI2MDYwOTE1MDViNTA1MDUwNTA1MDUwNTY1YjYwMDA2MTAyNDIzMDgzNjEwNDVlNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDI4YTU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTYxMDI4MTkwNjEwYzY3NTY1YjYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1NjViNjAwMDYxMDI5YTMwODM2MTA1NzY1NjViOTA1MDYwMTY2MDAzMGI4MTE0NjEwMmUyNTc2MDQwNTE3ZjA4YzM3OWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MTUyNjAwNDAxNjEwMmQ5OTA2MTBjZDM1NjViNjA0MDUxODA5MTAzOTBmZDViNTA1MDU2NWI2MDAwODA2MDYwNjAwMDgwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzI3OGUwYjg4NjBlMDFiODk4OTg5NjA0MDUxNjAyNDAxNjEwMzI0OTM5MjkxOTA2MTBkMTY1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwMzhlOTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwM2NiNTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwM2QwNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA0MmM1NzYwMTU2MDAwODA2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTAzZjg1NzYxMDNmNzYxMDcyMzU2NWI1YjYwNDA1MTkwODA4MjUyODA2MDIwMDI2MDIwMDE4MjAxNjA0MDUyODAxNTYxMDQyNjU3ODE2MDIwMDE2MDIwODIwMjgwMzY4MzM3ODA4MjAxOTE1MDUwOTA1MDViNTA2MTA0NDE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA0NDA5MTkwNjEwZWIyNTY1YjViODI2MDAzMGI5MjUwODA5NTUwODE5NjUwODI5NzUwNTA1MDUwNTA1MDkzNTA5MzUwOTM5MDUwNTY1YjYwMDA4MDYwMDA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDk5Nzk0ZTg2MGUwMWI4Njg2NjA0MDUxNjAyNDAxNjEwNDk3OTI5MTkwNjEwZjIxNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE5MTY2MDIwODIwMTgwNTE3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODM4MTgzMTYxNzgzNTI1MDUwNTA1MDYwNDA1MTYxMDUwMTkxOTA2MTBjMDQ1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg2NWFmMTkxNTA1MDNkODA2MDAwODExNDYxMDUzZTU3NjA0MDUxOTE1MDYwMWYxOTYwM2YzZDAxMTY4MjAxNjA0MDUyM2Q4MjUyM2Q2MDAwNjAyMDg0MDEzZTYxMDU0MzU2NWI2MDYwOTE1MDViNTA5MTUwOTE1MDgxNjEwNTU0NTc2MDE1NjEwNTY5NTY1YjgwODA2MDIwMDE5MDUxODEwMTkwNjEwNTY4OTE5MDYxMGY0YTU2NWI1YjYwMDMwYjkyNTA1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjAwMDYxMDE2NzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NjM0OTE0NmJkZTYwZTAxYjg2ODY2MDQwNTE2MDI0MDE2MTA1YWY5MjkxOTA2MTBmMjE1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTA3YmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTkxNjYwMjA4MjAxODA1MTdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MzgxODMxNjE3ODM1MjUwNTA1MDUwNjA0MDUxNjEwNjE5OTE5MDYxMGMwNDU2NWI2MDAwNjA0MDUxODA4MzAzODE2MDAwODY1YWYxOTE1MDUwM2Q4MDYwMDA4MTE0NjEwNjU2NTc2MDQwNTE5MTUwNjAxZjE5NjAzZjNkMDExNjgyMDE2MDQwNTIzZDgyNTIzZDYwMDA2MDIwODQwMTNlNjEwNjViNTY1YjYwNjA5MTUwNWI1MDkxNTA5MTUwODE2MTA2NmM1NzYwMTU2MTA2ODE1NjViODA4MDYwMjAwMTkwNTE4MTAxOTA2MTA2ODA5MTkwNjEwZjRhNTY1YjViNjAwMzBiOTI1MDUwNTA5MjkxNTA1MDU2NWI2MTA5Y2Y4MDYxMGY3ODgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDZkYTgyNjEwNmFmNTY1YjkwNTA5MTkwNTA1NjViNjEwNmVhODE2MTA2Y2Y1NjViODExNDYxMDZmNTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDcwNzgxNjEwNmUxNTY1YjkyOTE1MDUwNTY1YjYwMDA4MGZkNWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViN2Y0ZTQ4N2I3MTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDUyNjA0MTYwMDQ1MjYwMjQ2MDAwZmQ1YjYxMDc1YjgyNjEwNzEyNTY1YjgxMDE4MTgxMTA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTcxNTYxMDc3YTU3NjEwNzc5NjEwNzIzNTY1YjViODA2MDQwNTI1MDUwNTA1NjViNjAwMDYxMDc4ZDYxMDY5YjU2NWI5MDUwNjEwNzk5ODI4MjYxMDc1MjU2NWI5MTkwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdiOTU3NjEwN2I4NjEwNzIzNTY1YjViNjAyMDgyMDI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNTYxMDdlZjU3NjEwN2VlNjEwNzIzNTY1YjViNjEwN2Y4ODI2MTA3MTI1NjViOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI4MjgxODMzNzYwMDA4MzgzMDE1MjUwNTA1MDU2NWI2MDAwNjEwODI3NjEwODIyODQ2MTA3ZDQ1NjViNjEwNzgzNTY1YjkwNTA4MjgxNTI2MDIwODEwMTg0ODQ4NDAxMTExNTYxMDg0MzU3NjEwODQyNjEwN2NmNTY1YjViNjEwODRlODQ4Mjg1NjEwODA1NTY1YjUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwODZiNTc2MTA4NmE2MTA3MGQ1NjViNWI4MTM1NjEwODdiODQ4MjYwMjA4NjAxNjEwODE0NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA4OTc2MTA4OTI4NDYxMDc5ZTU2NWI2MTA3ODM1NjViOTA1MDgwODM4MjUyNjAyMDgyMDE5MDUwNjAyMDg0MDI4MzAxODU4MTExMTU2MTA4YmE1NzYxMDhiOTYxMDdjYTU2NWI1YjgzNWI4MTgxMTAxNTYxMDkwMTU3ODAzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDhkZjU3NjEwOGRlNjEwNzBkNTY1YjViODA4NjAxNjEwOGVjODk4MjYxMDg1NjU2NWI4NTUyNjAyMDg1MDE5NDUwNTA1MDYwMjA4MTAxOTA1MDYxMDhiYzU2NWI1MDUwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MTA5MjA1NzYxMDkxZjYxMDcwZDU2NWI1YjgxMzU2MTA5MzA4NDgyNjAyMDg2MDE2MTA4ODQ1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MTA5NTA1NzYxMDk0ZjYxMDZhNTU2NWI1YjYwMDA2MTA5NWU4NTgyODYwMTYxMDZmODU2NWI5MjUwNTA2MDIwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTdmNTc2MTA5N2U2MTA2YWE1NjViNWI2MTA5OGI4NTgyODYwMTYxMDkwYjU2NWI5MTUwNTA=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIweJt2s94U5mmsdZUmzQkF9zSbDs3wWl1vI0+fH3ukuIRPTOGCMPfSTcKHKsIf8a/YGgwIuvf/qwYQu+DbnAEiDwoJCP72/6sGENUHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj+9v+rBhDbBxICGAISAhgDGPfR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjaFyKAIDkyNTA5MjkwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwOWFiNTc2MTA5YWE2MTA2YTU1NjViNWI2MDAwNjEwOWI5ODQ4Mjg1MDE2MTA2Zjg1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjdmNDM2MTZlMjc3NDIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA4MjAxNTI1MDU2NWI2MDAwNjEwYTA5NjAwNjgzNjEwOWMyNTY1YjkxNTA2MTBhMTQ4MjYxMDlkMzU2NWI2MDIwODIwMTkwNTA5MTkwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTBhMzg4MTYxMDlmYzU2NWI5MDUwOTE5MDUwNTY1YjYxMGE0ODgxNjEwNmNmNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwYWI0NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwYTk5NTY1YjgzODExMTE1NjEwYWMzNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTBhZDQ4MjYxMGE3YTU2NWI2MTBhZGU4MTg1NjEwYTg1NTY1YjkzNTA2MTBhZWU4MTg1NjAyMDg2MDE2MTBhOTY1NjViNjEwYWY3ODE2MTA3MTI1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBiMGU4MzgzNjEwYWM5NTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTBiMmU4MjYxMGE0ZTU2NWI2MTBiMzg4MTg1NjEwYTU5NTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMGI0YTg1NjEwYTZhNTY1YjgwNjAwMDViODU4MTEwMTU2MTBiODY1Nzg0ODQwMzg5NTI4MTUxNjEwYjY3ODU4MjYxMGIwMjU2NWI5NDUwNjEwYjcyODM2MTBiMTY1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwYjRlNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTBiYWQ2MDAwODMwMTg1NjEwYTNmNTY1YjgxODEwMzYwMjA4MzAxNTI2MTBiYmY4MTg0NjEwYjIzNTY1YjkwNTA5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwYmRlODI2MTBhN2E1NjViNjEwYmU4ODE4NTYxMGJjODU2NWI5MzUwNjEwYmY4ODE4NTYwMjA4NjAxNjEwYTk2NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTBjMTA4Mjg0NjEwYmQzNTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI3ZjUzNmYyMDc1NmU2NjYxNjk3MjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGM1MTYwMDk4MzYxMDljMjU2NWI5MTUwNjEwYzVjODI2MTBjMWI1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwYzgwODE2MTBjNDQ1NjViOTA1MDkxOTA1MDU2NWI3ZjQ5NzQyNzczMjA3NTZlNjg2NTYxNzI2NDIwNmY2NjJlMmUyZTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjAwMDYxMGNiZDYwMTI4MzYxMDljMjU2NWI5MTUwNjEwY2M4ODI2MTBjODc1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA4MTgxMDM2MDAwODMwMTUyNjEwY2VjODE2MTBjYjA1NjViOTA1MDkxOTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjEwZDEwODE2MTBjZjM1NjViODI1MjUwNTA1NjViNjAwMDYwNjA4MjAxOTA1MDYxMGQyYjYwMDA4MzAxODY2MTBhM2Y1NjViNjEwZDM4NjAyMDgzMDE4NTYxMGQwNzU2NWI4MTgxMDM2MDQwODMwMTUyNjEwZDRhODE4NDYxMGIyMzU2NWI5MDUwOTQ5MzUwNTA1MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjEwZDZhODE2MTBkNTQ1NjViODExNDYxMGQ3NTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMGQ4NzgxNjEwZDYxNTY1YjkyOTE1MDUwNTY1YjYxMGQ5NjgxNjEwY2YzNTY1YjgxMTQ2MTBkYTE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTBkYjM4MTYxMGQ4ZDU2NWI5MjkxNTA1MDU2NWI2MDAwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE1NjEwZGQ0NTc2MTBkZDM2MTA3MjM1NjViNWI2MDIwODIwMjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTBkZjg4MTYxMGRlNTU2NWI4MTE0NjEwZTAzNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwZTE1ODE2MTBkZWY1NjViOTI5MTUwNTA1NjViNjAwMDYxMGUyZTYxMGUyOTg0NjEwZGI5NTY1YjYxMDc4MzU2NWI5MDUwODA4MzgyNTI2MDIwODIwMTkwNTA2MDIwODQwMjgzMDE4NTgxMTExNTYxMGU1MTU3NjEwZTUwNjEwN2NhNTY1YjViODM1YjgxODExMDE1NjEwZTdhNTc4MDYxMGU2Njg4ODI2MTBlMDY1NjViODQ1MjYwMjA4NDAxOTM1MDUwNjAyMDgxMDE5MDUwNjEwZTUzNTY1YjUwNTA1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMGU5OTU3NjEwZTk4NjEwNzBkNTY1YjViODE1MTYxMGVhOTg0ODI2MDIwODYwMTYxMGUxYjU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDAwNjA2MDg0ODYwMzEyMTU2MTBlY2I1NzYxMGVjYTYxMDZhNTU2NWI1YjYwMDA2MTBlZDk4NjgyODcwMTYxMGQ3ODU2NWI5MzUwNTA2MDIwNjEwZWVhODY4Mjg3MDE2MTBkYTQ1NjViOTI1MDUwNjA0MDg0MDE1MTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMGYwYjU3NjEwZjBhNjEwNmFhNTY1YjViNjEwZjE3ODY4Mjg3MDE2MTBlODQ1NjViOTE1MDUwOTI1MDkyNTA5MjU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwZjM2NjAwMDgzMDE4NTYxMGEzZjU2NWI2MTBmNDM2MDIwODMwMTg0NjEwYTNmNTY1YjkzOTI1MDUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwZjYwNTc2MTBmNWY2MTA2YTU1NjViNWI2MDAwNjEwZjZlODQ4Mjg1MDE2MTBkNzg1NjViOTE1MDUwOTI5MTUwNTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjEwOWFmODA2MTAwMjA2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDJiNTc2MDAwMzU2MGUwMWM4MDYzMGEyODRjYjYxNDYxMDAzMDU3NWI2MDAwODBmZDViNjEwMDRhNjAwNDgwMzYwMzgxMDE5MDYxMDA0NTkxOTA2MTA0YzY1NjViNjEwMDRjNTY1YjAwNWI2MDAwODA2MDAwNjEwMDVkODU2MDAwODY2MTAwYjA1NjViOTI1MDkyNTA5MjUwNjAxNjYwMDMwYjgzMTQ2MTAwYTk1NzYwNDA1MTdmMDhjMzc5YTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgxNTI2MDA0MDE2MTAwYTA5MDYxMDU3ZjU2NWI2MDQwNTE4MDkxMDM5MGZkNWI1MDUwNTA1MDUwNTY1YjYwMDA4MDYwNjA2MDAwODA2MTAxNjc3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMjc4ZTBiODg2MGUwMWI4OTg5ODk2MDQwNTE2MDI0MDE2MTAwZWU5MzkyOTE5MDYxMDcxYjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxNTg5MTkwNjEwNzk1NTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxOTU1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxOWE1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDFmNjU3NjAxNTYwMDA4MDY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDFjMjU3NjEwMWMxNjEwMmIwNTY1YjViNjA0MDUxOTA4MDgyNTI4MDYwMjAwMjYwMjAwMTgyMDE2MDQwNTI4MDE1NjEwMWYwNTc4MTYwMjAwMTYwMjA4MjAyODAzNjgzMzc4MDgyMDE5MTUwNTA5MDUwNWI1MDYxMDIwYjU2NWI=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwGVMaLJHhW5VWbKE04GkoKoCUpl+Fv0Ix2AlviFDlG5LeCZzxdo0YoeoN0bYqtTtMGgwIuvf/qwYQ86G2ngMiDwoJCP72/6sGENsHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj/9v+rBhDhBxICGAISAhgDGPbR6j4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBiCASAxjaFyKAIDgwODA2MDIwMDE5MDUxODEwMTkwNjEwMjBhOTE5MDYxMDkwYTU2NWI1YjgyNjAwMzBiOTI1MDgwOTU1MDgxOTY1MDgyOTc1MDUwNTA1MDUwNTA5MzUwOTM1MDkzOTA1MDU2NWI2MDAwNjA0MDUxOTA1MDkwNTY1YjYwMDA4MGZkNWI2MDAwODBmZDViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAyNjc4MjYxMDIzYzU2NWI5MDUwOTE5MDUwNTY1YjYxMDI3NzgxNjEwMjVjNTY1YjgxMTQ2MTAyODI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyOTQ4MTYxMDI2ZTU2NWI5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTAyZTg4MjYxMDI5ZjU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTAzMDc1NzYxMDMwNjYxMDJiMDU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTAzMWE2MTAyMjg1NjViOTA1MDYxMDMyNjgyODI2MTAyZGY1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzNDY1NzYxMDM0NTYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTAzN2M1NzYxMDM3YjYxMDJiMDU2NWI1YjYxMDM4NTgyNjEwMjlmNTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDNiNDYxMDNhZjg0NjEwMzYxNTY1YjYxMDMxMDU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTAzZDA1NzYxMDNjZjYxMDM1YzU2NWI1YjYxMDNkYjg0ODI4NTYxMDM5MjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDNmODU3NjEwM2Y3NjEwMjlhNTY1YjViODEzNTYxMDQwODg0ODI2MDIwODYwMTYxMDNhMTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNDI0NjEwNDFmODQ2MTAzMmI1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwNDQ3NTc2MTA0NDY2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA0OGU1NzgwMzU2N2ZmZmZmZmZmZmZmZmZmZmY4MTExMTU2MTA0NmM1NzYxMDQ2YjYxMDI5YTU2NWI1YjgwODYwMTYxMDQ3OTg5ODI2MTAzZTM1NjViODU1MjYwMjA4NTAxOTQ1MDUwNTA2MDIwODEwMTkwNTA2MTA0NDk1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwNGFkNTc2MTA0YWM2MTAyOWE1NjViNWI4MTM1NjEwNGJkODQ4MjYwMjA4NjAxNjEwNDExNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwNGRkNTc2MTA0ZGM2MTAyMzI1NjViNWI2MDAwNjEwNGViODU4Mjg2MDE2MTAyODU1NjViOTI1MDUwNjAyMDgzMDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDUwYzU3NjEwNTBiNjEwMjM3NTY1YjViNjEwNTE4ODU4Mjg2MDE2MTA0OTg1NjViOTE1MDUwOTI1MDkyOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViN2Y0MzYxNmUyNzc0MjA2NTc2NjU2ZTIxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDgyMDE1MjUwNTY1YjYwMDA2MTA1Njk2MDBiODM2MTA1MjI1NjViOTE1MDYxMDU3NDgyNjEwNTMzNTY1YjYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYxMDU5ODgxNjEwNTVjNTY1YjkwNTA5MTkwNTA1NjViNjEwNWE4ODE2MTAyNWM1NjViODI1MjUwNTA1NjViNjAwMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYxMDVjYjgxNjEwNWFlNTY1YjgyNTI1MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDgxOTA1MDYwMjA4MjAxOTA1MDkxOTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwNjM3NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwNjFjNTY1YjgzODExMTE1NjEwNjQ2NTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA2MTA2NTc4MjYxMDVmZDU2NWI2MTA2NjE4MTg1NjEwNjA4NTY1YjkzNTA2MTA2NzE4MTg1NjAyMDg2MDE2MTA2MTk1NjViNjEwNjdhODE2MTAyOWY1NjViODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA2OTE4MzgzNjEwNjRjNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YjE4MjYxMDVkMTU2NWI2MTA2YmI4MTg1NjEwNWRjNTY1YjkzNTA4MzYwMjA4MjAyODUwMTYxMDZjZDg1NjEwNWVkNTY1YjgwNjAwMDViODU4MTEwMTU2MTA3MDk1Nzg0ODQwMzg5NTI4MTUxNjEwNmVhODU4MjYxMDY4NTU2NWI5NDUwNjEwNmY1ODM2MTA2OTk1NjViOTI1MDYwMjA4YTAxOTk1MDUwNjAwMTgxMDE5MDUwNjEwNmQxNTY1YjUwODI5NzUwODc5NTUwNTA1MDUwNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDYwODIwMTkwNTA2MTA3MzA2MDAwODMwMTg2NjEwNTlmNTY1YjYxMDczZDYwMjA4MzAxODU2MTA1YzI1NjViODE4MTAzNjA0MDgzMDE1MjYxMDc0ZjgxODQ2MTA2YTY1NjViOTA1MDk0OTM1MDUwNTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDYxMDc2ZjgyNjEwNWZkNTY1YjYxMDc3OTgxODU2MTA3NTk1NjViOTM1MDYxMDc4OTgxODU2MDIwODYwMTYxMDYxOTU2NWI4MDg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjEwN2ExODI4NDYxMDc2NDU2NWI5MTUwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgxNjAwMzBiOTA1MDkxOTA1MDU2NWI2MTA3YzI4MTYxMDdhYzU2NWI4MTE0NjEwN2NkNTc2MDAwODBmZDViNTA1NjViNjAwMDgxNTE5MDUwNjEwN2RmODE2MTA3Yjk1NjViOTI5MTUwNTA1NjViNjEwN2VlODE2MTA1YWU1NjViODExNDYxMDdmOTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDgwYjgxNjEwN2U1NTY1YjkyOTE1MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTA4MmM1NzYxMDgyYjYxMDJiMDU2NWI1YjYwMjA4MjAyOTA1MDYwMjA4MTAxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDg1MDgxNjEwODNkNTY1YjgxMTQ2MTA4NWI1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MTA4NmQ4MTYxMDg0NzU2NWI5MjkxNTA1MDU2NWI2MDAwNjEwODg2NjEwODgxODQ2MTA4MTE1NjViNjEwMzEwNTY1YjkwNTA4MDgzODI1MjYwMjA4MjAxOTA1MDYwMjA4NDAyODMwMTg1ODExMTE1NjEwOGE5NTc2MTA4YTg2MTAzNTc1NjViNWI4MzViODE4MTEwMTU2MTA4ZDI1NzgwNjEwOGJlODg4MjYxMDg1ZTU2NWI4NDUyNjAyMDg0MDE5MzUwNTA2MDIwODEwMTkwNTA2MTA4YWI1NjViNTA1MDUwOTM5MjUwNTA1MDU2NWI2MDAwODI2MDFmODMwMTEyNjEwOGYxNTc2MTA4ZjA2MTAyOWE1NjViNWI4MTUxNjEwOTAxODQ4MjYwMjA4NjAxNjEwODczNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwMDA2MDYwODQ4NjAzMTIxNTYxMDkyMzU3NjEwOTIyNjEwMjMyNTY1YjViNjAwMDYxMDkzMTg2ODI4NzAxNjEwN2QwNTY1YjkzNTA1MDYwMjA2MTA5NDI4NjgyODcwMTYxMDdmYzU2NWI5MjUwNTA2MDQwODQwMTUxNjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjEwOTYzNTc2MTA5NjI2MTAyMzc1NjViNWI2MTA5NmY4NjgyODcwMTYxMDhkYzU2NWI5MTUwNTA5MjUwOTI1MDkyNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwYTNkZmY5MDJkODg2Y2ZmNDUzZTY2MWQyODAzODIwYWUyNmFiNGY0NWE0OGZhMzE5NTBiZDAzMWYwOGQzZWJhYzY0NzM2ZjZjNjM0MzAwMDgwYzAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDU1MDQ1OTE4MjMwMjQ4NTQ0MjA2NThkNDk0NThiNGFlZDg0ZWYyYWZhZTI4OTg5ZTAzMTU2ODdmNTk5MTRiM2Y2NDczNmY2YzYzNDMwMDA4MGMwMDMzYTI2NDY5NzA2NjczNTgyMjEyMjA5OTA0NDBlYTM0MmY5YzgzMTE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwMAC8e/1f2LAISQ55gFvW40LOG1AXI+7Fhs2DWqVUU7tdBOUX0xWjbd4dHfFQ0MzjGgwIu/f/qwYQu7qgpgEiDwoJCP/2/6sGEOEHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj/9v+rBhDnBxICGAISAhgDGImGlS0iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBSxIDGNoXIkRhYTkzOTZkOTY0YmMyM2RjYjU2MjY2MjVmNTBjMGYyZDZmM2U3ODA4NDMzY2U2NjQ3MzZmNmM2MzQzMDAwODBjMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwpdNhuInaOV3csXrT7NUkN564UZWkzsfCF3cxOk20mlzgdBSN6yl+uAcXWu1GDZwlGgwIu/f/qwYQw4/QpwMiDwoJCP/2/6sGEOcHEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiA9/+rBhDpBxICGAISAhgDGJ/Xt6UBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CIQoDGNkXIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGNsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAsCFo3ktJVT700AsJkld4+ejFNR7z+P9JQRYGi9fHhlFsUimzqMZOw2UTevNAe4OoaDAi89/+rBhC7neKvASIPCgkIgPf/qwYQ6QcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQtcnCgMY2xcSoiVggGBAUjSAFWEAEFdgAID9W1BgBDYQYQEAV2AANWDgHIBjqG41dhFhAJdXgGPk3CqkEWEAZleAY+TcKqQUYQJVV4Bj6dxjdRRhAnFXgGP0n0DbFGECjVeAY/eIiuwUYQKpV2EBAFZbgGOobjV2FGEB5VeAY8tg8b8UYQIBV4Bj2fxLYRRhAh1XgGPh8hxnFGECOVdhAQBWW4BjHynS3BFhANNXgGMfKdLcFGEBdVeAYzZ2BcoUYQGRV4Bja0K/LxRhAa1XgGOY3KW+FGEByVdhAQBWW4BjAZhIkhRhAQVXgGMDnW8ZFGEBIVeAYwmPI2YUYQE9V4BjFdrL6hRhAVlXW2AAgP1bYQEfYASANgOBAZBhARqRkGEK11ZbYQLFVlsAW2EBO2AEgDYDgQGQYQE2kZBhC59WW2EDPVZbAFthAVdgBIA2A4EBkGEBUpGQYQw5VlthA7hWWwBbYQFzYASANgOBAZBhAW6RkGEMeVZbYQQ3VlsAW2EBj2AEgDYDgQGQYQGKkZBhDDlWW2EErFZbAFthAatgBIA2A4EBkGEBppGQYQ0YVlthBStWWwBbYQHHYASANgOBAZBhAcKRkGENa1ZbYQWdVlsAW2EB42AEgDYDgQGQYQHekZBhDHlWW2EGH1ZbAFthAf9gBIA2A4EBkGEB+pGQYQrXVlthBo9WWwBbYQIbYASANgOBAZBhAhaRkGEMOVZbYQcHVlsAW2ECN2AEgDYDgQGQYQIykZBhDHlWW2EHhlZbAFthAlNgBIA2A4EBkGECTpGQYQ1rVlthB/tWWwBbYQJvYASANgOBAZBhAmqRkGEK11ZbYQhtVlsAW2ECi2AEgDYDgQGQYQKGkZBhDDlWW2EI4FZbAFthAqdgBIA2A4EBkGECopGQYQ2+VlthCWRWWwBbYQLDYASANgOBAZBhAr6RkGEOEVZbYQnmVlsAW4Bz//////////////////////////8WYwb93gNgQFGBY/////8WYOAbgVJgBAFgAGBAUYCDA4GGWvoVgBVhAxBXPWAAgD49YAD9W1BQUFBgQFE9YACCPj1gHxlgH4IBFoIBgGBAUlCBAZBhAzmRkGEPrVZbUFBWW4Vz//////////////////////////8WY7iNT96GhoaGhmBAUYZj/////xZg4BuBUmAEAWEDfpWUk5KRkGEQYVZbYABgQFGAgwOBYACHgDsVgBVhA5hXYACA/VtQWvEVgBVhA6xXPWAAgD49YAD9W1BQUFBQUFBQUFBWW4Fz//////////////////////////8WYwgYEvyCYEBRgmP/////FmDgG4FSYAQBYQPxkZBhEK9WW2AgYEBRgIMDgYZa+hWAFWEEDlc9YACAPj1gAP1bUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhBDKRkGEQ31ZbUFBQVluDc///////////////////////////FmMjuHLdhISEYEBRhGP/////FmDgG4FSYAQBYQR0k5KRkGERDFZbYABgQFGAgwOBYACHgDsVgBVhBI5XYACA/VtQWvEVgBVhBKJXPWAAgD49YAD9W1BQUFBQUFBQVluBc///////////////////////////FmNjUiEegmBAUYJj/////xZg4BuBUmAEAWEE5ZGQYRCvVltgIGBAUYCDA4GGWvoVgBVhBQJXPWAAgD49YAD9W1BQUFBgQFE9YB8ZYB+CARaCAYBgQFJQgQGQYQUmkZBhEN9WW1BQUFZbgnP//////////////////////////xZjoiy0ZYODYEBRg2P/////FmDgG4FSYAQBYQVmkpGQYRFSVltgAGBAUYCDA4FgAIeAOxWAFWEFgFdgAID9W1Ba8RWAFWEFlFc9YACAPj1gAP1bUFBQUFBQUFZbgnP//////////////////////////xZjL3RcWYODYEBRg2P/////FmDgG4FSYAQBYQXYkpGQYRF7VltgIGBAUYCDA4GGWvoVgBVhBfVXPWAAgD49YAD9W1BQUFBgQFE9YB8ZYB+CARaCAYBgQFJQgQGQYQYZkZBhEblWW1BQUFBWW4Nz//////////////////////////8WYyO4ct2EhIRgQFGEY/////8WYOAbgVJgBAFhBlyTkpGQYREMVltgAGBAUYCDA4FgAIeAOxWAFWEGdldgAID9W1Ba8RWAFWEGilc9YACAPj1gAP1bYACA/VuAc///////////////////////////FmOV2JtBYEBRgWP/////FmDgG4FSYAQBYABgQFGAgwOBhlr6FYAVYQbaVz1gAIA+PWAA/VtQUFBQYEBRPWAAgj49YB8ZYB+CARaCAYBgQFJQgQGQYQcDkZBhD61WW1BQVluBc///////////////////////////FmNPbMzngmBAUYJj/////xZg4BuBUmAEAWEHQJGQYRCvVltgIGBAUYCDA4GGWvoVgBVhB11XPWAAgD49YAD9W1BQUFBgQFE9YB8ZYB+CARaCAYBgQFJQgQGQYQeBkZBhEblWW1BQUFZbg3P//////////////////////////xZjQoQuDoSEhGBAUYRj/////xZg4BuBUmAEAWEHw5OSkZBhEQxWW2AAYEBRgIMDgWAAh4A7FYAVYQfdV2AAgP1bUFrxFYAVYQfxVz1gAIA+PWAA/VtQUFBQUFBQUFZbgnP//////////////////////////xZjCV6ns4ODYEBRg2P/////FmDgG4FSYAQBYQg2kpGQYRF7VltgAGBAUYCDA4FgAIeAOxWAFWEIUFdgAID9W1Ba8RWAFWEIZFc9YACAPj1gAP1bUFBQUFBQUFZbgHP//////////////////////////xZjGBYN3WBAUYFj/////xZg4BuBUmAEAWAgYEBRgIMDgYZa+hWAFWEIuFc9YACAPj1gAP1bUFBQUGBAUT1gHxlgH4IBFoIBgGBAUlCBAZBhCNyRkGERuVZbUFBWW4Fz//////////////////////////8WY8h7Vt2CYEBRgmP/////FmDgG4FSYAQBYQkZkZBhEK9WW2AAYEBRgIMDgYZa+hWAFWEJNlc9YACAPj1gAP1bUFBQUGBAUT1gAII+PWAfGWAfggEWggGAYEBSUIEBkGEJX5GQYQ+tVltQUFBWW4Jz//////////////////////////8WY+mF6cWDg2BAUYNj/////xZg4BuBUmAEAWEJn5KRkGER5lZbYCBgQFGAgwOBhlr6FYAVYQm8Vz1gAIA+PWAA/VtQUFBQYEBRPWAfGWAfggEWggGAYEBSUIEBkGEJ4JGQYRIkVltQUFBQVluBc///////////////////////////FmNwoIIxgmBAUYJj/////xZg4BuBUmAEAWEKH5GQYRJRVltgIGBAUYCDA4GGWvoVgBVhCjxXPWAAgD49YAD9W1BQUFBgQFE9YB8ZYB+CARaCAYBgQFJQgQGQYQpgkZBhEblWW1BQUFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQqkgmEKeVZbkFCRkFBWW2EKtIFhCplWW4EUYQq/V2AAgP1bUFZbYACBNZBQYQrRgWEKq1ZbkpFQUFZbYABgIIKEAxIVYQrtV2EK7GEKb1ZbW2AAYQr7hIKFAWEKwlZbkVBQkpFQUFZbYACBkFCRkFBWW2ELF4FhCwRWW4EUYQsiV2AAgP1bUFZbYACBNZBQYQs0gWELDlZbkpFQUFZbYACA/VtgAID9W2AAgP1bYACAg2AfhAESYQtfV2ELXmELOlZbW4I1kFBn//////////+BERVhC3xXYQt7YQs/VltbYCCDAZFQg2ABggKDAREVYQuYV2ELl2ELRFZbW5JQkpBQVltgAIBgAIBgAIBgoIeJAxIVYQu8V2ELu2EKb1ZbW2AAYQvKiYKKAWEKwlZbllBQYCBhC9uJgooBYQrCVluVUFBgQGEL7ImCigFhCsJWW5RQUGBgYQv9iYKKAWELJVZbk1BQYICHATVn//////////+BERVhDB5XYQwdYQp0VltbYQwqiYKKAWELSVZbklCSUFCSlVCSlVCSlVZbYACAYECDhQMSFWEMUFdhDE9hCm9WW1tgAGEMXoWChgFhCsJWW5JQUGAgYQxvhYKGAWELJVZbkVBQklCSkFBWW2AAgGAAgGCAhYcDEhVhDJNXYQySYQpvVltbYABhDKGHgogBYQrCVluUUFBgIGEMsoeCiAFhCsJWW5NQUGBAYQzDh4KIAWEKwlZbklBQYGBhDNSHgogBYQslVluRUFCSlZGUUJJQVltgAIEVFZBQkZBQVlthDPWBYQzgVluBFGENAFdgAID9W1BWW2AAgTWQUGENEoFhDOxWW5KRUFBWW2AAgGAAYGCEhgMSFWENMVdhDTBhCm9WW1tgAGENP4aChwFhCsJWW5NQUGAgYQ1QhoKHAWEKwlZbklBQYEBhDWGGgocBYQ0DVluRUFCSUJJQklZbYACAYABgYISGAxIVYQ2EV2ENg2EKb1ZbW2AAYQ2ShoKHAWEKwlZbk1BQYCBhDaOGgocBYQrCVluSUFBgQGENtIaChwFhCyVWW5FQUJJQklCSVltgAIBgAGBghIYDEhVhDddXYQ3WYQpvVltbYABhDeWGgocBYQrCVluTUFBgIGEN9oaChwFhCsJWW5JQUGBAYQ4HhoKHAWEKwlZbkVBQklCSUJJWW2AAgGBAg4UDEhVhDihXYQ4nYQpvVltbYABhDjaFgoYBYQrCVluSUFBgIGEOR4WChgFhCsJWW5FQUJJQkpBQVltgAID9W2AAYB8ZYB+DARaQUJGQUFZbf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VthDp+CYQ5WVluBAYGBEGf//////////4IRFxVhDr5XYQ69YQ5nVltbgGBAUlBQUFZbYABhDtFhCmVWW5BQYQ7dgoJhDpZWW5GQUFZbYABn//////////+CERVhDv1XYQ78YQ5nVltbYQ8GgmEOVlZbkFBgIIEBkFCRkFBWW2AAW4OBEBVhDzFXgIIBUYGEAVJgIIEBkFBhDxZWW2AAhIQBUlBQUFBWW2AAYQ9QYQ9LhGEO4lZbYQ7HVluQUIKBUmAggQGEhIQBERVhD2xXYQ9rYQ5RVltbYQ93hIKFYQ8TVltQk5JQUFBWW2AAgmAfgwESYQ+UV2EPk2ELOlZbW4FRYQ+khIJgIIYBYQ89VluRUFCSkVBQVltgAGAggoQDEhVhD8NXYQ/CYQpvVltbYACCAVFn//////////+BERVhD+FXYQ/gYQp0VltbYQ/thIKFAWEPf1ZbkVBQkpFQUFZbYQ//gWEKmVZbglJQUFZbYRAOgWELBFZbglJQUFZbYACCglJgIIIBkFCSkVBQVluCgYM3YACDgwFSUFBQVltgAGEQQIOFYRAUVluTUGEQTYOFhGEQJVZbYRBWg2EOVlZbhAGQUJOSUFBQVltgAGCAggGQUGEQdmAAgwGIYQ/2VlthEINgIIMBh2EP9lZbYRCQYECDAYZhEAVWW4GBA2BggwFSYRCjgYSGYRA0VluQUJaVUFBQUFBQVltgAGAgggGQUGEQxGAAgwGEYRAFVluSkVBQVltgAIFRkFBhENmBYQqrVluSkVBQVltgAGAggoQDEhVhEPVXYRD0YQpvVltbYABhEQOEgoUBYRDKVluRUFCSkVBQVltgAGBgggGQUGERIWAAgwGGYQ/2VlthES5gIIMBhWEP9lZbYRE7YECDAYRhEAVWW5STUFBQUFZbYRFMgWEM4FZbglJQUFZbYABgQIIBkFBhEWdgAIMBhWEP9lZbYRF0YCCDAYRhEUNWW5OSUFBQVltgAGBAggGQUGERkGAAgwGFYQ/2VlthEZ1gIIMBhGEQBVZbk5JQUFBWW2AAgVGQUGERs4FhCw5WW5KRUFBWW2AAYCCChAMSFWERz1dhEc5hCm9WW1tgAGER3YSChQFhEaRWW5FQUJKRUFBWW2AAYECCAZBQYRH7YACDAYVhD/ZWW2ESCGAggwGEYQ/2VluTklBQUFZbYACBUZBQYRIegWEM7FZbkpFQUFZbYABgIIKEAxIVYRI6V2ESOWEKb1ZbW2AAYRJIhIKFAWESD1ZbkVBQkpFQUFZbYABgIIIBkFBhEmZgAIMBhGEP9lZbkpFQUFb+omRpcGZzWCISIPf2J2x9dQ3I1yo5jRFsDm44OTGOhy6cPnn+knBAjgASZHNvbGNDAAgQADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGNsXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAvbcgcKAxjbFxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQiA9/+rBhDrBxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGNoXGiISIAYit808jbHcfGX+EtW8c/2yBpk3G01PKyAjEs0nXFr5IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGNwXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCNHrHYh4R98aDPsz8rykqHl30McxrmYnz5SK9eeBCdkCBeWzYc825P8Do9J12I4nwaDAi89/+rBhDjva2xAyIPCgkIgPf/qwYQ6wcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCC+JAHQrdSCgMY3BcSglBggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBXV2AANWDgHIBjQl2/lhRhAFxXgGNn1zHTFGEAjFeAY3B8UBkUYQC8V4Bjq/e/2BRhANhXgGPKlyFhFGEA9FdbYACA/VthAHZgBIA2A4EBkGEAcZGQYQmhVlthARBWW2BAUWEAg5GQYQn6VltgQFGAkQOQ81thAKZgBIA2A4EBkGEAoZGQYQmhVlthAUZWW2BAUWEAs5GQYQn6VltgQFGAkQOQ81thANZgBIA2A4EBkGEA0ZGQYQoVVlthAXxWWwBbYQDyYASANgOBAZBhAO2RkGEKeFZbYQJfVlsAW2EBDmAEgDYDgQGQYQEJkZBhChVWW2EDqVZbAFtgAGEBPoMwYACAVJBhAQAKkARz//////////////////////////8WhWEEjFZbkFCSkVBQVltgAGEBdIMwYACAVJBhAQAKkARz//////////////////////////8WhWEFqlZbkFCSkVBQVltgAGEBiDCDYQbIVluQUGAWYAMLgRRhAdBXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhAceQYQsCVltgQFGAkQOQ/VtgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WY9lCVAODYEBRgmP/////FmDgG4FSYAQBYQIpkZBhCzFWW2AAYEBRgIMDgWAAh4A7FYAVYQJDV2AAgP1bUFrxFYAVYQJXVz1gAIA+PWAA/VtQUFBQUFBWW2AAYP9g+Bswg2BAUYBgIAFhAniQYQj4VltgIIIBgQOCUmAfGWAfggEWYEBSUGBAUWAgAWECnJGQYQvGVltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgYEBRYCABYQLFlJOSkZBhDJNWW2BAUWAggYMDA4FSkGBAUoBRkGAgASBgAByQUIFgQFFhAu2QYQj4VluBkGBAUYCRA5BgAPWQUIAVgBVhAw1XPWAAgD49YAD9W1BgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAc///////////////////////////FmAAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xYUYQOlV2AAgP1bUFBWW2AAYQO1MINhB+BWW5BQYBZgAwuBFGED/VdgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWED9JBhDS1WW2BAUYCRA5D9W2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xZj6ljOIYNgQFGCY/////8WYOAbgVJgBAFhBFaRkGELMVZbYABgQFGAgwOBYACHgDsVgBVhBHBXYACA/VtQWvEVgBVhBIRXPWAAgD49YAD9W1BQUFBQUFZbYACAYABhAWdz//////////////////////////8WY+yjaRdg4BuIiIiIYEBRYCQBYQTJlJOSkZBhDVxWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFM5GQYQvGVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEFcFdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFdVZbYGCRUFtQkVCRUIFhBYZXYBVhBZtWW4CAYCABkFGBAZBhBZqRkGEN2lZbW2ADC5JQUFCUk1BQUFBWW2AAgGAAYQFnc///////////////////////////FmNc/JARYOAbiIiIiGBAUWAkAWEF55STkpGQYQ1cVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhBlGRkGELxlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhBo5XYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBpNWW2BgkVBbUJFQkVCBYQakV2AVYQa5VluAgGAgAZBRgQGQYQa4kZBhDdpWW1tgAwuSUFBQlJNQUFBQVltgAIBgAGEBZ3P//////////////////////////xZjCZeU6GDgG4aGYEBRYCQBYQcBkpGQYQ4HVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhB2uRkGELxlZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhB6hXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hB61WW2BgkVBbUJFQkVCBYQe+V2AVYQfTVluAgGAgAZBRgQGQYQfSkZBhDdpWW1tgAwuSUFBQkpFQUFZbYACAYABhAWdz//////////////////////////8WY0kUa95g4BuGhmBAUWAkAWEIGZKRkGEOB1ZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQiDkZBhC8ZWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQjAV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQjFVltgYJFQW1CRUJFQgWEI1ldgFWEI61ZbgIBgIAGQUYEBkGEI6pGQYQ3aVltbYAMLklBQUJKRUFBWW2EZnIBhDjGDOQGQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhCTWCYQkKVluQUJGQUFZbYQlFgWEJKlZbgRRhCVBXYACA/VtQVltgAIE1kFBhCWKBYQk8VluSkVBQVltgAIFgBwuQUJGQUFZbYQl+gWEJaFZbgRRhCYlXYACA/VtQVltgAIE1kFBhCZuBYQl1VluSkVBQVltgAIBgQIOFAxIVYQm4V2EJt2EJBVZbW2AAYQnGhYKGAWEJU1ZbklBQYCBhCdeFgoYBYQmMVluRUFCSUJKQUFZbYACBkFCRkFBWW2EJ9IFhCeFWW4JSUFBWW2AAYCCCAZBQYQoPYACDAYRhCetWW5KRUFBWW2AAYCCChAMSFWEKK1dhCiphCQVWW1tgAGEKOYSChQFhCVNWW5FQUJKRUFBWW2AAgZBQkZBQVlthClWBYQpCVluBFGEKYFdgAID9W1BWW2AAgTWQUGEKcoFhCkxWW5KRUFBWW2AAYCCChAMSFWEKjldhCo1hCQVWW1tgAGEKnISChQFhCmNWW5FQUJKRUFBWW2AAgoJSYCCCAZBQkpFQUFZbf05ldmVyIGVuZHMgd2VsbC4AAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGEK7GAQg2EKpVZbkVBhCveCYQq2VltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhCxuBYQrfVluQUJGQUFZbYQsrgWEJKlZbglJQUFZbYABgIIIBkFBhC0ZgAIMBhGELIlZbkpFQUFZbYACBUZBQkZBQVltgAIGQUJKRUFBWW2AAW4OBEBVhC4BXgIIBUYGEAVJgIIEBkFBhC2VWW4OBERVhC49XYACEhAFSW1BQUFBWW2AAYQuggmELTFZbYQuqgYVhC1dWW5NQYQu6gYVgIIYBYQtiVluAhAGRUFCSkVBQVltgAGEL0oKEYQuVVluRUIGQUJKRUFBWW2AAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAghaQUJGQUFZbYACBkFCRkFBWW2EMJGEMH4JhC91WW2EMCVZbglJQUFZbYACBYGAbkFCRkFBWW2AAYQxCgmEMKlZbkFCRkFBWW2AAYQxUgmEMN1ZbkFCRkFBWW2EMbGEMZ4JhCSpWW2EMSVZbglJQUFZbYACBkFCRkFBWW2EMjWEMiIJhCkJWW2EMclZbglJQUFZbYABhDJ+Ch2EME1ZbYAGCAZFQYQyvgoZhDFtWW2AUggGRUGEMv4KFYQx8VltgIIIBkVBhDM+ChGEMfFZbYCCCAZFQgZBQlZRQUFBQUFZbf1dlbGwsIEkgbmV2ZXIhAAAAAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGENF2AOg2EKpVZbkVBhDSKCYQzhVltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhDUaBYQ0KVluQUJGQUFZbYQ1WgWEJaFZbglJQUFZbYABggIIBkFBhDXFgAIMBh2ELIlZbYQ1+YCCDAYZhCyJWW2ENi2BAgwGFYQsiVlthDZhgYIMBhGENTVZblZRQUFBQUFZbYACBYAMLkFCRkFBWW2ENt4FhDaFWW4EUYQ3CV2AAgP1bUFZbYACBUZBQYQ3UgWENrlZbkpFQUFZbYABgIIKEAxIVYQ3wV2EN72EJBVZbW2AAYQ3+hIKFAWENxVZbkVBQkpFQUFZbYABgQIIBkFBhDhxgAIMBhWELIlZbYQ4pYCCDAYRhCyJWW5OSUFBQVv5ggGBAUjSAFWEAEFdgAID9W1BhGXyAYQAgYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhAExXYAA1YOAcgGMKKEy2FGEAUVeAY0hKlakUYQBtV4Bj2UJUAxRhAIlXgGPqWM4hFGEApVdbYACA/VthAGtgBIA2A4EBkGEAZpGQYQk5VlthAMFWWwBbYQCHYASANgOBAZBhAIKRkGEJOVZbYQElVlsAW2EAo2AEgDYDgQGQYQCekZBhCZVWW2ECNlZbAFthAL9gBIA2A4EBkGEAupGQYQmVVlthAo5WWwBbYACAYABhANKFYACGYQLmVluSUJJQklBgFmADC4MUYQEeV2BAUX8Iw3mgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFSYAQBYQEVkGEKH1ZbYEBRgJEDkP1bUFBQUFBWW2AAYEBRYQEzkGEGjlZbYEBRgJEDkGAA8IAVgBVhAU9XPWAAgD49YAD9W1CQUIBz//////////////////////////8WYwooTLZg4BuEhGBAUWAkAWEBhJKRkGELmFZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQHukZBhDARWW2AAYEBRgIMDgYVa9JFQUD2AYACBFGECKVdgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmECLlZbYGCRUFtQUFBQUFBWW2AAYQJCMINhBF5WW5BQYBZgAwuBFGECildgQFF/CMN5oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBUmAEAWECgZBhDGdWW2BAUYCRA5D9W1BQVltgAGECmjCDYQV2VluQUGAWYAMLgRRhAuJXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhAtmQYQzTVltgQFGAkQOQ/VtQUFZbYACAYGBgAIBhAWdz//////////////////////////8WYyeOC4hg4BuJiYlgQFFgJAFhAySTkpGQYQ0WVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhA46RkGEMBFZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhA8tXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hA9BWW2BgkVBbUJFQkVCBYQQsV2AVYACAZ///////////gREVYQP4V2ED92EHI1ZbW2BAUZCAglKAYCACYCABggFgQFKAFWEEJleBYCABYCCCAoA2gzeAggGRUFCQUFtQYQRBVluAgGAgAZBRgQGQYQRAkZBhDrJWW1uCYAMLklCAlVCBllCCl1BQUFBQUJNQk1CTkFBWW2AAgGAAYQFnc///////////////////////////FmMJl5ToYOAbhoZgQFFgJAFhBJeSkZBhDyFWW2BAUWAggYMDA4FSkGBAUpB7/////////////////////////////////////xkWYCCCAYBRe/////////////////////////////////////+DgYMWF4NSUFBQUGBAUWEFAZGQYQwEVltgAGBAUYCDA4FgAIZa8ZFQUD2AYACBFGEFPldgQFGRUGAfGWA/PQEWggFgQFI9glI9YABgIIQBPmEFQ1ZbYGCRUFtQkVCRUIFhBVRXYBVhBWlWW4CAYCABkFGBAZBhBWiRkGEPSlZbW2ADC5JQUFCSkVBQVltgAIBgAGEBZ3P//////////////////////////xZjSRRr3mDgG4aGYEBRYCQBYQWvkpGQYQ8hVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhBhmRkGEMBFZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhBlZXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hBltWW2BgkVBbUJFQkVCBYQZsV2AVYQaBVluAgGAgAZBRgQGQYQaAkZBhD0pWW1tgAwuSUFBQkpFQUFZbYQnPgGEPeIM5AZBWW2AAYEBRkFCQVltgAID9W2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEG2oJhBq9WW5BQkZBQVlthBuqBYQbPVluBFGEG9VdgAID9W1BWW2AAgTWQUGEHB4FhBuFWW5KRUFBWW2AAgP1bYABgHxlgH4MBFpBQkZBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EHW4JhBxJWW4EBgYEQZ///////////ghEXFWEHeldhB3lhByNWW1uAYEBSUFBQVltgAGEHjWEGm1ZbkFBhB5mCgmEHUlZbkZBQVltgAGf//////////4IRFWEHuVdhB7hhByNWW1tgIIICkFBgIIEBkFCRkFBWW2AAgP1bYACA/VtgAGf//////////4IRFWEH71dhB+5hByNWW1thB/iCYQcSVluQUGAggQGQUJGQUFZbgoGDN2AAg4MBUlBQUFZbYABhCCdhCCKEYQfUVlthB4NWW5BQgoFSYCCBAYSEhAERFWEIQ1dhCEJhB89WW1thCE6EgoVhCAVWW1CTklBQUFZbYACCYB+DARJhCGtXYQhqYQcNVltbgTVhCHuEgmAghgFhCBRWW5FQUJKRUFBWW2AAYQiXYQiShGEHnlZbYQeDVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQi6V2EIuWEHylZbW4NbgYEQFWEJAVeANWf//////////4ERFWEI31dhCN5hBw1WW1uAhgFhCOyJgmEIVlZbhVJgIIUBlFBQUGAggQGQUGEIvFZbUFBQk5JQUFBWW2AAgmAfgwESYQkgV2EJH2EHDVZbW4E1YQkwhIJgIIYBYQiEVluRUFCSkVBQVltgAIBgQIOFAxIVYQlQV2EJT2EGpVZbW2AAYQlehYKGAWEG+FZbklBQYCCDATVn//////////+BERVhCX9XYQl+YQaqVltbYQmLhYKGAWEJC1ZbkVBQklCSkFBWW2AAYCCChAMSFWEJq1dhCaphBqVWW1tgAGEJuYSChQFhBvhWW5FQUJKRUFBWW2AAgoJSYCCCAZBQkpFQUFZbf0Nhbid0IQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGEKCWAGg2EJwlZbkVBhChSCYQnTVltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhCjiBYQn8VluQUJGQUFZbYQpIgWEGz1ZbglJQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAgZBQYCCCAZBQkZBQVltgAIFRkFCRkFBWW2AAgoJSYCCCAZBQkpFQUFZbYABbg4EQFWEKtFeAggFRgYQBUmAggQGQUGEKmVZbg4ERFWEKw1dgAISEAVJbUFBQUFZbYABhCtSCYQp6VlthCt6BhWEKhVZbk1BhCu6BhWAghgFhCpZWW2EK94FhBxJWW4QBkVBQkpFQUFZbYABhCw6Dg2EKyVZbkFCSkVBQVltgAGAgggGQUJGQUFZbYABhCy6CYQpOVlthCziBhWEKWVZbk1CDYCCCAoUBYQtKhWEKalZbgGAAW4WBEBVhC4ZXhIQDiVKBUWELZ4WCYQsCVluUUGELcoNhCxZWW5JQYCCKAZlQUGABgQGQUGELTlZbUIKXUIeVUFBQUFBQkpFQUFZbYABgQIIBkFBhC61gAIMBhWEKP1ZbgYEDYCCDAVJhC7+BhGELI1ZbkFCTklBQUFZbYACBkFCSkVBQVltgAGEL3oJhCnpWW2EL6IGFYQvIVluTUGEL+IGFYCCGAWEKllZbgIQBkVBQkpFQUFZbYABhDBCChGEL01ZbkVCBkFCSkVBQVlt/U28gdW5mYWlyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQxRYAmDYQnCVluRUGEMXIJhDBtWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEMgIFhDERWW5BQkZBQVlt/SXQncyB1bmhlYXJkIG9mLi4uAAAAAAAAAAAAAAAAAABgAIIBUlBWW2AAYQy9YBKDYQnCVluRUGEMyIJhDIdWW2AgggGQUJGQUFZbYABgIIIBkFCBgQNgAIMBUmEM7IFhDLBWW5BQkZBQVltgAGf//////////4IWkFCRkFBWW2ENEIFhDPNWW4JSUFBWW2AAYGCCAZBQYQ0rYACDAYZhCj9WW2ENOGAggwGFYQ0HVluBgQNgQIMBUmENSoGEYQsjVluQUJSTUFBQUFZbYACBYAMLkFCRkFBWW2ENaoFhDVRWW4EUYQ11V2AAgP1bUFZbYACBUZBQYQ2HgWENYVZbkpFQUFZbYQ2WgWEM81ZbgRRhDaFXYACA/VtQVltgAIFRkFBhDbOBYQ2NVluSkVBQVltgAGf//////////4IRFWEN1FdhDdNhByNWW1tgIIICkFBgIIEBkFCRkFBWW2AAgZBQkZBQVlthDfiBYQ3lVluBFGEOA1dgAID9W1BWW2AAgVGQUGEOFYFhDe9WW5KRUFBWW2AAYQ4uYQ4phGENuVZbYQeDVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQ5RV2EOUGEHylZbW4NbgYEQFWEOeleAYQ5miIJhDgZWW4RSYCCEAZNQUGAggQGQUGEOU1ZbUFBQk5JQUFBWW2AAgmAfgwESYQ6ZV2EOmGEHDVZbW4FRYQ6phIJgIIYBYQ4bVluRUFCSkVBQVltgAIBgAGBghIYDEhVhDstXYQ7KYQalVltbYABhDtmGgocBYQ14VluTUFBgIGEO6oaChwFhDaRWW5JQUGBAhAFRZ///////////gREVYQ8LV2EPCmEGqlZbW2EPF4aChwFhDoRWW5FQUJJQklCSVltgAGBAggGQUGEPNmAAgwGFYQo/VlthD0NgIIMBhGEKP1Zbk5JQUFBWW2AAYCCChAMSFWEPYFdhD19hBqVWW1tgAGEPboSChQFhDXhWW5FQUJKRUFBW/mCAYEBSNIAVYQAQV2AAgP1bUGEJr4BhACBgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEAK1dgADVg4ByAYwooTLYUYQAwV1tgAID9W2EASmAEgDYDgQGQYQBFkZBhBMZWW2EATFZbAFtgAIBgAGEAXYVgAIZhALBWW5JQklCSUGAWYAMLgxRhAKlXYEBRfwjDeaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgVJgBAFhAKCQYQV/VltgQFGAkQOQ/VtQUFBQUFZbYACAYGBgAIBhAWdz//////////////////////////8WYyeOC4hg4BuJiYlgQFFgJAFhAO6TkpGQYQcbVltgQFFgIIGDAwOBUpBgQFKQe/////////////////////////////////////8ZFmAgggGAUXv/////////////////////////////////////g4GDFheDUlBQUFBgQFFhAViRkGEHlVZbYABgQFGAgwOBYACGWvGRUFA9gGAAgRRhAZVXYEBRkVBgHxlgPz0BFoIBYEBSPYJSPWAAYCCEAT5hAZpWW2BgkVBbUJFQkVCBYQH2V2AVYACAZ///////////gREVYQHCV2EBwWECsFZbW2BAUZCAglKAYCACYCABggFgQFKAFWEB8FeBYCABYCCCAoA2gzeAggGRUFCQUFtQYQILVluAgGAgAZBRgQGQYQIKkZBhCQpWW1uCYAMLklCAlVCBllCCl1BQUFBQUJNQk1CTkFBWW2AAYEBRkFCQVltgAID9W2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGECZ4JhAjxWW5BQkZBQVlthAneBYQJcVluBFGECgldgAID9W1BWW2AAgTWQUGEClIFhAm5WW5KRUFBWW2AAgP1bYABgHxlgH4MBFpBQkZBQVlt/Tkh7cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAFJgQWAEUmAkYAD9W2EC6IJhAp9WW4EBgYEQZ///////////ghEXFWEDB1dhAwZhArBWW1uAYEBSUFBQVltgAGEDGmECKFZbkFBhAyaCgmEC31ZbkZBQVltgAGf//////////4IRFWEDRldhA0VhArBWW1tgIIICkFBgIIEBkFCRkFBWW2AAgP1bYACA/VtgAGf//////////4IRFWEDfFdhA3thArBWW1thA4WCYQKfVluQUGAggQGQUJGQUFZbgoGDN2AAg4MBUlBQUFZbYABhA7RhA6+EYQNhVlthAxBWW5BQgoFSYCCBAYSEhAERFWED0FdhA89hA1xWW1thA9uEgoVhA5JWW1CTklBQUFZbYACCYB+DARJhA/hXYQP3YQKaVltbgTVhBAiEgmAghgFhA6FWW5FQUJKRUFBWW2AAYQQkYQQfhGEDK1ZbYQMQVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQRHV2EERmEDV1ZbW4NbgYEQFWEEjleANWf//////////4ERFWEEbFdhBGthAppWW1uAhgFhBHmJgmED41ZbhVJgIIUBlFBQUGAggQGQUGEESVZbUFBQk5JQUFBWW2AAgmAfgwESYQStV2EErGECmlZbW4E1YQS9hIJgIIYBYQQRVluRUFCSkVBQVltgAIBgQIOFAxIVYQTdV2EE3GECMlZbW2AAYQTrhYKGAWEChVZbklBQYCCDATVn//////////+BERVhBQxXYQULYQI3VltbYQUYhYKGAWEEmFZbkVBQklCSkFBWW2AAgoJSYCCCAZBQkpFQUFZbf0Nhbid0IGV2ZW4hAAAAAAAAAAAAAAAAAAAAAAAAAAAAYACCAVJQVltgAGEFaWALg2EFIlZbkVBhBXSCYQUzVltgIIIBkFCRkFBWW2AAYCCCAZBQgYEDYACDAVJhBZiBYQVcVluQUJGQUFZbYQWogWECXFZbglJQUFZbYABn//////////+CFpBQkZBQVlthBcuBYQWuVluCUlBQVltgAIFRkFCRkFBWW2AAgoJSYCCCAZBQkpFQUFZbYACBkFBgIIIBkFCRkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQY3V4CCAVGBhAFSYCCBAZBQYQYcVluDgREVYQZGV2AAhIQBUltQUFBQVltgAGEGV4JhBf1WW2EGYYGFYQYIVluTUGEGcYGFYCCGAWEGGVZbYQZ6gWECn1ZbhAGRUFCSkVBQVltgAGEGkYODYQZMVluQUJKRUFBWW2AAYCCCAZBQkZBQVltgAGEGsYJhBdFWW2EGu4GFYQXcVluTUINgIIIChQFhBs2FYQXtVluAYABbhYEQFWEHCVeEhAOJUoFRYQbqhYJhBoVWW5RQYQb1g2EGmVZbklBgIIoBmVBQYAGBAZBQYQbRVltQgpdQh5VQUFBQUFCSkVBQVltgAGBgggGQUGEHMGAAgwGGYQWfVlthBz1gIIMBhWEFwlZbgYEDYECDAVJhB0+BhGEGplZbkFCUk1BQUFBWW2AAgZBQkpFQUFZbYABhB2+CYQX9VlthB3mBhWEHWVZbk1BhB4mBhWAghgFhBhlWW4CEAZFQUJKRUFBWW2AAYQehgoRhB2RWW5FQgZBQkpFQUFZbYACBYAMLkFCRkFBWW2EHwoFhB6xWW4EUYQfNV2AAgP1bUFZbYACBUZBQYQffgWEHuVZbkpFQUFZbYQfugWEFrlZbgRRhB/lXYACA/VtQVltgAIFRkFBhCAuBYQflVluSkVBQVltgAGf//////////4IRFWEILFdhCCthArBWW1tgIIICkFBgIIEBkFCRkFBWW2AAgZBQkZBQVlthCFCBYQg9VluBFGEIW1dgAID9W1BWW2AAgVGQUGEIbYFhCEdWW5KRUFBWW2AAYQiGYQiBhGEIEVZbYQMQVluQUICDglJgIIIBkFBgIIQCgwGFgREVYQipV2EIqGEDV1ZbW4NbgYEQFWEI0leAYQi+iIJhCF5WW4RSYCCEAZNQUGAggQGQUGEIq1ZbUFBQk5JQUFBWW2AAgmAfgwESYQjxV2EI8GECmlZbW4FRYQkBhIJgIIYBYQhzVluRUFCSkVBQVltgAIBgAGBghIYDEhVhCSNXYQkiYQIyVltbYABhCTGGgocBYQfQVluTUFBgIGEJQoaChwFhB/xWW5JQUGBAhAFRZ///////////gREVYQljV2EJYmECN1ZbW2EJb4aChwFhCNxWW5FQUJJQklCSVv6iZGlwZnNYIhIgo9/5AtiGz/RT5mHSgDggriarT0Wkj6MZUL0DHwjT66xkc29sY0MACAwAM6JkaXBmc1giEiBVBFkYIwJIVEIGWNSUWLSu2E7yr64omJ4DFWh/WZFLP2Rzb2xjQwAIDAAzomRpcGZzWCISIJkEQOo0L5yDEaqTltlkvCPctWJmJfUMDy1vPngIQzzmZHNvbGNDAAgMADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKO7tDDoDGNwXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAvccgcKAxjcFxABUhYKCQoCGAIQg/ChDgoJCgIYYhCE8KEO"},{"b64Body":"Cg8KCQiB9/+rBhDtBxICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46MAoDGNwXEICS9AEiJKv3v9iqu8zd7v8AEaq7zN3u/wARqrvM3e7/ABGqu8zd7v8AEQ==","b64Record":"CiUIFiIDGNwXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCPqi4OdxFg64lp5fhhgonJHyF9Xif4EeR3sDOmFm3CE1EzDNJ2anBBMZRNyUUiiXQaDAi99/+rBhCb48y5ASIPCgkIgff/qwYQ7QcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOqQCCgMY3BcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwE6AxjdF3IHCgMY3BcQAnIHCgMY3RcQAVIYCgoKAhgCEP+v1tgBCgoKAhhiEICw1tgB"},{"b64Body":"ChEKCQiB9/+rBhDtBxICGAIgAUI4GiISIAYit808jbHcfGX+EtW8c/2yBpk3G01PKyAjEs0nXFr5QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGN0XEjA/YMduuuHaOWq5m0lTzb8OjaTELgSevDlEvU7HxgOkw3OtjuiAWGZVoh2wD3ZMoZgaDAi99/+rBhCc48y5ASIRCgkIgff/qwYQ7QcSAhgCIAFCHQoDGN0XShYKFLebGvAmbscOfjvinCg6VUjubCMFUgB6DAi99/+rBhCb48y5AQ=="},{"b64Body":"Cg8KCQiB9/+rBhDyBxICGAISAhgDGIDIr6AlIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAVYKEG5vbkZ1bmdpYmxlVG9rZW4SCFFIQVJDRUdJKgMY3RdSIhIgBiK3zTyNsdx8Zf4S1bxz/bIGmTcbTU8rICMSzSdcWvlqDAi9xdqvBhCw/O6kA4gBAQ==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGN4XEjBqN7HrVn2HQqbOCOUe9ybKigNnE3l2wChxKG8fLyI9T8LrnW9qNsaxoYKGkuUGjToaDAi99/+rBhCL8cK6AyIPCgkIgff/qwYQ8gcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAcgoKAxjeFxIDGN0X"},{"b64Body":"Cg8KCQiC9/+rBhD4BxICGAISAhgDGNPtlwgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjqoCEAoDGN4XGglQUklDRUxFU1M=","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1gBcgEBEjAFcegwABUhjx7fYQQKSBlSsRATJu4Ldadun5G5KyjUVwNgX5ZFkA1pTIMzCT93V7saDAi+9/+rBhCT68vCASIPCgkIgvf/qwYQ+AcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWhIKAxjeFxoLCgIYABIDGN0XGAE="},{"b64Body":"Cg8KCQiC9/+rBhCACBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46UAoDGNsXEICS9AEiRB8p0twAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB","b64Record":"CiUIFiIDGNsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAkPlH0jYcf1WU9OIRg3sZgGIMV2j5qlXoUkYwncpJKS/X7W1lxuhZsv8vOj3oL/D8aDAi+9/+rBhCbhrjEAyIPCgkIgvf/qwYQgAgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOo0CCgMY2xcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICowwFSGAoKCgIYAhD/r9bYAQoKCgIYYhCAsNbYAQ=="},{"b64Body":"ChEKCQiC9/+rBhCACBICGAIgATpFCgMY5wIQASI8YY3GXgAAAAAAAAAAAAAAAAAAAAAAAAveY1IhHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB","b64Record":"CgIIFhIwTvqBhRyOwqSGXqi8bmpqxvBfDiuZ5IcFSUQQxcCHNvyfIzG+WsCjAWDLhoZrSJMBGgwIvvf/qwYQnIa4xAMiEQoJCIL3/6sGEIAIEgIYAiABOi4KAxjnAhIgAAAAAAAAAAAAAAAAt5sa8CZuxw5+O+KcKDpVSO5sIwUoZGoDGNsXUgB6DAi+9/+rBhCbhrjEAw=="}]},"CanAssociateInConstructor":{"placeholderNum":3039,"encodedItems":[{"b64Body":"Cg8KCQiH9/+rBhCYCBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjDxdqvBhCItaemARptCiISIPnrLkrZcOGV446hCMslySxYohfgBJrvlrL5GrdbtOgnCiM6IQPctApZ7eNS/3BEMDTuOtJo4cIgjGDJ3PSut0I3TFRHsAoiEiBuGh4jgfwErAnZjsVHNkMV62xlFvthUdgXszWyIdz5hCIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOAXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB23l7C2Xc9zsSWs1W70kF0/MP8WTGaBaeQEhcGC3qPqZeB83BDGAZnX1CFCPY3kuMaDAjD9/+rBhDLzpW+ASIPCgkIh/f/qwYQmAgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiH9/+rBhCcCBICGAISAhgDGKLupTsiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBghsKAxjgFyL6GjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwNDA1MTYxMDZiZDM4MDM4MDYxMDZiZDgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMzI5MTkwNjEwMWRmNTY1YjYwMDAzMDkwNTA2MDAwNjEwMDRkODI4NDYxMDA5ZDYwMjAxYjYxMDAwOTE3NjAyMDFjNTY1YjkwNTA2MDE2NjAwMzBiODExNDYxMDA5NTU3NjA0MDUxN2YwOGMzNzlhMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwODE1MjYwMDQwMTYxMDA4YzkwNjEwMmRjNTY1YjYwNDA1MTgwOTEwMzkwZmQ1YjUwNTA1MDYxMDNmMTU2NWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzQ5MTQ2YmRlNjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDBkNjkyOTE5MDYxMDJiMzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAxNDA5MTkwNjEwMjljNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAxN2Q1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAxODI1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDE5MzU3NjAxNTYxMDFhODU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDFhNzkxOTA2MTAyMGM1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDFjNDgxNjEwM2MzNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDFkOTgxNjEwM2RhNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDFmNTU3NjEwMWY0NjEwMzk1NTY1YjViNjAwMDYxMDIwMzg0ODI4NTAxNjEwMWI1NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDIyMjU3NjEwMjIxNjEwMzk1NTY1YjViNjAwMDYxMDIzMDg0ODI4NTAxNjEwMWNhNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDI0MjgxNjEwMzIzNTY1YjgyNTI1MDUwNTY1YjYwMDA2MTAyNTM4MjYxMDJmYzU2NWI2MTAyNWQ4MTg1NjEwMzA3NTY1YjkzNTA2MTAyNmQ4MTg1NjAyMDg2MDE2MTAzNjI1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDI4NjYwMWI4MzYxMDMxMjU2NWI5MTUwNjEwMjkxODI2MTAzOWE1NjViNjAyMDgyMDE5MDUwOTE5MDUwNTY1YjYwMDA2MTAyYTg4Mjg0NjEwMjQ4NTY1YjkxNTA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjA0MDgyMDE5MDUwNjEwMmM4NjAwMDgzMDE4NTYxMDIzOTU2NWI2MTAyZDU2MDIwODMwMTg0NjEwMjM5NTY1YjkzOTI1MDUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTAyZjU4MTYxMDI3OTU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTI5MTUwNTA1NjViNjAwMDgyODI1MjYwMjA4MjAxOTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAzMmU4MjYxMDM0MjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMzgwNTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMzY1NTY1YjgzODExMTE1NjEwMzhmNTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA4MGZkNWI3ZjQzNmY3NTZjNjQyMDZlNmY3NDIwNjE3MzczNmY2MzY5NjE3NDY1MjA2MTYzNjM2Zjc1NmU3NDAwMDAwMDAwMDA2MDAwODIwMTUyNTA1NjViNjEwM2NjODE2MTAzMjM1NjViODExNDYxMDNkNzU3NjAwMDgwZmQ1YjUwNTY1YjYxMDNlMzgxNjEwMzM1NTY1YjgxMTQ2MTAzZWU1NzYwMDA4MGZkNWI1MDU2NWI2MTAyYmQ4MDYxMDQwMDYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDA4MGZkNWI2MDAwODA2MDAwNjEwMTY3NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY2MzQ5MTQ2YmRlNjBlMDFiODY4NjYwNDA1MTYwMjQwMTYxMDA0MjkyOTE5MDYxMDFiYTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MDdiZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxOTE2NjAyMDgyMDE4MDUxN2JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgzODE4MzE2MTc4MzUyNTA1MDUwNTA2MDQwNTE2MTAwYWM5MTkwNjEwMWEzNTY1YjYwMDA2MDQwNTE4MDgzMDM4MTYwMDA4NjVhZjE5MTUwNTAzZDgwNjAwMDgxMTQ2MTAwZTk1NzYwNDA1MTkxNTA2MDFmMTk2MDNmM2QwMTE2ODIwMTYwNDA1MjNkODI1MjNkNjAwMDYwMjA4NDAxM2U2MTAwZWU1NjViNjA2MDkxNTA1YjUwOTE1MDkxNTA4MTYxMDBmZjU3NjAxNTYxMDExNDU2NWI4MDgwNjAyMDAxOTA1MTgxMDE5MDYxMDExMzkxOTA2MTAxMzY1NjViNWI2MDAzMGI5MjUwNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTUxOTA1MDYxMDEzMDgxNjEwMjcwNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDE0YzU3NjEwMTRiNjEwMjZiNTY1YjViNjAwMDYxMDE1YTg0ODI4NTAxNjEwMTIxNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDE2YzgxNjEwMWY5NTY1YjgyNTI1MDUwNTY1YjYwMDA2MTAxN2Q4MjYxMDFlMzU2NWI2MTAxODc4MTg1NjEwMWVlNTY1YjkzNTA2MTAxOTc4MTg1NjAyMDg2MDE2MTAyMzg1NjViODA4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDFhZjgyODQ2MTAxNzI1NjViOTE1MDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MTAxY2Y2MDAwODMwMTg1NjEwMTYzNTY1YjYxMDFkYzYwMjA4MzAxODQ2MTAxNjM1NjViOTM5MjUwNTA1MDU2NWI2MDAwODE1MTkwNTA5MTkwNTA1NjViNjAwMDgxOTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAyMDQ4MjYxMDIxODU2NWI5MDUwOTE5MDUwNTY1YjYwMDA4MTYwMDMwYjkwNTA5MTkwNTA1NjViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA1YjgzODExMDE1NjEwMjU2NTc4MDgyMDE1MTgxODQwMTUyNjAyMDgxMDE5MDUwNjEwMjNiNTY1YjgzODExMTE1NjEwMjY1NTc2MDAwODQ4NDAxNTI1YjUwNTA1MDUwNTY1YjYwMDA4MGZkNWI2MTAyNzk4MTYxMDIwYjU2NWI4MTE0NjEwMjg0NTc2MDAwODBmZDViNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBiNzA3Yzk5OGFmNGQwMjI4OTJhNWRjNGVlMWJjNDIyMzRiOWQyMGEzNmE3ZTRkNzgzZGVhYTdlZGEzNmVkMTc1NjQ3MzZmNmM2MzQzMDAwODA3MDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw8Dq2UVSMVf88vLzmU8GH+/IBsyGYN68AsoKYML6VWojooeetkEpVOL/EkogfMJCjGgwIw/f/qwYQw5GIowMiDwoJCIf3/6sGEJwIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiI9/+rBhCeCBICGAISAhgDGIKu5dYCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qASYKBXRva2VuEghVQ1ZHVE1ESCCQTioCGAJqDAjExdqvBhCgwoutAQ==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGOEXEjBECeV6ME9NC4KDeCgLnmvNoY8bbtDGHdkLKQAn93yMW67XEIWxkoJqRRe7lSo73n8aDAjE9/+rBhD7rP3HASIPCgkIiPf/qwYQnggSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWg8KAxjhFxIICgIYAhCgnAFyCQoDGOEXEgIYAg=="},{"b64Body":"Cg8KCQiI9/+rBhCgCBICGAISAhgDGIDa/6YBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRAoDGOAXIICS9AFCBQiAztoDSiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL4VIAWgBqC2NlbGxhciBkb29y","b64Record":"CiUIFiIDGOIXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCnM6nYg4vGxUN6ZBzLVuU1GWRlSmSfjmxoxkQP3KCxy9tSyZyCk9VG76ecn1bMIJYaDAjE9/+rBhD7uY6tAyIPCgkIiPf/qwYQoAgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsQvMHCgMY4hcSvQVggGBAUmAAgP1bYACAYABhAWdz//////////////////////////8WY0kUa95g4BuGhmBAUWAkAWEAQpKRkGEBulZbYEBRYCCBgwMDgVKQYEBSkHv/////////////////////////////////////GRZgIIIBgFF7/////////////////////////////////////4OBgxYXg1JQUFBQYEBRYQCskZBhAaNWW2AAYEBRgIMDgWAAhlrxkVBQPYBgAIEUYQDpV2BAUZFQYB8ZYD89ARaCAWBAUj2CUj1gAGAghAE+YQDuVltgYJFQW1CRUJFQgWEA/1dgFWEBFFZbgIBgIAGQUYEBkGEBE5GQYQE2VltbYAMLklBQUJKRUFBWW2AAgVGQUGEBMIFhAnBWW5KRUFBWW2AAYCCChAMSFWEBTFdhAUthAmtWW1tgAGEBWoSChQFhASFWW5FQUJKRUFBWW2EBbIFhAflWW4JSUFBWW2AAYQF9gmEB41ZbYQGHgYVhAe5WW5NQYQGXgYVgIIYBYQI4VluAhAGRUFCSkVBQVltgAGEBr4KEYQFyVluRUIGQUJKRUFBWW2AAYECCAZBQYQHPYACDAYVhAWNWW2EB3GAggwGEYQFjVluTklBQUFZbYACBUZBQkZBQVltgAIGQUJKRUFBWW2AAYQIEgmECGFZbkFCRkFBWW2AAgWADC5BQkZBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAW4OBEBVhAlZXgIIBUYGEAVJgIIEBkFBhAjtWW4OBERVhAmVXYACEhAFSW1BQUFBWW2AAgP1bYQJ5gWECC1ZbgRRhAoRXYACA/VtQVv6iZGlwZnNYIhIgtwfJmK9NAiiSpdxO4bxCI0udIKNqfk14Peqn7aNu0XVkc29sY0MACAcAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogKjDAToDGOIXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAvicgcKAxjiFxABUhgKCgoCGAIQ/6/W2AEKCgoCGGIQgLDW2AE="},{"b64Body":"ChEKCQiI9/+rBhCgCBICGAIgAcICCgoDGOIXEgMY4Rc=","b64Record":"CgIIFhIwoEEywzHcVfR91GLjGyxYwrLseswyFXp5orsYrfdv75kfm8JQ6oPiEQSJcxBjMOO+GgwIxPf/qwYQ/LmOrQMiEQoJCIj3/6sGEKAIEgIYAiABOnsKAxjnAhIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYo4v0qUOam6wFiREkUa94AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL4gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvhagMY4hdSAHoMCMT3/6sGEPu5jq0D"}]},"CanMergeCreate2ChildWithHollowAccount":{"placeholderNum":3043,"encodedItems":[{"b64Body":"Cg8KCQiN9/+rBhC0CBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjJxdqvBhDI88HHARptCiISICRTbUqquAvmHQxhUv0K7OaIq6Ub6uLTYHbM5CyGcx+7CiM6IQPiKBp3UzUVME6S1sCxKcywkcs7I3Tg4cOO2Z9qXQSefQoiEiAaZ7//qBkDZ00jnA4ZJ28XwdEfhOcnLJx0x3FwQqj15CIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOQXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBSJIt07qjYQrW2YrTTDjBoaUCPOkfSYw5FDBjlfrOihBMnquoSw7gvSrR7uKxQKMYaDAjJ9/+rBhDjit7WASIPCgkIjff/qwYQtAgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiN9/+rBhC4CBICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjkFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGI0NTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwM2Y1NzYwMDAzNTYwZTAxYzgwNjM4MTg3MWNiYzE0NjEwMDQ0NTc4MDYzOTRjYTJjYjUxNDYxMDA4MTU3ODA2MzljNGFlMmQwMTQ2MTAwYmU1NzgwNjNhMzRkMDYwMzE0NjEwMGRhNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwNTA1NzYwMDA4MGZkNWI1MDYxMDA2YjYwMDQ4MDM2MDM4MTAxOTA2MTAwNjY5MTkwNjEwMmZhNTY1YjYxMDBmNjU2NWI2MDQwNTE2MTAwNzg5MTkwNjEwM2NhNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDA4ZDU3NjAwMDgwZmQ1YjUwNjEwMGE4NjAwNDgwMzYwMzgxMDE5MDYxMDBhMzkxOTA2MTA1MjE1NjViNjEwMTZjNTY1YjYwNDA1MTYxMDBiNTkxOTA2MTA1OGM1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMGQ4NjAwNDgwMzYwMzgxMDE5MDYxMDBkMzkxOTA2MTA1MjE1NjViNjEwMWI2NTY1YjAwNWI2MTAwZjQ2MDA0ODAzNjAzODEwMTkwNjEwMGVmOTE5MDYxMDUyMTU2NWI2MTAyMGM1NjViMDA1YjYwNjA2MDAwNjA0MDUxODA2MDIwMDE2MTAxMGE5MDYxMDI0NTU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDgwODQ4NDYwNDA1MTYwMjAwMTYxMDEzNDkyOTE5MDYxMDViNjU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI2MDQwNTE2MDIwMDE2MTAxNTQ5MjkxOTA2MTA2MWI1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyOTE1MDUwOTI5MTUwNTA1NjViNjAwMDgwNjBmZjYwZjgxYjMwODQ4NjgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTkxOTQ5MzkyOTE5MDYxMDcyMDU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDYwMDAxYzkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTgzNTE2MDIwODUwMTM0ZjU5MDUwODAzYjYxMDFjZTU3NjAwMDgwZmQ1YjdmYjAzYzUzYjI4ZTc4YTg4ZTMxNjA3YTI3ZTFmYTQ4MjM0ZGNlMjhkNWQ5ZDllYzdiMjk1YWViMDJlNjc0YTFlMTgxODM2MDQwNTE2MTAxZmY5MjkxOTA2MTA1YjY1NjViNjA0MDUxODA5MTAzOTBhMTUwNTA1MDU2NWI2MDAwODA2MDAyMzQ2MTAyMWM5MTkwNjEwNzlkNTY1YjkwNTA4Mjg0NTE2MDIwODYwMTgzZjU5MTUwODI4NDUxNjAyMDg2MDE4M2Y1OTE1MDgxM2I2MTAyM2Y1NzYwMDA4MGZkNWI1MDUwNTA1MDU2NWI2MTAzNDE4MDYxMDdjZjgzMzkwMTkwNTY1YjYwMDA2MDQwNTE5MDUwOTA1NjViNjAwMDgwZmQ1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDI5MTgyNjEwMjY2NTY1YjkwNTA5MTkwNTA1NjViNjEwMmExODE2MTAyODY1NjViODExNDYxMDJhYzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJiZTgxNjEwMjk4NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMmQ3ODE2MTAyYzQ1NjViODExNDYxMDJlMjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYxMDJmNDgxNjEwMmNlNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMzExNTc2MTAzMTA2MTAyNWM1NjViNWI2MDAwNjEwMzFmODU4Mjg2MDE2MTAyYWY1NjViOTI1MDUwNjAyMDYxMDMzMDg1ODI4NjAxNjEwMmU1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MjgyNTI2MDIwODIwMTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDM3NDU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDM1OTU2NWI2MDAwODQ4NDAxNTI1MDUwNTA1MDU2NWI2MDAwNjAxZjE5NjAxZjgzMDExNjkwNTA5MTkwNTA1NjViNjAwMDYxMDM5YzgyNjEwMzNhNTY1YjYxMDNhNjgxODU2MTAzNDU1NjViOTM1MDYxMDNiNjgxODU2MDIwODYwMTYxMDM1NjU2NWI2MTAzYmY4MTYxMDM4MDU2NWI4NDAxOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDgxODEwMzYwMDA4MzAxNTI2MTAzZTQ4MTg0NjEwMzkxNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MTA0MmU4MjYxMDM4MDU2NWI4MTAxODE4MTEwNjdmZmZmZmZmZmZmZmZmZmZmODIxMTE3MTU2MTA0NGQ1NzYxMDQ0YzYxMDNmNjU2NWI1YjgwNjA0MDUyNTA1MDUwNTY1YjYwMDA2MTA0NjA2MTAyNTI1NjViOTA1MDYxMDQ2YzgyODI2MTA0MjU1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MTA0OGM1NzYxMDQ4YjYxMDNmNjU2NWI1YjYxMDQ5NTgyNjEwMzgwNTY1YjkwNTA2MDIwODEwMTkwNTA5MTkwNTA1NjViODI4MTgzMzc2MDAwODM4MzAxNTI1MDUwNTA1NjViNjAwMDYxMDRjNDYxMDRiZjg0NjEwNDcxNTY1YjYxMDQ1NjU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MTA0ZTA1NzYxMDRkZjYxMDNmMTU2NWI1YjYxMDRlYjg0ODI4NTYxMDRhMjU2NWI1MDkzOTI1MDUwNTA1NjViNjAwMDgyNjAxZjgzMDExMjYxMDUwODU3NjEwNTA3NjEwM2VjNTY1YjViODEzNTYxMDUxODg0ODI2MDIwODYwMTYxMDRiMTU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MDQwODM4NTAzMTIxNTYxMDUzODU3NjEwNTM3NjEwMjVjNTY1YjViNjAwMDgzMDEzNTY3ZmZmZmZmZmZmZmZmZmZmZjgxMTExNTYxMDU1NjU3NjEwNTU1NjEwMjYxNTY1YjViNjEwNTYyODU4Mjg2MDE2MTA0ZjM1NjViOTI1MDUwNjAyMDYxMDU3Mzg1ODI4NjAxNjEwMmU1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwNTg2ODE2MTAyODY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDVhMTYwMDA4MzAxODQ2MTA1N2Q1NjViOTI5MTUwNTA1NjViNjEwNWIwODE2MTAyYzQ1NjViODI1MjUwNTA1NjViNjAwMDYwNDA4MjAxOTA1MDYxMDVjYjYwMDA4MzAxODU2MTA1N2Q1NjViNjEwNWQ4NjAyMDgzMDE4NDYxMDVhNzU2NWI5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjEwNWY1ODI2MTAzM2E1NjViNjEwNWZmODE4NTYxMDVkZjU2NWI5MzUwNjEwNjBmODE4NTYwMjA4NjAxNjEwMzU2NTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTA2Mjc4Mjg1NjEwNWVhNTY1YjkxNTA2MTA2MzM4Mjg0NjEwNWVhNTY1YjkxNTA4MTkwNTA5MzkyNTA1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDY4NjYxMDY4MTgyNjEwNjNmNTY1YjYxMDY2YjU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YTQ4MjYxMDY4YzU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTA2YjY4MjYxMDY5OTU2NWI5MDUwOTE5MDUwNTY1YjYxMDZjZTYxMDZjOTgyNjEwMjg2NTY1YjYxMDZhYjU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDZlZjYxMDZlYTgyNjEwMmM0NTY1YjYxMDZkNDU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNzFhNjEwNzE1ODI2MTA2ZjU1NjViNjEwNmZmNTY1YjgyNTI1MDUwNTY1YjYwMDA2MTA3MmM4Mjg3NjEwNjc1NTY1YjYwMDE4MjAxOTE1MDYxMDczYzgyODY2MTA2YmQ1NjViNjAxNDgyMDE5MTUwNjEwNzRjODI4NTYxMDZkZTU2NWI2MDIwODIwMTkxNTA2MTA3NWM4Mjg0NjEwNzA5NTY1YjYwMjA4MjAxOTE1MDgxOTA1MDk1OTQ1MDUwNTA1MDUwNTY1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwMTI2MDA0NTI2MDI0NjAwMGZkNWI2MDAwNjEwN2E4ODI2MTAyYzQ1NjViOTE1MDYxMDdiMzgzNjEwMmM0NTY1YjkyNTA4MjYxMDdjMzU3NjEwN2MyNjEwNzZlNTY1YjViODI4MjA0OTA1MDkyOTE1MDUwNTZmZTYwODA2MDQwNTI2MDQwNTE2MTAzNDEzODAzODA2MTAzNDE=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwBBLkEyc/6XDaHsLH1n6DczE00V38ghPJrGfY+LevbpOMTpGPww103AI6sWcxKia1GgwIyff/qwYQu5us1wMiDwoJCI33/6sGELgIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO9/+rBhC+CBICGAISAhgDGLnZvjQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIB0g0SAxjkFyLKDTgzMzk4MTgxMDE2MDQwNTI4MTAxOTA2MTAwMjU5MTkwNjEwMTBjNTY1YjgxNjAwMDgwNjEwMTAwMGE4MTU0ODE3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYwMjE5MTY5MDgzNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTYwMjE3OTA1NTUwODA2MDAxODE5MDU1NTA1MDUwNjEwMTRjNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDBhMzgyNjEwMDc4NTY1YjkwNTA5MTkwNTA1NjViNjEwMGIzODE2MTAwOTg1NjViODExNDYxMDBiZTU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDBkMDgxNjEwMGFhNTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMGU5ODE2MTAwZDY1NjViODExNDYxMDBmNDU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDEwNjgxNjEwMGUwNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMTIzNTc2MTAxMjI2MTAwNzM1NjViNWI2MDAwNjEwMTMxODU4Mjg2MDE2MTAwYzE1NjViOTI1MDUwNjAyMDYxMDE0Mjg1ODI4NjAxNjEwMGY3NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjEwMWU2ODA2MTAxNWI2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDRjNTc2MDAwMzU2MGUwMWM4MDYzMTIwNjVmZTAxNDYxMDA1MTU3ODA2MzczN2JjM2M5MTQ2MTAwNmY1NzgwNjM4ZGE1Y2I1YjE0NjEwMDc5NTc4MDYzYzI5ODU1NzgxNDYxMDA5NzU3NWI2MDAwODBmZDViNjEwMDU5NjEwMGI1NTY1YjYwNDA1MTYxMDA2NjkxOTA2MTAxMzk1NjViNjA0MDUxODA5MTAzOTBmMzViNjEwMDc3NjEwMGJkNTY1YjAwNWI2MTAwODE2MTAwZjY1NjViNjA0MDUxNjEwMDhlOTE5MDYxMDE5NTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwOWY2MTAxMWE1NjViNjA0MDUxNjEwMGFjOTE5MDYxMDEzOTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNDc5MDUwOTA1NjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgwNTQ5MDYxMDEwMDBhOTAwNDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1NjViNjAwMTU0ODE1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MTAxMzM4MTYxMDEyMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTRlNjAwMDgzMDE4NDYxMDEyYTU2NWI5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDE3ZjgyNjEwMTU0NTY1YjkwNTA5MTkwNTA1NjViNjEwMThmODE2MTAxNzQ1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDFhYTYwMDA4MzAxODQ2MTAxODY1NjViOTI5MTUwNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjBjMDAxMmU4ZmVkYjY5ZTBhZTgyNTEyZjg4NjA0MTFiNDM5MzM3MmQ4NTE2YzU4ZDg2ZDAzNmNiNDNhNTkxOGFiNjQ3MzZmNmM2MzQzMDAwODExMDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwMmU3NWVmZWE2YTA1OWNiZTZkOWRhNWQzMmI4NzVjNDU3Y2ExMzlmNzg2OGE5MzEzMjE3YzY2ZDQ1NzZmZWUxMDY0NzM2ZjZjNjM0MzAwMDgxMTAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwP6xsx2eZP5GcfkeXVslxaTElyI9A8+yAk8zv3EkAm+JzwxHh8B7lgt5fKTZOT6eZGgwIyvf/qwYQs6DB3wEiDwoJCI73/6sGEL4IEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiO9/+rBhDACBICGAISAhgDGIvbqJ0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRAoDGOQXGiISIEKSxeWpsh/9OPenSycNcQtwSb3fb4ro09V7MNOL8LF7IJChD0IFCIDO2gNSAFoAagpKVVNUIERPIElU","b64Record":"CiUIFiIDGOUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBxeF5e2jBAuRoWc2TJlx+WWgpjAL0dZA39DkXfMaQrfSip4lMMI55hLyLAJOYA6nwaDAjK9/+rBhCLxYnEAyIPCgkIjvf/qwYQwAgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQvoYCgMY5RcSxRZggGBAUmAENhBhAD9XYAA1YOAcgGOBhxy8FGEARFeAY5TKLLUUYQCBV4BjnEri0BRhAL5XgGOjTQYDFGEA2ldbYACA/Vs0gBVhAFBXYACA/VtQYQBrYASANgOBAZBhAGaRkGEC+lZbYQD2VltgQFFhAHiRkGEDylZbYEBRgJEDkPNbNIAVYQCNV2AAgP1bUGEAqGAEgDYDgQGQYQCjkZBhBSFWW2EBbFZbYEBRYQC1kZBhBYxWW2BAUYCRA5DzW2EA2GAEgDYDgQGQYQDTkZBhBSFWW2EBtlZbAFthAPRgBIA2A4EBkGEA75GQYQUhVlthAgxWWwBbYGBgAGBAUYBgIAFhAQqQYQJFVltgIIIBgQOCUmAfGWAfggEWYEBSUJBQgISEYEBRYCABYQE0kpGQYQW2VltgQFFgIIGDAwOBUpBgQFJgQFFgIAFhAVSSkZBhBhtWW2BAUWAggYMDA4FSkGBAUpFQUJKRUFBWW2AAgGD/YPgbMISGgFGQYCABIGBAUWAgAWEBkZSTkpGQYQcgVltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgkFCAYAAckVBQkpFQUFZbYACBg1FgIIUBNPWQUIA7YQHOV2AAgP1bf7A8U7KOeKiOMWB6J+H6SCNNzijV2dnseyla6wLmdKHhgYNgQFFhAf+SkZBhBbZWW2BAUYCRA5ChUFBQVltgAIBgAjRhAhyRkGEHnVZbkFCChFFgIIYBg/WRUIKEUWAghgGD9ZFQgTthAj9XYACA/VtQUFBQVlthA0GAYQfPgzkBkFZbYABgQFGQUJBWW2AAgP1bYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQKRgmECZlZbkFCRkFBWW2ECoYFhAoZWW4EUYQKsV2AAgP1bUFZbYACBNZBQYQK+gWECmFZbkpFQUFZbYACBkFCRkFBWW2EC14FhAsRWW4EUYQLiV2AAgP1bUFZbYACBNZBQYQL0gWECzlZbkpFQUFZbYACAYECDhQMSFWEDEVdhAxBhAlxWW1tgAGEDH4WChgFhAq9WW5JQUGAgYQMwhYKGAWEC5VZbkVBQklCSkFBWW2AAgVGQUJGQUFZbYACCglJgIIIBkFCSkVBQVltgAFuDgRAVYQN0V4CCAVGBhAFSYCCBAZBQYQNZVltgAISEAVJQUFBQVltgAGAfGWAfgwEWkFCRkFBWW2AAYQOcgmEDOlZbYQOmgYVhA0VWW5NQYQO2gYVgIIYBYQNWVlthA7+BYQOAVluEAZFQUJKRUFBWW2AAYCCCAZBQgYEDYACDAVJhA+SBhGEDkVZbkFCSkVBQVltgAID9W2AAgP1bf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VthBC6CYQOAVluBAYGBEGf//////////4IRFxVhBE1XYQRMYQP2VltbgGBAUlBQUFZbYABhBGBhAlJWW5BQYQRsgoJhBCVWW5GQUFZbYABn//////////+CERVhBIxXYQSLYQP2VltbYQSVgmEDgFZbkFBgIIEBkFCRkFBWW4KBgzdgAIODAVJQUFBWW2AAYQTEYQS/hGEEcVZbYQRWVluQUIKBUmAggQGEhIQBERVhBOBXYQTfYQPxVltbYQTrhIKFYQSiVltQk5JQUFBWW2AAgmAfgwESYQUIV2EFB2ED7FZbW4E1YQUYhIJgIIYBYQSxVluRUFCSkVBQVltgAIBgQIOFAxIVYQU4V2EFN2ECXFZbW2AAgwE1Z///////////gREVYQVWV2EFVWECYVZbW2EFYoWChgFhBPNWW5JQUGAgYQVzhYKGAWEC5VZbkVBQklCSkFBWW2EFhoFhAoZWW4JSUFBWW2AAYCCCAZBQYQWhYACDAYRhBX1WW5KRUFBWW2EFsIFhAsRWW4JSUFBWW2AAYECCAZBQYQXLYACDAYVhBX1WW2EF2GAggwGEYQWnVluTklBQUFZbYACBkFCSkVBQVltgAGEF9YJhAzpWW2EF/4GFYQXfVluTUGEGD4GFYCCGAWEDVlZbgIQBkVBQkpFQUFZbYABhBieChWEF6lZbkVBhBjOChGEF6lZbkVCBkFCTklBQUFZbYAB//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCFpBQkZBQVltgAIGQUJGQUFZbYQaGYQaBgmEGP1ZbYQZrVluCUlBQVltgAIFgYBuQUJGQUFZbYABhBqSCYQaMVluQUJGQUFZbYABhBraCYQaZVluQUJGQUFZbYQbOYQbJgmEChlZbYQarVluCUlBQVltgAIGQUJGQUFZbYQbvYQbqgmECxFZbYQbUVluCUlBQVltgAIGQUJGQUFZbYACBkFCRkFBWW2EHGmEHFYJhBvVWW2EG/1ZbglJQUFZbYABhByyCh2EGdVZbYAGCAZFQYQc8goZhBr1WW2AUggGRUGEHTIKFYQbeVltgIIIBkVBhB1yChGEHCVZbYCCCAZFQgZBQlZRQUFBQUFZbf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYBJgBFJgJGAA/VtgAGEHqIJhAsRWW5FQYQezg2ECxFZbklCCYQfDV2EHwmEHblZbW4KCBJBQkpFQUFb+YIBgQFJgQFFhA0E4A4BhA0GDOYGBAWBAUoEBkGEAJZGQYQEMVluBYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQgGABgZBVUFBQYQFMVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhAKOCYQB4VluQUJGQUFZbYQCzgWEAmFZbgRRhAL5XYACA/VtQVltgAIFRkFBhANCBYQCqVluSkVBQVltgAIGQUJGQUFZbYQDpgWEA1lZbgRRhAPRXYACA/VtQVltgAIFRkFBhAQaBYQDgVluSkVBQVltgAIBgQIOFAxIVYQEjV2EBImEAc1ZbW2AAYQExhYKGAWEAwVZbklBQYCBhAUKFgoYBYQD3VluRUFCSUJKQUFZbYQHmgGEBW2AAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQBMV2AANWDgHIBjEgZf4BRhAFFXgGNze8PJFGEAb1eAY42ly1sUYQB5V4BjwphVeBRhAJdXW2AAgP1bYQBZYQC1VltgQFFhAGaRkGEBOVZbYEBRgJEDkPNbYQB3YQC9VlsAW2EAgWEA9lZbYEBRYQCOkZBhAZVWW2BAUYCRA5DzW2EAn2EBGlZbYEBRYQCskZBhATlWW2BAUYCRA5DzW2AAR5BQkFZbYACAVJBhAQAKkARz//////////////////////////8Wc///////////////////////////Fv9bYACAVJBhAQAKkARz//////////////////////////8WgVZbYAFUgVZbYACBkFCRkFBWW2EBM4FhASBWW4JSUFBWW2AAYCCCAZBQYQFOYACDAYRhASpWW5KRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAX+CYQFUVluQUJGQUFZbYQGPgWEBdFZbglJQUFZbYABgIIIBkFBhAapgAIMBhGEBhlZbkpFQUFb+omRpcGZzWCISIMABLo/ttp4K6CUS+IYEEbQ5M3LYUWxY2G0DbLQ6WRirZHNvbGNDAAgRADOiZGlwZnNYIhIgLnXv6moFnL5tnaXTK4dcRXyhOfeGipMTIXxm1Fdv7hBkc29sY0MACBEAMyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMY5RdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAC+VyBwoDGOUXEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQiP9/+rBhDCCBICGAISAhgDGI/lrRYiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIAgxe52Tc78tRrdXlYzCd5++4qq7VC1yXC0QdpJItbpwEICU69wDSgUIgM7aA3AC","b64Record":"CiUIFhIDGOYXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDiwku1eu3u05tWJNQn6i4ukimIKd4UDx/6XTMDAgsLN6hsc1hQR0j0cwAqzsOcGVYaDAjL9/+rBhCLvYLpASIPCgkIj/f/qwYQwggSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjmFxCAqNa5Bw=="},{"b64Body":"Cg8KCQiP9/+rBhDECBICGAISAhgDGKWr3ugCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAS8KBnRva2VuQRIISUxCUERYR0sg6AcqAxjmF2oMCMvF2q8GEPiL+cYDkAEBmAGQTg==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGOcXEjCX11mysHKxTdvhNMXLV+2XsGqXBOv5CA01dX3WxYww/PsPk+0b4UXv9G78Iw0jR4QaDAjL9/+rBhDb3ejNAyIPCgkIj/f/qwYQxAgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWg8KAxjnFxIICgMY5hcQ0A9yCgoDGOcXEgMY5hc="},{"b64Body":"Cg8KCQiQ9/+rBhDGCBICGAISAhgDGKX2mvsCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAW4KBG5mdEESCFFXS1VNTlhTKgMY5hcyIhIgVWiAXsJ9RId8MJJLPiUH1dsJaD1bVRDM0GLTXhbeEchSIhIgVWiAXsJ9RId8MJJLPiUH1dsJaD1bVRDM0GLTXhbeEchqDAjMxdqvBhDIyubbAYgBAQ==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGOgXEjDLFfZZlaR7dX7rDtiCMXIJnER9RIkkWNBkVw2kIIeo1WFkEm3h8SaxSVHT/gfn1IoaDAjM9/+rBhDTjezyASIPCgkIkPf/qwYQxggSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAcgoKAxjoFxIDGOYX"},{"b64Body":"Cg8KCQiQ9/+rBhDMCBICGAISAhgDGP2NkRAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjqoCCwoDGOgXGgFhGgFi","b64Record":"CiYIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1gCcgIBAhIwnjk5RzZG48RIbFRVBoGzo9ENPp6Sno/S42s6e861CBz+ORxe+NN0k9+QEZoHVXOYGgwIzPf/qwYQk+/M1wMiDwoJCJD3/6sGEMwIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAFofCgMY6BcaCwoCGAASAxjmFxgBGgsKAhgAEgMY5hcYAg=="},{"b64Body":"ChEKCQiR9/+rBhDVCBICGAIgAVo6CgIyAEoFCIDO2gNqFGxhenktY3JlYXRlZCBhY2NvdW50cAKSARSnFkkWm9Yb1P5hqVK8DjaycHDc9g==","b64Record":"CgcIFhIDGOkXEjDSBO2JIDiD6phdRgh5BshELCOmhM38UmtMj02CxMBeq8jRT5RqgUe/IdjHW6FtQAQaDAjN9/+rBhCK2L38ASIRCgkIkff/qwYQ1QgSAhgCIAEqFGxhenktY3JlYXRlZCBhY2NvdW50UgA="},{"b64Body":"Cg8KCQiR9/+rBhDVCBICGAISAhgDGIDC1y8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnKoAQoqCh0KFiIUpxZJFpvWG9T+YalSvA42snBw3PYQgISvXwoJCgIYAhD/g69fEj8KAxjnFxIbChYiFAAAAAAAAAAAAAAAAAAAAAAAAAvmEOcHEhsKFiIUpxZJFpvWG9T+YalSvA42snBw3PYQ6AcSOQoDGOgXGjIKFiIUAAAAAAAAAAAAAAAAAAAAAAAAC+YSFiIUpxZJFpvWG9T+YalSvA42snBw3PYYAQ==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw68dhhmvptIBCO4qimdT6nFk1H4BiYc/yg39BBS1Nd+j/P10cF7ohf+p0V9th6WQ2GgwIzff/qwYQi9i9/AEiDwoJCJH3/6sGENUIEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SFwoJCgIYAhD/g69fCgoKAxjpFxCAhK9fWhkKAxjnFxIICgMY5hcQ5wcSCAoDGOkXEOgHWhMKAxjoFxoMCgMY5hcSAxjpFxgBcgoKAxjnFxIDGOkXcgoKAxjoFxIDGOkX"},{"b64Body":"Cg8KCQiS9/+rBhDfCBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjlFxCAkvQBGNIJIoQIo00GAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIISIDGOUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC7cYhCCYheLLvz/UP1lByAYuJ2R1K3kZsaimcPoOyjA8nUy+Z67n0ltZW2i730C/0aCwjO9/+rBhDLv9QEIg8KCQiS9/+rBhDfCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMJbEsYUBOgkaAjB4KLq88AFSGAoKCgIYAhCriOOKAgoKCgIYYhCsiOOKAg=="},{"b64Body":"Cg8KCQiS9/+rBhDhCBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAgKAxjlFxCAkvQBGNIJIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIFiIDGOUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBZ3uRKa7x/ZMfvqNiQ+C7+xOyiHsRt0lNOl9oJkBlL/jSYuKcO0B3yvguhrp3+l/AaDAjO9/+rBhD7hc2FAiIPCgkIkvf/qwYQ4QgSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAmKtsOo4FCgMY5RcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAEAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAKICowwEy7AIKAxjlFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAaILA8U7KOeKiOMWB6J+H6SCNNzijV2dnseyla6wLmdKHhIkAAAAAAAAAAAAAAAACnFkkWm9Yb1P5hqVK8DjaycHDc9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqcgcKAxjlFxACcgcKAxjpFxABUiIKCgoCGAIQo8PW2AEKCgoCGGIQgLDW2AEKCAoDGOkXEKQT"},{"b64Body":"ChEKCQiS9/+rBhDhCBICGAIgAUI3GiISIEKSxeWpsh/9OPenSycNcQtwSb3fb4ro09V7MNOL8LF7QgUIgM7aA2oKSlVTVCBETyBJVA==","b64Record":"CgcIFiIDGOkXEjA1TXsL495uCdBb+Lk0+QE93/ouH98gzqce1MHIuGal8Kg85YOFfdyIzUpCjtg0er0aDAjO9/+rBhD8hc2FAiIRCgkIkvf/qwYQ4QgSAhgCIAFCHQoDGOkXShYKFKcWSRab1hvU/mGpUrwONrJwcNz2UgB6DAjO9/+rBhD7hc2FAg=="},{"b64Body":"Cg8KCQiT9/+rBhDrCBICGAISAhgDGID+tYcBIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46kQgKAxjlFxCAkvQBIoQInEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgWCAYEBSYEBRYQNBOAOAYQNBgzmBgQFgQFKBAZBhACWRkGEBDFZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBQUGEBTFZbYACA/VtgAHP//////////////////////////4IWkFCRkFBWW2AAYQCjgmEAeFZbkFCRkFBWW2EAs4FhAJhWW4EUYQC+V2AAgP1bUFZbYACBUZBQYQDQgWEAqlZbkpFQUFZbYACBkFCRkFBWW2EA6YFhANZWW4EUYQD0V2AAgP1bUFZbYACBUZBQYQEGgWEA4FZbkpFQUFZbYACAYECDhQMSFWEBI1dhASJhAHNWW1tgAGEBMYWChgFhAMFWW5JQUGAgYQFChYKGAWEA91ZbkVBQklCSkFBWW2EB5oBhAVtgADlgAPP+YIBgQFI0gBVhABBXYACA/VtQYAQ2EGEATFdgADVg4ByAYxIGX+AUYQBRV4Bjc3vDyRRhAG9XgGONpctbFGEAeVeAY8KYVXgUYQCXV1tgAID9W2EAWWEAtVZbYEBRYQBmkZBhATlWW2BAUYCRA5DzW2EAd2EAvVZbAFthAIFhAPZWW2BAUWEAjpGQYQGVVltgQFGAkQOQ81thAJ9hARpWW2BAUWEArJGQYQE5VltgQFGAkQOQ81tgAEeQUJBWW2AAgFSQYQEACpAEc///////////////////////////FnP//////////////////////////xb/W2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAgZBQkZBQVlthATOBYQEgVluCUlBQVltgAGAgggGQUGEBTmAAgwGEYQEqVluSkVBQVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQF/gmEBVFZbkFCRkFBWW2EBj4FhAXRWW4JSUFBWW2AAYCCCAZBQYQGqYACDAYRhAYZWW5KRUFBW/qJkaXBmc1giEiDAAS6P7baeCuglEviGBBG0OTNy2FFsWNhtA2y0OlkYq2Rzb2xjQwAIEQAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC+UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","b64Record":"CiUIISIDGOUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCySFg7iKVK+8q3AE5rFqX6nBR1/+jam+P73gvCs1jGnFTPNl3jym7rJD+3Tzyg5e4aCwjP9/+rBhDLoJYqIg8KCQiT9/+rBhDrCBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMelrIUBOgkaAjB4KIGz8AFSGAoKCgIYAhCNy9iKAgoKCgIYYhCOy9iKAg=="}]},"CanMergeCreate2MultipleCreatesWithHollowAccount":{"placeholderNum":3051,"encodedItems":[{"b64Body":"Cg8KCQiX9/+rBhCJCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAjTxdqvBhDI2+nmARptCiISILCXhBJh4sIgC/EgafbEH3LReFmWSEEU2cmnhpqKJ3hqCiM6IQKht5L/XGbBcT51Axxx+QdfZmAc5jIq0Ac2x6yc5fkBJgoiEiCjR4CTrzoUQayYjyByv1LeGuOgAaCfCmKPUF3iaHCGgyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGOwXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBxiv43KtW5fg18SaAojmzzr9NenhCjt55Wfu7tp6Pus8TAmvkYwgr3g2PF9pedg7oaDAjT9/+rBhDTppz8ASIPCgkIl/f/qwYQiQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiX9/+rBhCNCRICGAISAhgDGIydjj4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBiCAKAxjsFyKAIDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMGQyNzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MjAwMDAzODU3NjAwMDM1NjBlMDFjODA2MzgxODcxY2JjMTQ2MjAwMDAzZDU3ODA2Mzk0Y2EyY2I1MTQ2MjAwMDA4MTU3ODA2MzljNGFlMmQwMTQ2MjAwMDBjNTU3NWI2MDAwODBmZDViMzQ4MDE1NjIwMDAwNGE1NzYwMDA4MGZkNWI1MDYyMDAwMDY5NjAwNDgwMzYwMzgxMDE5MDYyMDAwMDYzOTE5MDYyMDAwMmM4NTY1YjYyMDAwMGU1NTY1YjYwNDA1MTYyMDAwMDc4OTE5MDYyMDAwM2IzNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYyMDAwMDhlNTc2MDAwODBmZDViNTA2MjAwMDBhZDYwMDQ4MDM2MDM4MTAxOTA2MjAwMDBhNzkxOTA2MjAwMDUyODU2NWI2MjAwMDE2MTU2NWI2MDQwNTE2MjAwMDBiYzkxOTA2MjAwMDU5ZjU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MjAwMDBlMzYwMDQ4MDM2MDM4MTAxOTA2MjAwMDBkZDkxOTA2MjAwMDUyODU2NWI2MjAwMDFhZDU2NWIwMDViNjA2MDYwMDA2MDQwNTE4MDYwMjAwMTYyMDAwMGZiOTA2MjAwMDIwNjU2NWI2MDIwODIwMTgxMDM4MjUyNjAxZjE5NjAxZjgyMDExNjYwNDA1MjUwOTA1MDgwODQ4NDYwNDA1MTYwMjAwMTYyMDAwMTI3OTI5MTkwNjIwMDA1Y2Q1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyNjA0MDUxNjAyMDAxNjIwMDAxNDk5MjkxOTA2MjAwMDYzYzU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwODA2MGZmNjBmODFiMzA4NDg2ODA1MTkwNjAyMDAxMjA2MDQwNTE2MDIwMDE2MjAwMDE4ODk0OTM5MjkxOTA2MjAwMDc1OTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDYwMDAxYzkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MTgzNTE2MDIwODUwMTM0ZjU5MDUwODAzYjYyMDAwMWM2NTc2MDAwODBmZDViN2ZiMDNjNTNiMjhlNzhhODhlMzE2MDdhMjdlMWZhNDgyMzRkY2UyOGQ1ZDlkOWVjN2IyOTVhZWIwMmU2NzRhMWUxODE4MzYwNDA1MTYyMDAwMWY5OTI5MTkwNjIwMDA1Y2Q1NjViNjA0MDUxODA5MTAzOTBhMTUwNTA1MDU2NWI2MTA1NDI4MDYyMDAwN2IwODMzOTAxOTA1NjViNjAwMDYwNDA1MTkwNTA5MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjIwMDAyNTU4MjYyMDAwMjI4NTY1YjkwNTA5MTkwNTA1NjViNjIwMDAyNjc4MTYyMDAwMjQ4NTY1YjgxMTQ2MjAwMDI3MzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTM1OTA1MDYyMDAwMjg3ODE2MjAwMDI1YzU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYyMDAwMmEyODE2MjAwMDI4ZDU2NWI4MTE0NjIwMDAyYWU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MjAwMDJjMjgxNjIwMDAyOTc1NjViOTI5MTUwNTA1NjViNjAwMDgwNjA0MDgzODUwMzEyMTU2MjAwMDJlMjU3NjIwMDAyZTE2MjAwMDIxZTU2NWI1YjYwMDA2MjAwMDJmMjg1ODI4NjAxNjIwMDAyNzY1NjViOTI1MDUwNjAyMDYyMDAwMzA1ODU4Mjg2MDE2MjAwMDJiMTU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYwMDA4MTUxOTA1MDkxOTA1MDU2NWI2MDAwODI4MjUyNjAyMDgyMDE5MDUwOTI5MTUwNTA1NjViNjAwMDViODM4MTEwMTU2MjAwMDM0YjU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYyMDAwMzJlNTY1YjgzODExMTE1NjIwMDAzNWI1NzYwMDA4NDg0MDE1MjViNTA1MDUwNTA1NjViNjAwMDYwMWYxOTYwMWY4MzAxMTY5MDUwOTE5MDUwNTY1YjYwMDA2MjAwMDM3ZjgyNjIwMDAzMGY1NjViNjIwMDAzOGI4MTg1NjIwMDAzMWE1NjViOTM1MDYyMDAwMzlkODE4NTYwMjA4NjAxNjIwMDAzMmI1NjViNjIwMDAzYTg4MTYyMDAwMzYxNTY1Yjg0MDE5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwODE4MTAzNjAwMDgzMDE1MjYyMDAwM2NmODE4NDYyMDAwMzcyNTY1YjkwNTA5MjkxNTA1MDU2NWI2MDAwODBmZDViNjAwMDgwZmQ1YjdmNGU0ODdiNzEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDYwMDA1MjYwNDE2MDA0NTI2MDI0NjAwMGZkNWI2MjAwMDQxYjgyNjIwMDAzNjE1NjViODEwMTgxODExMDY3ZmZmZmZmZmZmZmZmZmZmZjgyMTExNzE1NjIwMDA0M2Q1NzYyMDAwNDNjNjIwMDAzZTE1NjViNWI4MDYwNDA1MjUwNTA1MDU2NWI2MDAwNjIwMDA0NTI2MjAwMDIxNDU2NWI5MDUwNjIwMDA0NjA4MjgyNjIwMDA0MTA1NjViOTE5MDUwNTY1YjYwMDA2N2ZmZmZmZmZmZmZmZmZmZmY4MjExMTU2MjAwMDQ4MzU3NjIwMDA0ODI2MjAwMDNlMTU2NWI1YjYyMDAwNDhlODI2MjAwMDM2MTU2NWI5MDUwNjAyMDgxMDE5MDUwOTE5MDUwNTY1YjgyODE4MzM3NjAwMDgzODMwMTUyNTA1MDUwNTY1YjYwMDA2MjAwMDRjMTYyMDAwNGJiODQ2MjAwMDQ2NTU2NWI2MjAwMDQ0NjU2NWI5MDUwODI4MTUyNjAyMDgxMDE4NDg0ODQwMTExMTU2MjAwMDRlMDU3NjIwMDA0ZGY2MjAwMDNkYzU2NWI1YjYyMDAwNGVkODQ4Mjg1NjIwMDA0OWI1NjViNTA5MzkyNTA1MDUwNTY1YjYwMDA4MjYwMWY4MzAxMTI2MjAwMDUwZDU3NjIwMDA1MGM2MjAwMDNkNzU2NWI1YjgxMzU2MjAwMDUxZjg0ODI2MDIwODYwMTYyMDAwNGFhNTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjIwMDA1NDI1NzYyMDAwNTQxNjIwMDAyMWU1NjViNWI2MDAwODMwMTM1NjdmZmZmZmZmZmZmZmZmZmZmODExMTE1NjIwMDA1NjM1NzYyMDAwNTYyNjIwMDAyMjM1NjViNWI2MjAwMDU3MTg1ODI4NjAxNjIwMDA0ZjU1NjViOTI1MDUwNjAyMDYyMDAwNTg0ODU4Mjg2MDE2MjAwMDJiMTU2NWI5MTUwNTA5MjUwOTI5MDUwNTY1YjYyMDAwNTk5ODE2MjAwMDI0ODU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjIwMDA1YjY2MDAwODMwMTg0NjIwMDA1OGU1NjViOTI5MTUwNTA1NjViNjIwMDA1Yzc4MTYyMDAwMjhkNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDQwODIwMTkwNTA2MjAwMDVlNDYwMDA4MzAxODU2MjAwMDU4ZTU2NWI2MjAwMDVmMzYwMjA4MzAxODQ2MjAwMDViYzU2NWI5MzkyNTA1MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNjIwMDA2MTI4MjYyMDAwMzBmNTY1YjYyMDAwNjFlODE4NTYyMDAwNWZhNTY1YjkzNTA2MjAwMDYzMDgxODU2MDIwODYwMTYyMDAwMzJiNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MjAwMDY0YTgyODU2MjAwMDYwNTU2NWI5MTUwNjIwMDA2NTg4Mjg0NjIwMDA2MDU1NjViOTE1MDgxOTA1MDkzOTI1MDUwNTA1NjViNjAwMDdmZmYwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgyMTY5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjIwMDA2YWY2MjAwMDZhOTgyNjIwMDA2NjQ1NjViNjIwMDA2OTA1NjViODI1MjUwNTA1NjViNjAwMDgxNjA2MDFiOTA1MDkxOTA1MDU2NWI2MDAwNjIwMDA2Y2Y4MjYyMDAwNmI1NTY1YjkwNTA5MTkwNTA1NjViNjAwMDYyMDAwNmUzODI2MjAwMDZjMjU2NWI5MDUwOTE5MDUwNTY1YjYyMDAwNmZmNjIwMDA2Zjk4MjYyMDAwMjQ4NTY1YjYyMDAwNmQ2NTY1YjgyNTI1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjIwMDA3MjQ2MjAwMDcxZTgyNjIwMDAyOGQ1NjViNjIwMDA3MDU1NjViODI1MjUwNTA1NjViNjAwMDgxOTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYyMDAwNzUzNjIwMDA3NGQ4MjYyMDAwNzJhNTY1YjYyMDAwNzM0NTY1YjgyNTI1MDUwNTY1YjYwMDA2MjAwMDc2NzgyODc2MjAwMDY5YTU2NWI2MDAxODIwMTkxNTA2MjAwMDc3OTgyODY2MjAwMDZlYTU2NWI2MDE0ODIwMTkxNTA2MjAwMDc4YjgyODU2MjAwMDcwZjU2NWI2MDIwODIwMTkxNTA2MjAwMDc5ZDgyODQ2MjAwMDczZTU2NWI2MDIwODIwMTkxNTA4MTkwNTA5NTk0NTA1MDUwNTA1MDU2ZmU2MDgwNjA0MDUyNjA0MDUxNjEwNTQyMzgwMzgwNjEwNTQyODMzOTgxODEwMTYwNDA1MjgxMDE5MDYxMDAyNTkxOTA2MTAyMmE1NjViODE2MDAwODA2MTAxMDAwYTgxNTQ=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwdxbR8L/T/bwl/TMXJuRfszVbaHnFYrB+P6WZeb35xlKeYIC/6ik6MqxL0CXfG3KMGgsI1Pf/qwYQk7z6AyIPCgkIl/f/qwYQjQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiY9/+rBhCTCRICGAISAhgDGIz62zgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjoIBlhUSAxjsFyKOFTgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDgwNjAwMTgxOTA1NTUwNjAwMDYwNDA1MTYxMDA3YTkwNjEwMTg1NTY1YjYxMDA4NDkxOTA2MTAyODk1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDBhMDU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDI2MDAwNjAwMjgxMTA2MTAwYjY1NzYxMDBiNTYxMDJhNDU2NWI1YjAxNjAwMDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDYwMDE2MDQwNTE2MTAxMDM5MDYxMDE4NTU2NWI2MTAxMGQ5MTkwNjEwMjg5NTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxMjk1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA2MDAyNjAwMTYwMDI4MTEwNjEwMTNmNTc2MTAxM2U2MTAyYTQ1NjViNWIwMTYwMDA2MTAxMDAwYTgxNTQ4MTczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjAyMTkxNjkwODM3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjAyMTc5MDU1NTA1MDUwNjEwMmQzNTY1YjYwZjk4MDYxMDQ0OTgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDFjMTgyNjEwMTk2NTY1YjkwNTA5MTkwNTA1NjViNjEwMWQxODE2MTAxYjY1NjViODExNDYxMDFkYzU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDFlZTgxNjEwMWM4NTY1YjkyOTE1MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwMjA3ODE2MTAxZjQ1NjViODExNDYxMDIxMjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDIyNDgxNjEwMWZlNTY1YjkyOTE1MDUwNTY1YjYwMDA4MDYwNDA4Mzg1MDMxMjE1NjEwMjQxNTc2MTAyNDA2MTAxOTE1NjViNWI2MDAwNjEwMjRmODU4Mjg2MDE2MTAxZGY1NjViOTI1MDUwNjAyMDYxMDI2MDg1ODI4NjAxNjEwMjE1NTY1YjkxNTA1MDkyNTA5MjkwNTA1NjViNjAwMDYzZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MTAyODM4MTYxMDI2YTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMjllNjAwMDgzMDE4NDYxMDI3YTU2NWI5MjkxNTA1MDU2NWI3ZjRlNDg3YjcxMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwNTI2MDMyNjAwNDUyNjAyNDYwMDBmZDViNjEwMTY3ODA2MTAyZTI2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDQzNjEwNjEwMDM2NTc2MDAwMzU2MGUwMWM4MDYzOGRhNWNiNWIxNDYxMDAzYjU3ODA2M2MyOTg1NTc4MTQ2MTAwNTk1NzViNjAwMDgwZmQ1YjYxMDA0MzYxMDA3NzU2NWI2MDQwNTE2MTAwNTA5MTkwNjEwMGUyNTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYxMDA2MTYxMDA5YjU2NWI2MDQwNTE2MTAwNmU5MTkwNjEwMTE2NTY1YjYwNDA1MTgwOTEwMzkwZjM1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTY1YjYwMDE1NDgxNTY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMGNjODI2MTAwYTE1NjViOTA1MDkxOTA1MDU2NWI2MTAwZGM4MTYxMDBjMTU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMGY3NjAwMDgzMDE4NDYxMDBkMzU2NWI5MjkxNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDExMDgxNjEwMGZkNTY1YjgyNTI1MDUwNTY1YjYwMDA2MDIwODIwMTkwNTA2MTAxMmI2MDAwODMwMTg0NjEwMTA3NTY1YjkyOTE1MDUwNTZmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwZTczYjM4ZTQyNzA4YWIyNGYyN2YwNDFjM2IwOWViMzg4NGM3MjY4ZTU4NjYyNzM0ZTY5MzhlYWEyNWFlNmMyMTY0NzM2ZjZjNjM0MzAwMDgwYTAwMzM2MDgwNjA0MDUyNjA0MDUxNjEwMGY5MzgwMzgwNjEwMGY5ODMzOTgxODEwMTYwNDA1MjgxMDE5MDYwMjM5MTkwNjA4NDU2NWI4MDYwMDA4MDYxMDEwMDBhODE1NDgxNjNmZmZmZmZmZjAyMTkxNjkwODM2M2ZmZmZmZmZmMTYwMjE3OTA1NTUwNTA2MGFjNTY1YjYwMDA4MGZkNWI2MDAwNjNmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwNjQ4MTYwNGQ1NjViODExNDYwNmU1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODE1MTkwNTA2MDdlODE2MDVkNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYwOTc1NzYwOTY2MDQ4NTY1YjViNjAwMDYwYTM4NDgyODUwMTYwNzE1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAzZjgwNjEwMGJhNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwMDgwZmRmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwZDdjYjkyNmQwNjA5YjNlOTY2YmRlODFhNTc1ODEwOTA0NzVmZGQxZTg4NmQ4MjgyNTdkZGY5MWMzZjcyMmI4YTY0NzM2ZjZjNjM0MzAwMDgwYTAwMzNhMjY0Njk3MDY2NzM1ODIyMTIyMDM0ZTkxNGUzZmJhZDc5ZWY4OTg0ODQ0ZjhkYmUxODA2ZmVjNTA4NDQ3MGIyYjM2NjU4MTMyOWI1MjQ3YWZjNDM2NDczNmY2YzYzNDMwMDA4MGEwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwnUW7/66GeUAhRg5CLKuBH8ZfmYXe+Xwu3XbEsKciWdb4IIwvAe48R32ZciEtOkdGGgwI1Pf/qwYQm9u7hQIiDwoJCJj3/6sGEJMJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQiZ9/+rBhCVCRICGAISAhgDGIvbqJ0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGOwXGiISIHUCm+V9zHOBbv0d2Y2P8y52zbmOWkDMTrsjh2ktmTwvIICt4gRCBQiAztoDUgBaAGoKSlVTVCBETyBJVA==","b64Record":"CiUIFiIDGO0XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBUvXiLJMPPT9zTCib0bIz13zATuCVcWTH4+czdv7M8dOx0zojAd3sElkEmAIFKHXEaCwjV9/+rBhCrl7wNIg8KCQiZ9/+rBhCVCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMID8644CQt0cCgMY7RcSpxpggGBAUmAENhBiAAA4V2AANWDgHIBjgYccvBRiAAA9V4BjlMostRRiAACBV4BjnEri0BRiAADFV1tgAID9WzSAFWIAAEpXYACA/VtQYgAAaWAEgDYDgQGQYgAAY5GQYgACyFZbYgAA5VZbYEBRYgAAeJGQYgADs1ZbYEBRgJEDkPNbNIAVYgAAjldgAID9W1BiAACtYASANgOBAZBiAACnkZBiAAUoVltiAAFhVltgQFFiAAC8kZBiAAWfVltgQFGAkQOQ81tiAADjYASANgOBAZBiAADdkZBiAAUoVltiAAGtVlsAW2BgYABgQFGAYCABYgAA+5BiAAIGVltgIIIBgQOCUmAfGWAfggEWYEBSUJBQgISEYEBRYCABYgABJ5KRkGIABc1WW2BAUWAggYMDA4FSkGBAUmBAUWAgAWIAAUmSkZBiAAY8VltgQFFgIIGDAwOBUpBgQFKRUFCSkVBQVltgAIBg/2D4GzCEhoBRkGAgASBgQFFgIAFiAAGIlJOSkZBiAAdZVltgQFFgIIGDAwOBUpBgQFKAUZBgIAEgkFCAYAAckVBQkpFQUFZbYACBg1FgIIUBNPWQUIA7YgABxldgAID9W3+wPFOyjniojjFgeifh+kgjTc4o1dnZ7HspWusC5nSh4YGDYEBRYgAB+ZKRkGIABc1WW2BAUYCRA5ChUFBQVlthBUKAYgAHsIM5AZBWW2AAYEBRkFCQVltgAID9W2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGIAAlWCYgACKFZbkFCRkFBWW2IAAmeBYgACSFZbgRRiAAJzV2AAgP1bUFZbYACBNZBQYgACh4FiAAJcVluSkVBQVltgAIGQUJGQUFZbYgACooFiAAKNVluBFGIAAq5XYACA/VtQVltgAIE1kFBiAALCgWIAApdWW5KRUFBWW2AAgGBAg4UDEhViAALiV2IAAuFiAAIeVltbYABiAALyhYKGAWIAAnZWW5JQUGAgYgADBYWChgFiAAKxVluRUFCSUJKQUFZbYACBUZBQkZBQVltgAIKCUmAgggGQUJKRUFBWW2AAW4OBEBViAANLV4CCAVGBhAFSYCCBAZBQYgADLlZbg4ERFWIAA1tXYACEhAFSW1BQUFBWW2AAYB8ZYB+DARaQUJGQUFZbYABiAAN/gmIAAw9WW2IAA4uBhWIAAxpWW5NQYgADnYGFYCCGAWIAAytWW2IAA6iBYgADYVZbhAGRUFCSkVBQVltgAGAgggGQUIGBA2AAgwFSYgADz4GEYgADclZbkFCSkVBQVltgAID9W2AAgP1bf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYEFgBFJgJGAA/VtiAAQbgmIAA2FWW4EBgYEQZ///////////ghEXFWIABD1XYgAEPGIAA+FWW1uAYEBSUFBQVltgAGIABFJiAAIUVluQUGIABGCCgmIABBBWW5GQUFZbYABn//////////+CERViAASDV2IABIJiAAPhVltbYgAEjoJiAANhVluQUGAggQGQUJGQUFZbgoGDN2AAg4MBUlBQUFZbYABiAATBYgAEu4RiAARlVltiAARGVluQUIKBUmAggQGEhIQBERViAATgV2IABN9iAAPcVltbYgAE7YSChWIABJtWW1CTklBQUFZbYACCYB+DARJiAAUNV2IABQxiAAPXVltbgTViAAUfhIJgIIYBYgAEqlZbkVBQkpFQUFZbYACAYECDhQMSFWIABUJXYgAFQWIAAh5WW1tgAIMBNWf//////////4ERFWIABWNXYgAFYmIAAiNWW1tiAAVxhYKGAWIABPVWW5JQUGAgYgAFhIWChgFiAAKxVluRUFCSUJKQUFZbYgAFmYFiAAJIVluCUlBQVltgAGAgggGQUGIABbZgAIMBhGIABY5WW5KRUFBWW2IABceBYgACjVZbglJQUFZbYABgQIIBkFBiAAXkYACDAYViAAWOVltiAAXzYCCDAYRiAAW8VluTklBQUFZbYACBkFCSkVBQVltgAGIABhKCYgADD1ZbYgAGHoGFYgAF+lZbk1BiAAYwgYVgIIYBYgADK1ZbgIQBkVBQkpFQUFZbYABiAAZKgoViAAYFVluRUGIABliChGIABgVWW5FQgZBQk5JQUFBWW2AAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAghaQUJGQUFZbYACBkFCRkFBWW2IABq9iAAapgmIABmRWW2IABpBWW4JSUFBWW2AAgWBgG5BQkZBQVltgAGIABs+CYgAGtVZbkFCRkFBWW2AAYgAG44JiAAbCVluQUJGQUFZbYgAG/2IABvmCYgACSFZbYgAG1lZbglJQUFZbYACBkFCRkFBWW2IAByRiAAcegmIAAo1WW2IABwVWW4JSUFBWW2AAgZBQkZBQVltgAIGQUJGQUFZbYgAHU2IAB02CYgAHKlZbYgAHNFZbglJQUFZbYABiAAdngodiAAaaVltgAYIBkVBiAAd5goZiAAbqVltgFIIBkVBiAAeLgoViAAcPVltgIIIBkVBiAAedgoRiAAc+VltgIIIBkVCBkFCVlFBQUFBQVv5ggGBAUmBAUWEFQjgDgGEFQoM5gYEBYEBSgQGQYQAlkZBhAipWW4FgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVCAYAGBkFVQYABgQFFhAHqQYQGFVlthAISRkGECiVZbYEBRgJEDkGAA8IAVgBVhAKBXPWAAgD49YAD9W1BgAmAAYAKBEGEAtldhALVhAqRWW1sBYABhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVBgAWBAUWEBA5BhAYVWW2EBDZGQYQKJVltgQFGAkQOQYADwgBWAFWEBKVc9YACAPj1gAP1bUGACYAFgAoEQYQE/V2EBPmECpFZbWwFgAGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUFBQYQLTVltg+YBhBEmDOQGQVltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhAcGCYQGWVluQUJGQUFZbYQHRgWEBtlZbgRRhAdxXYACA/VtQVltgAIFRkFBhAe6BYQHIVluSkVBQVltgAIGQUJGQUFZbYQIHgWEB9FZbgRRhAhJXYACA/VtQVltgAIFRkFBhAiSBYQH+VluSkVBQVltgAIBgQIOFAxIVYQJBV2ECQGEBkVZbW2AAYQJPhYKGAWEB31ZbklBQYCBhAmCFgoYBYQIVVluRUFCSUJKQUFZbYABj/////4IWkFCRkFBWW2ECg4FhAmpWW4JSUFBWW2AAYCCCAZBQYQKeYACDAYRhAnpWW5KRUFBWW39OSHtxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAUmAyYARSYCRgAP1bYQFngGEC4mAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQA2V2AANWDgHIBjjaXLWxRhADtXgGPCmFV4FGEAWVdbYACA/VthAENhAHdWW2BAUWEAUJGQYQDiVltgQFGAkQOQ81thAGFhAJtWW2BAUWEAbpGQYQEWVltgQFGAkQOQ81tgAIBUkGEBAAqQBHP//////////////////////////xaBVltgAVSBVltgAHP//////////////////////////4IWkFCRkFBWW2AAYQDMgmEAoVZbkFCRkFBWW2EA3IFhAMFWW4JSUFBWW2AAYCCCAZBQYQD3YACDAYRhANNWW5KRUFBWW2AAgZBQkZBQVlthARCBYQD9VluCUlBQVltgAGAgggGQUGEBK2AAgwGEYQEHVluSkVBQVv6iZGlwZnNYIhIg5zs45CcIqyTyfwQcOwnrOITHJo5YZic05pOOqiWubCFkc29sY0MACAoAM2CAYEBSYEBRYQD5OAOAYQD5gzmBgQFgQFKBAZBgI5GQYIRWW4BgAIBhAQAKgVSBY/////8CGRaQg2P/////FgIXkFVQUGCsVltgAID9W2AAY/////+CFpBQkZBQVltgZIFgTVZbgRRgbldgAID9W1BWW2AAgVGQUGB+gWBdVluSkVBQVltgAGAggoQDEhVgl1dglmBIVltbYABgo4SChQFgcVZbkVBQkpFQUFZbYD+AYQC6YAA5YADz/mCAYEBSYACA/f6iZGlwZnNYIhIg18uSbQYJs+lmvegaV1gQkEdf3R6IbYKCV935HD9yK4pkc29sY0MACAoAM6JkaXBmc1giEiA06RTj+61574mEhE+NvhgG/sUIRHCys2ZYEym1JHr8Q2Rzb2xjQwAICgAzIoACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACiApOgDOgMY7RdKFgoUAAAAAAAAAAAAAAAAAAAAAAAAC+1yBwoDGO0XEAFSGAoKCgIYAhD/99edBAoKCgIYYhCA+NedBA=="},{"b64Body":"Cg8KCQiZ9/+rBhCXCRICGAISAhgDGI/lrRYiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlozCiISIKzT3ccCfMvI4w1vYZE0j59Lwug+5in3ShRonp935agyEICU69wDSgUIgM7aA3AC","b64Record":"CiUIFhIDGO4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAFHT3l+cxOEzlYTebBmrsbu6cYC9b7VaS9YFaiAjvJMG2qnw01EVF1jjl5kmcbolIaDAjV9/+rBhDb9v+OAiIPCgkImff/qwYQlwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxjuFxCAqNa5Bw=="},{"b64Body":"Cg8KCQia9/+rBhCZCRICGAISAhgDGKWr3ugCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAS4KBnRva2VuQRIIUVpRUkpMTFIg6AcqAxjuF2oLCNbF2q8GENiD+QeQAQGYAZBO","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGO8XEjAfutKfNaXDSHbhJrwXc1O/NI2ypmNGDRI1Ef8Gl3w+6z8UOoHK0cUwR+NRsvBeo3UaCwjW9/+rBhCLz4EXIg8KCQia9/+rBhCZCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgBaDwoDGO8XEggKAxjuFxDQD3IKCgMY7xcSAxjuFw=="},{"b64Body":"Cg8KCQia9/+rBhCbCRICGAISAhgDGKX2mvsCIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo7qAW4KBG5mdEESCFJIWUtCQkZXKgMY7hcyIhIgNdJfvJINNRWZretQMgLvgASC1HxfPQK9s3hjWjhR+dBSIhIgNdJfvJINNRWZretQMgLvgASC1HxfPQK9s3hjWjhR+dBqDAjWxdqvBhDo1en4AYgBAQ==","b64Record":"CiUIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1IDGPAXEjD7u8N0gQkVBCiBGa+XQL7xQWPuJzsZuHDd/vBhrVETCYaO5I2Ydemmqy83gRa9gcoaDAjW9/+rBhDzjpCUAiIPCgkImvf/qwYQmwkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAcgoKAxjwFxIDGO4X"},{"b64Body":"Cg8KCQib9/+rBhChCRICGAISAhgDGP2NkRAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjqoCCwoDGPAXGgFhGgFi","b64Record":"CiYIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkD1gCcgIBAhIwSbhs++DpWK9Xo7Z8xGRUs7afBXJHJXwOXEUsZKBYlep8hWa+146C/R52ZanJ0v5yGgsI1/f/qwYQ4+T9GyIPCgkIm/f/qwYQoQkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIAWh8KAxjwFxoLCgIYABIDGO4XGAEaCwoCGAASAxjuFxgC"},{"b64Body":"ChEKCQib9/+rBhCqCRICGAIgAVo6CgIyAEoFCIDO2gNqFGxhenktY3JlYXRlZCBhY2NvdW50cAKSARSxJWiFNL5xhLfO4HFZlAXkvb4ACA==","b64Record":"CgcIFhIDGPEXEjD5THJY7dkCH2u9WHC/qSTtJz5FFHNSJq2Zin4AJ1gnJ7EX83PaaViOtmiFFf/RipYaDAjX9/+rBhDSh+qdAiIRCgkIm/f/qwYQqgkSAhgCIAEqFGxhenktY3JlYXRlZCBhY2NvdW50UgA="},{"b64Body":"Cg8KCQib9/+rBhCqCRICGAISAhgDGIDC1y8iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjnKoAQoqCh0KFiIUsSVohTS+cYS3zuBxWZQF5L2+AAgQgISvXwoJCgIYAhD/g69fEj8KAxjvFxIbChYiFAAAAAAAAAAAAAAAAAAAAAAAAAvuEOcHEhsKFiIUsSVohTS+cYS3zuBxWZQF5L2+AAgQ6AcSOQoDGPAXGjIKFiIUAAAAAAAAAAAAAAAAAAAAAAAAC+4SFiIUsSVohTS+cYS3zuBxWZQF5L2+AAgYAQ==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwN5F+1ZKyj0udFTVmXfx8XQ+KXyoXzpEfe/LApakVeM26k+nljjomgIf/LZv3HRgwGgwI1/f/qwYQ04fqnQIiDwoJCJv3/6sGEKoJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SFwoJCgIYAhD/g69fCgoKAxjxFxCAhK9fWhkKAxjvFxIICgMY7hcQ5wcSCAoDGPEXEOgHWhMKAxjwFxoMCgMY7hcSAxjxFxgBcgoKAxjvFxIDGPEXcgoKAxjwFxIDGPEX"},{"b64Body":"Cg8KCQic9/+rBhC0CRICGAISAhgDGID7xtICIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo46lAwKAxjtFxCAreIEGNIJIoQMnEri0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgmCAYEBSYEBRYQVCOAOAYQVCgzmBgQFgQFKBAZBhACWRkGECKlZbgWAAgGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUIBgAYGQVVBgAGBAUWEAepBhAYVWW2EAhJGQYQKJVltgQFGAkQOQYADwgBWAFWEAoFc9YACAPj1gAP1bUGACYABgAoEQYQC2V2EAtWECpFZbWwFgAGEBAAqBVIFz//////////////////////////8CGRaQg3P//////////////////////////xYCF5BVUGABYEBRYQEDkGEBhVZbYQENkZBhAolWW2BAUYCRA5BgAPCAFYAVYQEpVz1gAIA+PWAA/VtQYAJgAWACgRBhAT9XYQE+YQKkVltbAWAAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQUFBhAtNWW2D5gGEESYM5AZBWW2AAgP1bYABz//////////////////////////+CFpBQkZBQVltgAGEBwYJhAZZWW5BQkZBQVlthAdGBYQG2VluBFGEB3FdgAID9W1BWW2AAgVGQUGEB7oFhAchWW5KRUFBWW2AAgZBQkZBQVlthAgeBYQH0VluBFGECEldgAID9W1BWW2AAgVGQUGECJIFhAf5WW5KRUFBWW2AAgGBAg4UDEhVhAkFXYQJAYQGRVltbYABhAk+FgoYBYQHfVluSUFBgIGECYIWChgFhAhVWW5FQUJJQkpBQVltgAGP/////ghaQUJGQUFZbYQKDgWECalZbglJQUFZbYABgIIIBkFBhAp5gAIMBhGECelZbkpFQUFZbf05Ie3EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYABSYDJgBFJgJGAA/VthAWeAYQLiYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhADZXYAA1YOAcgGONpctbFGEAO1eAY8KYVXgUYQBZV1tgAID9W2EAQ2EAd1ZbYEBRYQBQkZBhAOJWW2BAUYCRA5DzW2EAYWEAm1ZbYEBRYQBukZBhARZWW2BAUYCRA5DzW2AAgFSQYQEACpAEc///////////////////////////FoFWW2ABVIFWW2AAc///////////////////////////ghaQUJGQUFZbYABhAMyCYQChVluQUJGQUFZbYQDcgWEAwVZbglJQUFZbYABgIIIBkFBhAPdgAIMBhGEA01ZbkpFQUFZbYACBkFCRkFBWW2EBEIFhAP1WW4JSUFBWW2AAYCCCAZBQYQErYACDAYRhAQdWW5KRUFBW/qJkaXBmc1giEiDnOzjkJwirJPJ/BBw7Ces4hMcmjlhmJzTmk46qJa5sIWRzb2xjQwAICgAzYIBgQFJgQFFhAPk4A4BhAPmDOYGBAWBAUoEBkGAjkZBghFZbgGAAgGEBAAqBVIFj/////wIZFpCDY/////8WAheQVVBQYKxWW2AAgP1bYABj/////4IWkFCRkFBWW2BkgWBNVluBFGBuV2AAgP1bUFZbYACBUZBQYH6BYF1WW5KRUFBWW2AAYCCChAMSFWCXV2CWYEhWW1tgAGCjhIKFAWBxVluRUFCSkVBQVltgP4BhALpgADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiDXy5JtBgmz6Wa96BpXWBCQR1/dHohtgoJX3fkcP3IrimRzb2xjQwAICgAzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC+0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","b64Record":"CiUIFiIDGO0XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDP+SxPjTB8EYy2QwDJYYlCCq7s4S+6Cs4Qy4Wp0NQNPcn9C8KUH4qVlZ8tqFspsqoaCwjY9/+rBhCruOYlIg8KCQic9/+rBhC0CRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMID8644COqoFCgMY7RcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAKICk6AMy7AIKAxjtFxKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAaILA8U7KOeKiOMWB6J+H6SCNNzijV2dnseyla6wLmdKHhIkAAAAAAAAAAAAAAAACxJWiFNL5xhLfO4HFZlAXkvb4ACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqOgMY8xc6Axj0F3IHCgMY7RcQAnIHCgMY8RcQA3IHCgMY8xcQAXIHCgMY9BcQAVIiCgoKAhgCEKOL2J0ECgoKAhhiEID4150ECggKAxjxFxCkEw=="},{"b64Body":"ChEKCQic9/+rBhC0CRICGAIgAUIhGgIyAEIFCIDO2gNqFGxhenktY3JlYXRlZCBhY2NvdW50","b64Record":"CgcIFiIDGPMXEjAhSFHzR0o3Fd06Mr1l7uEOMYuNj1NxFHK6G+5mlCQ/IcWV1qFlmh4ivful867a7p0aCwjY9/+rBhCsuOYlIhEKCQic9/+rBhC0CRICGAIgAUIdCgMY8xdKFgoUfKqa33qBj4IpF2pm0nZU8U2vZDpSAHoLCNj3/6sGEKu45iU="},{"b64Body":"ChEKCQic9/+rBhC0CRICGAIgAkIhGgIyAEIFCIDO2gNqFGxhenktY3JlYXRlZCBhY2NvdW50","b64Record":"CgcIFiIDGPQXEjDwBhJYcnOjgjsH5f5MPbjFkWwpDiHhAIBw/l5y6GOIbz7Zn7rlG6AOHlqFJTSNAN0aCwjY9/+rBhCtuOYlIhEKCQic9/+rBhC0CRICGAIgAkIdCgMY9BdKFgoUjcjeKP6R8gZyNMndImNNA0/ifhdSAHoLCNj3/6sGEKu45iU="},{"b64Body":"ChEKCQic9/+rBhC0CRICGAIgA0I3GiISIHUCm+V9zHOBbv0d2Y2P8y52zbmOWkDMTrsjh2ktmTwvQgUIgM7aA2oKSlVTVCBETyBJVA==","b64Record":"CgcIFiIDGPEXEjCmrh5s8xDHiojvf9sAdT3n9oRg+d7O3+v9oFTmKmk486KGXW25CCSJTTMlBW2IkIAaCwjY9/+rBhCuuOYlIhEKCQic9/+rBhC0CRICGAIgA0IdCgMY8RdKFgoUsSVohTS+cYS3zuBxWZQF5L2+AAhSAHoLCNj3/6sGEKu45iU="},{"b64Body":"Cg8KCQic9/+rBhDMCRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIDD8wR4/pFoypsf6zjLVwSdvLMtQgRCnstJmpxSMjDmaEICU69wDSgUIgM7aAw==","b64Record":"CiUIFhIDGPUXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAxxCymQfG4w4xS32Q2sFNbK482M1rukMDg8NICtRXjjmuMSoZC6fxPrHKNbmMPJp0aDAjY9/+rBhCDp+mmAiIPCgkInPf/qwYQzAkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIZCgoKAhgCEP+n1rkHCgsKAxj1FxCAqNa5Bw=="}]},"CanCallFinalizedContractViaHapi":{"placeholderNum":3062,"encodedItems":[{"b64Body":"Cg8KCQih9/+rBhDcCRICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIISLnmTN4oxq00GaM3b7NsHTiUzYZde/o8UtXQjuFwR+EIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGPcXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCvlScSGlTYpVFROvwJdK+i4aYWLPLIEC14NZxSF6x8MxZMeKy3PsRMxj3dJM0hcYUaCwjd9/+rBhDDpto4Ig8KCQih9/+rBhDcCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGPcXEICQ38BK"},{"b64Body":"ChEKCQih9/+rBhDeCRICGAIgAVpoCiM6IQM1HGFjVZ/0mzSfD+pmtSjH/+T67D6RvEbFXoNzfPPFAkoFCIDO2gNqFGF1dG8tY3JlYXRlZCBhY2NvdW50kgEjOiEDNRxhY1Wf9Js0nw/qZrUox//k+uw+kbxGxV6Dc3zzxQI=","b64Record":"CgcIFhIDGPgXEjBr0M7znOQsID5K61OE2nRzOIDZ7H0SOjbFdLktQsyjVH3OVA/PQZU8MAd+Y15BL34aDAjd9/+rBhCSjKqdAiIRCgkIoff/qwYQ3gkSAhgCIAEqFGF1dG8tY3JlYXRlZCBhY2NvdW50UgCqARTdqs+6PhakkAW6QLxyiaZFEsmcrA=="},{"b64Body":"Cg8KCQih9/+rBhDeCRICGAISAhgDGKqQBSICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOcj0KOwoKCgIYAhD/j9/ASgotCiUiIzohAzUcYWNVn/SbNJ8P6ma1KMf/5PrsPpG8RsVeg3N888UCEICQ38BK","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwmDHqNLBTkfDBprKX3ND2gpn+Z+nY+qK9HWGyV273nNkQnHA/WL83ar3UctAHEsSeGgwI3ff/qwYQk4yqnQIiDwoJCKH3/6sGEN4JEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SGQoKCgIYAhD/j9/ASgoLCgMY+BcQgJDfwEo="},{"b64Body":"Cg8KCQii9/+rBhDgCRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwjexdqvBhDQiLcmGm0KIhIgiYZO7sA6SprrDq2KclQVrMBU0M35kgHFHO/LbFTClpYKIzohA8iJ3nptKB3g+aqnRhffbhN2KEtlvJJGnRyHEqoA+S0kCiISIN4LNteUVpTRnP6VV2E1Y/+nRoZh732As9ckoYIQU7wtIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGPkXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjC/JLmNC0+yiYzMLGXdQPL0NuwdBotnkq6NbLnLJknjtWVFv8cnGU9hub48VxYIz7YaCwje9/+rBhCrnZhCIg8KCQii9/+rBhDgCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQii9/+rBhDkCRICGAISAhgDGI3KgzwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBqhwKAxj5FyKiHDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDZmMTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwMjk1NzYwMDAzNTYwZTAxYzgwNjNjYWQzNzJhNzE0NjEwMDJlNTc4MDYzZTVkNTc1OWYxNDYxMDA2YjU3NWI2MDAwODBmZDViMzQ4MDE1NjEwMDNhNTc2MDAwODBmZDViNTA2MTAwNTU2MDA0ODAzNjAzODEwMTkwNjEwMDUwOTE5MDYxMDIxMjU2NWI2MTAwODc1NjViNjA0MDUxNjEwMDYyOTE5MDYxMDI4MDU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwODU2MDA0ODAzNjAzODEwMTkwNjEwMDgwOTE5MDYxMDIxMjU2NWI2MTAxM2Y1NjViMDA1YjYwMDA4MDYwNDA1MTgwNjAyMDAxNjEwMDlhOTA2MTAxY2E1NjViNjAyMDgyMDE4MTAzODI1MjYwMWYxOTYwMWY4MjAxMTY2MDQwNTI1MDkwNTA2MDAwODEzMDYwNDA1MTYwMjAwMTYxMDBjNDkxOTA2MTAyODA1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyNjA0MDUxNjAyMDAxNjEwMGU0OTI5MTkwNjEwMzBjNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwNTA2MDAwNjBmZjYwZjgxYjMwODY4NDgwNTE5MDYwMjAwMTIwNjA0MDUxNjAyMDAxNjEwMTE5OTQ5MzkyOTE5MDYxMDQxMTU2NWI2MDQwNTE2MDIwODE4MzAzMDM4MTUyOTA2MDQwNTI4MDUxOTA2MDIwMDEyMDkwNTA4MDYwMDAxYzkzNTA1MDUwNTA5MTkwNTA1NjViNjAwMDYwNDA1MTgwNjAyMDAxNjEwMTUxOTA2MTAxY2E1NjViNjAyMDgyMDE4MTAzODI1MjYwMWYxOTYwMWY4MjAxMTY2MDQwNTI1MDkwNTA2MDAwODEzMDYwNDA1MTYwMjAwMTYxMDE3YjkxOTA2MTAyODA1NjViNjA0MDUxNjAyMDgxODMwMzAzODE1MjkwNjA0MDUyNjA0MDUxNjAyMDAxNjEwMTliOTI5MTkwNjEwMzBjNTY1YjYwNDA1MTYwMjA4MTgzMDMwMzgxNTI5MDYwNDA1MjkwNTA2MDAwODM4MjUxNjAyMDg0MDEzNGY1OTA1MDgwM2I2MTAxYzQ1NzYwMDA4MGZkNWI1MDUwNTA1MDU2NWI2MTAyNWM4MDYxMDQ2MDgzMzkwMTkwNTY1YjYwMDA4MGZkNWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDFlZjgxNjEwMWRjNTY1YjgxMTQ2MTAxZmE1NzYwMDA4MGZkNWI1MDU2NWI2MDAwODEzNTkwNTA2MTAyMGM4MTYxMDFlNjU2NWI5MjkxNTA1MDU2NWI2MDAwNjAyMDgyODQwMzEyMTU2MTAyMjg1NzYxMDIyNzYxMDFkNzU2NWI1YjYwMDA2MTAyMzY4NDgyODUwMTYxMDFmZDU2NWI5MTUwNTA5MjkxNTA1MDU2NWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDI2YTgyNjEwMjNmNTY1YjkwNTA5MTkwNTA1NjViNjEwMjdhODE2MTAyNWY1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDI5NTYwMDA4MzAxODQ2MTAyNzE1NjViOTI5MTUwNTA1NjViNjAwMDgxNTE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MjkxNTA1MDU2NWI2MDAwNWI4MzgxMTAxNTYxMDJjZjU3ODA4MjAxNTE4MTg0MDE1MjYwMjA4MTAxOTA1MDYxMDJiNDU2NWI2MDAwODQ4NDAxNTI1MDUwNTA1MDU2NWI2MDAwNjEwMmU2ODI2MTAyOWI1NjViNjEwMmYwODE4NTYxMDJhNjU2NWI5MzUwNjEwMzAwODE4NTYwMjA4NjAxNjEwMmIxNTY1YjgwODQwMTkxNTA1MDkyOTE1MDUwNTY1YjYwMDA2MTAzMTg4Mjg1NjEwMmRiNTY1YjkxNTA2MTAzMjQ4Mjg0NjEwMmRiNTY1YjkxNTA4MTkwNTA5MzkyNTA1MDUwNTY1YjYwMDA3ZmZmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MjE2OTA1MDkxOTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDM3NzYxMDM3MjgyNjEwMzMwNTY1YjYxMDM1YzU2NWI4MjUyNTA1MDU2NWI2MDAwODE2MDYwMWI5MDUwOTE5MDUwNTY1YjYwMDA2MTAzOTU4MjYxMDM3ZDU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTAzYTc4MjYxMDM4YTU2NWI5MDUwOTE5MDUwNTY1YjYxMDNiZjYxMDNiYTgyNjEwMjVmNTY1YjYxMDM5YzU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYxMDNlMDYxMDNkYjgyNjEwMWRjNTY1YjYxMDNjNTU2NWI4MjUyNTA1MDU2NWI2MDAwODE5MDUwOTE5MDUwNTY1YjYwMDA4MTkwNTA5MTkwNTA1NjViNjEwNDBiNjEwNDA2ODI2MTAzZTY1NjViNjEwM2YwNTY1YjgyNTI1MDUwNTY1YjYwMDA2MTA0MWQ4Mjg3NjEwMzY2NTY1YjYwMDE4MjAxOTE1MDYxMDQyZDgyODY2MTAzYWU1NjViNjAxNDgyMDE5MTUwNjEwNDNkODI4NTYxMDNjZjU2NWI2MDIwODIwMTkxNTA2MTA0NGQ4Mjg0NjEwM2ZhNTY1YjYwMjA4MjAxOTE1MDgxOTA1MDk1OTQ1MDUwNTA1MDUwNTZmZTYwODA2MDQwNTI2MDQwNTE2MTAyNWMzODAzODA2MTAyNWM4MzM5ODE4MTAxNjA0MDUyODEwMTkwNjEwMDI1OTE5MDYxMDBjZTU2NWI4MDYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDUwNjEwMGZiNTY1YjYwMDA4MGZkNWI2MDAwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmODIxNjkwNTA5MTkwNTA1NjViNjAwMDYxMDA5YjgyNjEwMDcwNTY1YjkwNTA5MTkwNTA1NjViNjEwMGFiODE2MTAwOTA1NjViODExNDYxMDBiNjU3NjAwMDgwZmQ1YjUwNTY1YjYwMDA4MTUxOTA1MDYxMDBjODgxNjEwMGEyNTY1YjkyOTE1MDUwNTY1YjYwMDA2MDIwODI4NDAzMTIxNTYxMDBlNDU3NjEwMGUzNjEwMDZiNTY1YjViNjAwMDYxMDBmMjg0ODI4NTAxNjEwMGI5NTY1YjkxNTA1MDkyOTE1MDUwNTY1YjYxMDE1MjgwNjEwMTBhNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAzNjU3NjAwMDM1NjBlMDFjODA2MzYwZjk2YThmMTQ2MTAwM2I1NzgwNjM3MzdiYzNjOTE0NjEwMDU5NTc1YjYwMDA4MGZkNWI2MTAwNDM2MTAwNjM1NjViNjA0MDUxNjEwMDUwOTE5MDYxMDEwMTU2NWI2MDQwNTE4MDkxMDM5MGYzNWI2MTAwNjE2MTAwODc1NjViMDA1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTY1YjYwMDA4MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwMDA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmY4MjE2OTA1MDkxOTA1MDU2NWI2MDAwNjEwMGViODI2MTAwYzA1NjViOTA1MDkxOTA1MDU2NWI2MTAwZmI4MTYxMDBlMDU2NWI4MjUyNTA1MDU2NWI2MDAwNjAyMDgyMDE5MDUwNjEwMTE2NjAwMDgzMDE4NDYxMDBmMjU2NWI5MjkxNTA1MDU2ZmVhMjY0Njk3MDY2NzM1ODIyMTIyMGNhNWRkNDM3OTU2YzdhYWVjYTcyN2Q5N2I4MWVhMGNjMjYwMTdjZDc3YzM2NGJkYmIyM2NhYThlMjdiNjYwODY2NDczNmY2YzYzNDMwMDA4MTAwMDMzYTI2NDY5NzA2NjczNTgyMjEyMjA0OTQ4NjExM2RhYmQyY2RlODQ3NzMwZGM5MGRmNGU2MmVlOWZhMGU5NzlmMTNlODhhM2M2NzZkYmU0MDgxZWJiNjQ3MzZmNmM2MzQzMDAwODEwMDAzMw==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw2BbXLHUA0Z6ZSFuZdxjYuGc1pM0HzKvY0kdDZ6SHJU3h0Uy59/gmPJCXzfnN1NJTGgwI3vf/qwYQ47+ApwIiDwoJCKL3/6sGEOQJEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQij9/+rBhDmCRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGPkXGiISILEsgB3swJbuk11qwBPkKnlkMSMgpFkwCwdvwWOyvOJnIKDCHkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGPoXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBk5S565MqOhNUSJruQ1IUjKJNu5k91F+etf4Wk+IBOzJ3gymZOhfYydgj8vcUfQZkaCwjf9/+rBhCjqpYvIg8KCQij9/+rBhDmCRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMICzxQ1CphAKAxj6FxLxDWCAYEBSYAQ2EGEAKVdgADVg4ByAY8rTcqcUYQAuV4Bj5dV1nxRhAGtXW2AAgP1bNIAVYQA6V2AAgP1bUGEAVWAEgDYDgQGQYQBQkZBhAhJWW2EAh1ZbYEBRYQBikZBhAoBWW2BAUYCRA5DzW2EAhWAEgDYDgQGQYQCAkZBhAhJWW2EBP1ZbAFtgAIBgQFGAYCABYQCakGEBylZbYCCCAYEDglJgHxlgH4IBFmBAUlCQUGAAgTBgQFFgIAFhAMSRkGECgFZbYEBRYCCBgwMDgVKQYEBSYEBRYCABYQDkkpGQYQMMVltgQFFgIIGDAwOBUpBgQFKQUGAAYP9g+BswhoSAUZBgIAEgYEBRYCABYQEZlJOSkZBhBBFWW2BAUWAggYMDA4FSkGBAUoBRkGAgASCQUIBgAByTUFBQUJGQUFZbYABgQFGAYCABYQFRkGEBylZbYCCCAYEDglJgHxlgH4IBFmBAUlCQUGAAgTBgQFFgIAFhAXuRkGECgFZbYEBRYCCBgwMDgVKQYEBSYEBRYCABYQGbkpGQYQMMVltgQFFgIIGDAwOBUpBgQFKQUGAAg4JRYCCEATT1kFCAO2EBxFdgAID9W1BQUFBWW2ECXIBhBGCDOQGQVltgAID9W2AAgZBQkZBQVlthAe+BYQHcVluBFGEB+ldgAID9W1BWW2AAgTWQUGECDIFhAeZWW5KRUFBWW2AAYCCChAMSFWECKFdhAidhAddWW1tgAGECNoSChQFhAf1WW5FQUJKRUFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAmqCYQI/VluQUJGQUFZbYQJ6gWECX1ZbglJQUFZbYABgIIIBkFBhApVgAIMBhGECcVZbkpFQUFZbYACBUZBQkZBQVltgAIGQUJKRUFBWW2AAW4OBEBVhAs9XgIIBUYGEAVJgIIEBkFBhArRWW2AAhIQBUlBQUFBWW2AAYQLmgmECm1ZbYQLwgYVhAqZWW5NQYQMAgYVgIIYBYQKxVluAhAGRUFCSkVBQVltgAGEDGIKFYQLbVluRUGEDJIKEYQLbVluRUIGQUJOSUFBQVltgAH//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIIWkFCRkFBWW2AAgZBQkZBQVlthA3dhA3KCYQMwVlthA1xWW4JSUFBWW2AAgWBgG5BQkZBQVltgAGEDlYJhA31WW5BQkZBQVltgAGEDp4JhA4pWW5BQkZBQVlthA79hA7qCYQJfVlthA5xWW4JSUFBWW2AAgZBQkZBQVlthA+BhA9uCYQHcVlthA8VWW4JSUFBWW2AAgZBQkZBQVltgAIGQUJGQUFZbYQQLYQQGgmED5lZbYQPwVluCUlBQVltgAGEEHYKHYQNmVltgAYIBkVBhBC2ChmEDrlZbYBSCAZFQYQQ9goVhA89WW2AgggGRUGEETYKEYQP6VltgIIIBkVCBkFCVlFBQUFBQVv5ggGBAUmBAUWECXDgDgGECXIM5gYEBYEBSgQGQYQAlkZBhAM5WW4BgAIBhAQAKgVSBc///////////////////////////AhkWkINz//////////////////////////8WAheQVVBQYQD7VltgAID9W2AAc///////////////////////////ghaQUJGQUFZbYABhAJuCYQBwVluQUJGQUFZbYQCrgWEAkFZbgRRhALZXYACA/VtQVltgAIFRkFBhAMiBYQCiVluSkVBQVltgAGAggoQDEhVhAORXYQDjYQBrVltbYABhAPKEgoUBYQC5VluRUFCSkVBQVlthAVKAYQEKYAA5YADz/mCAYEBSNIAVYQAQV2AAgP1bUGAENhBhADZXYAA1YOAcgGNg+WqPFGEAO1eAY3N7w8kUYQBZV1tgAID9W2EAQ2EAY1ZbYEBRYQBQkZBhAQFWW2BAUYCRA5DzW2EAYWEAh1ZbAFtgAIBUkGEBAAqQBHP//////////////////////////xaBVltgAIBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8W/1tgAHP//////////////////////////4IWkFCRkFBWW2AAYQDrgmEAwFZbkFCRkFBWW2EA+4FhAOBWW4JSUFBWW2AAYCCCAZBQYQEWYACDAYRhAPJWW5KRUFBW/qJkaXBmc1giEiDKXdQ3lWx6rspyfZe4HqDMJgF813w2S9uyPKqOJ7ZghmRzb2xjQwAIEAAzomRpcGZzWCISIElIYRPavSzehHcw3JDfTmLun6DpefE+iKPGdtvkCB67ZHNvbGNDAAgQADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIC1GDoDGPoXShYKFAAAAAAAAAAAAAAAAAAAAAAAAAv6cgcKAxj6FxABUhYKCQoCGAIQ/+WKGwoJCgIYYhCA5oob"},{"b64Body":"ChIKCQik9/+rBhDsCRIDGPcXIAFaOAoCMgBKBQiAztoDahRsYXp5LWNyZWF0ZWQgYWNjb3VudJIBFAnSu/U4UdESS5cg0TkaTZNmEZNc","b64Record":"CgcIFhIDGPsXEjBOG9OnUVdBlFGWArg+JA82pSotfpSmviKKsu9HMCvVYERWrZwKTlxZ5ueAYe3FYusaCwjg9/+rBhDyxfQ4IhIKCQik9/+rBhDsCRIDGPcXIAEqFGxhenktY3JlYXRlZCBhY2NvdW50MInK5RJSAA=="},{"b64Body":"ChAKCQik9/+rBhDsCRIDGPcXEgIYAxiyg8UVIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo6SA7kBCrABAvitggEqgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB0alKIAIMehICUCdK79ThR0RJLlyDRORpNk2YRk1yIDeC2s6dkAACAwAGgAwakjG7QEYGYVFBKdEkRaA6hey9Y5U8zD7+Ka2wyuiqgXmae2uyVvckaKM6GlzScb2D9yGfub3SabsQ5u7X1Cs0YgMq17gE=","b64Record":"CiUIFiIDGPsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB4H8qB2actfmnvSoBn3VCokb8JJxNScCQ8tuZHNyLZgs7fvhp8YfS0g7kP9D5C/gEaCwjg9/+rBhDzxfQ4IhAKCQik9/+rBhDsCRIDGPcXKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCyz9pLOpoCCgMY+xcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYVCAiXpYgMLXL2oDGPgXUkgKCQoCGAMQxPHEAQoKCgIYYhDE0dyRAQoKCgMYoAYQ3NuTBAoKCgMY9xcQ45qGOAoLCgMY+BcQ/4fevgEKCgoDGPsXEICEr1+KASBeyO5FAIBoXT5Dha8OZc0XyOc46NAactjacFPiGirbFQ=="},{"b64Body":"Cg8KCQik9/+rBhD0CRICGAISAhgDGID/2kMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjo0CgMY+hcQgIl6GIDC1y8iJOXVdZ8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASZYC0g==","b64Record":"CiUIFiIDGPoXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCJ1DpjUb39FVzr6jIH4wqk3lb8t70f3ka1fI3zxnqPA4Eg9E85AInZIWZvYoKL3TIaDAjg9/+rBhCb0aCdAiIPCgkIpPf/qwYQ9AkSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAzJU2Op4CCgMY+hcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDUYXIHCgMY+hcQAnIHCgMY+xcQAVIjCgoKAhgCEP+b2ssBCgkKAhhiEICYq2wKCgoDGPsXEICEr18="},{"b64Body":"ChEKCQik9/+rBhD0CRICGAIgAUI4GiISILEsgB3swJbuk11qwBPkKnlkMSMgpFkwCwdvwWOyvOJnQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGPsXEjBfL/fyRteltA8KWnoJGfPtUMf7LXK45Ffz7NV2yFJi+0lYl23gnHAds9U02vnrln0aDAjg9/+rBhCc0aCdAiIRCgkIpPf/qwYQ9AkSAhgCIAFCHQoDGPsXShYKFAnSu/U4UdESS5cg0TkaTZNmEZNcUgB6DAjg9/+rBhCb0aCdAg=="},{"b64Body":"Cg8KCQil9/+rBhD8CRICGAISAhgDGOCssQMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoiChYiFAnSu/U4UdESS5cg0TkaTZNmEZNcEKCNBiIEc3vDyQ==","b64Record":"CiUIFiIDGPsXKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBkBscY9LWnryH0e6QqaBXmN7ETkE66Djalxl7F4chtlcEsHI1jJQHEmtgZNIBfacQaCwjh9/+rBhDT7JVCIg8KCQil9/+rBhD8CRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMIDX2gI6jAIKAxj7FyKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAogPEEUjAKCQoCGAIQ/621BQoJCgIYYhCArrUFCgsKAxj6FxCAiN6+AQoLCgMY+xcQ/4fevgE="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/record-snapshots/CreateOperation.json b/hedera-node/test-clients/record-snapshots/CreateOperation.json new file mode 100644 index 000000000000..cc56c73a1269 --- /dev/null +++ b/hedera-node/test-clients/record-snapshots/CreateOperation.json @@ -0,0 +1 @@ +{"specSnapshots":{"simpleFactoryWorks":{"placeholderNum":3069,"encodedItems":[{"b64Body":"Cg8KCQjHy4GsBhC4BBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiDmtyvBhDw59lZGm0KIhIg3T2bpbKjLQNAVl7c62cR+Co8MwtwGtm/7mph6Xz08fYKIzohAosF1iHaOGLJKhn1ADpGg385LKfDmqj4qlV08SchWHrKCiISINo9oyxraJwJ6+jQzV7v6RVVSFJSVfNnOgXg7MRXLZzLIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGP4XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDoBu4Jb4WEVDr3H51HNMnAfYLZ2HjagIV71wZ6CFJsbVLeq9HDoeMecBXA9CI8lb8aCwiDzIGsBhCD1dJ0Ig8KCQjHy4GsBhC4BBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjHy4GsBhC8BBICGAISAhgDGMLTyDgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkBYKAxj+FyKIFjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDU2NDgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNmQ1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMjliNGE3YTkxNDYxMDA3MjU3ODA2MzMwZjNmNzhkMTQ2MTAwODk1NzgwNjM0NjAwMDI4ZjE0NjEwMGEwNTc4MDYzNTc0NjUzMTExNDYxMDBiNzU3ODA2Mzk5ZWVmMDU1MTQ2MTAwY2U1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA3ZTU3NjAwMDgwZmQ1YjUwNjEwMDg3NjEwMGU1NTY1YjAwNWIzNDgwMTU2MTAwOTU1NzYwMDA4MGZkNWI1MDYxMDA5ZTYxMDExYTU2NWIwMDViMzQ4MDE1NjEwMGFjNTc2MDAwODBmZDViNTA2MTAwYjU2MTAxNTI1NjViMDA1YjM0ODAxNTYxMDBjMzU3NjAwMDgwZmQ1YjUwNjEwMGNjNjEwMTdhNTY1YjAwNWIzNDgwMTU2MTAwZGE1NzYwMDA4MGZkNWI1MDYxMDBlMzYxMDFhMjU2NWIwMDViNjEwMGVkNjEwMWRhNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxMDk1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDYwMDAxNTE1NjEwMTE4NTc2MDAwODBmZDViNTY1YjYwMDA2MTAxMjQ2MTAxZTk1NjViODA4MjE1MTUxNTE1ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxNGU1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDU2NWI2MTAxNWE2MTAxZGE1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE3NjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYxMDE4MjYxMDFmOTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTllNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViNjAwMTYxMDFhYzYxMDFlOTU2NWI4MDgyMTUxNTE1MTU4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDFkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYwNDA1MTYwYzE4MDYxMDIwOTgzMzkwMTkwNTY1YjYwNDA1MTYxMDIxMTgwNjEwMmNhODMzOTAxOTA1NjViNjA0MDUxNjA1ZTgwNjEwNGRiODMzOTAxOTA1NjAwNjA4MDYwNDA1MjYwMDE2MDAwNTUzNDgwMTU2MDE0NTc2MDAwODBmZDViNTA2MDllODA2MTAwMjM2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDA0MzYxMDYwM2Y1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMDI2M2FkNmUxNDYwNDQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwNGY1NzYwMDA4MGZkNWI1MDYwNTY2MDZjNTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjAwMDU0ODE1NjAwYTE2NTYyN2E3YTcyMzA1ODIwMWM5ZWQ5MzQ5NjU0YTVkYTNmZGI4MTc5ZGUyYTU1YTcyMDA0YTIwOGM1NGQxYjZmNjMxYjYxYTY3N2EyZGVkNzAwMjk2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MDIwODA2MTAyMTE4MzM5ODEwMTgwNjA0MDUyODEwMTkwODA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgwMTU2MTAwNjQ1NzYxMDA0MTYxMDA5MTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDVkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA2MTAwOGI1NjViNjEwMDZjNjEwMGEwNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwODg1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDViNTA2MTAwYWY1NjViNjA0MDUxNjA1ZTgwNjEwMGYyODMzOTAxOTA1NjViNjA0MDUxNjBjMTgwNjEwMTUwODMzOTAxOTA1NjViNjAzNTgwNjEwMGJkNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDE1OGRhMDliMmQyMGQyNDRiOWE2NDlmMThmMGM5NzFmODhiZjE3Y2Y1ZWFkNzBkYzU4MTNlOGQzZjllNjBmZmQwMDI5NjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDAxNTE1NjAxYzU3NjAwMDgwZmQ1YjYwMzU4MDYwMjk2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDAwODBmZDAwYTE2NTYyN2E3YTcyMzA1ODIwMzE4ZDE3OTZlYjQxODI2Nzk2NTBlZDFmMzY5ZThlNWJjZGFjM2U2NTRlNDJkZTYzNTExYzUxODRjYmI0NzM2YzAwMjk2MDgwNjA0MDUyNjAwMTYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwOWU4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwMjYzYWQ2ZTE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNTQ4MTU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxYzllZDkzNDk2NTRhNWRhM2ZkYjgxNzlkZTJhNTVhNzIwMDRhMjA4YzU0ZDFiNmY2MzFiNjFhNjc3YTJkZWQ3MDAyOTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDAwMTUxNTYwMWM1NzYwMDA4MGZkNWI2MDM1ODA2MDI5NjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDMxOGQxNzk2ZWI0MTgyNjc5NjUwZWQxZjM2OWU4ZTViY2RhYzNlNjU0ZTQyZGU2MzUxMWM1MTg0Y2JiNDczNmMwMDI5YTE2NTYyN2E3YTcyMzA1ODIwNTM0NmVhYjgxOGYwNGM0ZDQxNmZhMTBiN2ZjZjA5Yzc0MTNiYzBmNGMzZGViMTY2NDJjOGFkMzIxMDU4MmJmOTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwIUOOIwm8ZXzsae6o2dK7Ggwxf2W9ZEAWvt/WxBQW6ceRPwLg5duwt6JGb40sHmKaGgwIg8yBrAYQ++Oo9gIiDwoJCMfLgawGELwEEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjIy4GsBhC+BBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGP4XGiISIJuP7VigKXznuNOhFgke8/E9slRqQ2rjUTV+tcvkz+2VIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGP8XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCrSh6bcaoJVGJmPYL/OPm2FtfH0CtFqrpFEdMMQA3JczwHnsuwVBsu6M9WxUL1qEYaDAiEzIGsBhD7oIubASIPCgkIyMuBrAYQvgQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQpkNCgMY/xcS5ApggGBAUmAENhBhAG1XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYym0p6kUYQByV4BjMPP3jRRhAIlXgGNGAAKPFGEAoFeAY1dGUxEUYQC3V4Bjme7wVRRhAM5XW2AAgP1bNIAVYQB+V2AAgP1bUGEAh2EA5VZbAFs0gBVhAJVXYACA/VtQYQCeYQEaVlsAWzSAFWEArFdgAID9W1BhALVhAVJWWwBbNIAVYQDDV2AAgP1bUGEAzGEBelZbAFs0gBVhANpXYACA/VtQYQDjYQGiVlsAW2EA7WEB2lZbYEBRgJEDkGAA8IAVgBVhAQlXPWAAgD49YAD9W1BQYAAVFWEBGFdgAID9W1ZbYABhASRhAelWW4CCFRUVFYFSYCABkVBQYEBRgJEDkGAA8IAVgBVhAU5XPWAAgD49YAD9W1BQVlthAVphAdpWW2BAUYCRA5BgAPCAFYAVYQF2Vz1gAIA+PWAA/VtQUFZbYQGCYQH5VltgQFGAkQOQYADwgBWAFWEBnlc9YACAPj1gAP1bUFBWW2ABYQGsYQHpVluAghUVFRWBUmAgAZFQUGBAUYCRA5BgAPCAFYAVYQHWVz1gAIA+PWAA/VtQUFZbYEBRYMGAYQIJgzkBkFZbYEBRYQIRgGECyoM5AZBWW2BAUWBegGEE24M5AZBWAGCAYEBSYAFgAFU0gBVgFFdgAID9W1BgnoBhACNgADlgAPMAYIBgQFJgBDYQYD9XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYwJjrW4UYERXW2AAgP1bNIAVYE9XYACA/VtQYFZgbFZbYEBRgIKBUmAgAZFQUGBAUYCRA5DzW2AAVIFWAKFlYnp6cjBYIBye2TSWVKXaP9uBed4qVacgBKIIxU0bb2MbYaZ3ot7XAClggGBAUjSAFWEAEFdgAID9W1BgQFFgIIBhAhGDOYEBgGBAUoEBkICAUZBgIAGQkpGQUFBQgBVhAGRXYQBBYQCRVltgQFGAkQOQYADwgBWAFWEAXVc9YACAPj1gAP1bUFBhAItWW2EAbGEAoFZbYEBRgJEDkGAA8IAVgBVhAIhXPWAAgD49YAD9W1BQW1BhAK9WW2BAUWBegGEA8oM5AZBWW2BAUWDBgGEBUIM5AZBWW2A1gGEAvWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggFY2gmy0g0kS5pknxjwyXH4i/F89erXDcWBPo0/nmD/0AKWCAYEBSNIAVYA9XYACA/VtQYAAVFWAcV2AAgP1bYDWAYClgADlgAPMAYIBgQFJgAID9AKFlYnp6cjBYIDGNF5brQYJnllDtHzaejlvNrD5lTkLeY1EcUYTLtHNsAClggGBAUmABYABVNIAVYBRXYACA/VtQYJ6AYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMCY61uFGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAFSBVgChZWJ6enIwWCAcntk0llSl2j/bgXneKlWnIASiCMVNG29jG2Gmd6Le1wApYIBgQFI0gBVgD1dgAID9W1BgABUVYBxXYACA/VtgNYBgKWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggMY0XlutBgmeWUO0fNp6OW82sPmVOQt5jURxRhMu0c2wAKaFlYnp6cjBYIFNG6rgY8ExNQW+hC3/PCcdBO8D0w96xZkLIrTIQWCv5ACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGP8XShYKFAAAAAAAAAAAAAAAAAAAAAAAAAv/cgcKAxj/FxABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjIy4GsBhDABBICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMY/xcQ4M0vIgRGAAKP","b64Record":"CiUIFiIDGP8XKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCTY64VDVIaAvW0nwW4U7gr+s+G2SyES8ZC/3s7WpMBYVePxKa9buoh4vjliGKeLmMaDAiEzIGsBhDz59L/AiIPCgkIyMuBrAYQwAQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOqMCCgMY/xcigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICLJjoDGIAYcgcKAxj/FxACcgcKAxiAGBABUhYKCQoCGAIQ/5mgKgoJCgIYYhCAmqAq"},{"b64Body":"ChEKCQjIy4GsBhDABBICGAIgAUI4GiISIJuP7VigKXznuNOhFgke8/E9slRqQ2rjUTV+tcvkz+2VQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGIAYEjBTIOfUidgazavJDNBz4ZwdswQeZM69c2QA4K1iy7DtYGB6kOWBOdNO/x4++vOKkM4aDAiEzIGsBhD059L/AiIRCgkIyMuBrAYQwAQSAhgCIAFCHQoDGIAYShYKFBD2CHTBbNTc3yedK8KlHkfh/ESFUgB6DAiEzIGsBhDz59L/Ag=="}]},"StackedFactoryWorks":{"placeholderNum":3073,"encodedItems":[{"b64Body":"Cg8KCQjNy4GsBhDaBBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwiJmtyvBhC44+hEGm0KIhIgl2I/4N0oTl/pmdjQdrNYjeTOKIm2SvJMqkb73bQ/6fAKIzohA5lfAZfj4pJf+WAoWPnLORu2v0fX7e/sHIFnSYPfM/wyCiISIFlNLVn3mzbASgfrQddbfCIDs1cWqdZR2klZxduZ6or6IgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGIIYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCD5OaFz9fZtrXsI6TADa8utsmg5PSTNLYSZ3lkzWpHOJnG0Eu70bU9B9k/sahTagcaCwiJzIGsBhDj8utXIg8KCQjNy4GsBhDaBBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjNy4GsBhDeBBICGAISAhgDGMLTyDgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkBYKAxiCGCKIFjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDU2NDgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNmQ1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMjliNGE3YTkxNDYxMDA3MjU3ODA2MzMwZjNmNzhkMTQ2MTAwODk1NzgwNjM0NjAwMDI4ZjE0NjEwMGEwNTc4MDYzNTc0NjUzMTExNDYxMDBiNzU3ODA2Mzk5ZWVmMDU1MTQ2MTAwY2U1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA3ZTU3NjAwMDgwZmQ1YjUwNjEwMDg3NjEwMGU1NTY1YjAwNWIzNDgwMTU2MTAwOTU1NzYwMDA4MGZkNWI1MDYxMDA5ZTYxMDExYTU2NWIwMDViMzQ4MDE1NjEwMGFjNTc2MDAwODBmZDViNTA2MTAwYjU2MTAxNTI1NjViMDA1YjM0ODAxNTYxMDBjMzU3NjAwMDgwZmQ1YjUwNjEwMGNjNjEwMTdhNTY1YjAwNWIzNDgwMTU2MTAwZGE1NzYwMDA4MGZkNWI1MDYxMDBlMzYxMDFhMjU2NWIwMDViNjEwMGVkNjEwMWRhNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxMDk1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDYwMDAxNTE1NjEwMTE4NTc2MDAwODBmZDViNTY1YjYwMDA2MTAxMjQ2MTAxZTk1NjViODA4MjE1MTUxNTE1ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxNGU1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDU2NWI2MTAxNWE2MTAxZGE1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE3NjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYxMDE4MjYxMDFmOTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTllNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViNjAwMTYxMDFhYzYxMDFlOTU2NWI4MDgyMTUxNTE1MTU4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDFkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYwNDA1MTYwYzE4MDYxMDIwOTgzMzkwMTkwNTY1YjYwNDA1MTYxMDIxMTgwNjEwMmNhODMzOTAxOTA1NjViNjA0MDUxNjA1ZTgwNjEwNGRiODMzOTAxOTA1NjAwNjA4MDYwNDA1MjYwMDE2MDAwNTUzNDgwMTU2MDE0NTc2MDAwODBmZDViNTA2MDllODA2MTAwMjM2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDA0MzYxMDYwM2Y1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMDI2M2FkNmUxNDYwNDQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwNGY1NzYwMDA4MGZkNWI1MDYwNTY2MDZjNTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjAwMDU0ODE1NjAwYTE2NTYyN2E3YTcyMzA1ODIwMWM5ZWQ5MzQ5NjU0YTVkYTNmZGI4MTc5ZGUyYTU1YTcyMDA0YTIwOGM1NGQxYjZmNjMxYjYxYTY3N2EyZGVkNzAwMjk2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MDIwODA2MTAyMTE4MzM5ODEwMTgwNjA0MDUyODEwMTkwODA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgwMTU2MTAwNjQ1NzYxMDA0MTYxMDA5MTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDVkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA2MTAwOGI1NjViNjEwMDZjNjEwMGEwNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwODg1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDViNTA2MTAwYWY1NjViNjA0MDUxNjA1ZTgwNjEwMGYyODMzOTAxOTA1NjViNjA0MDUxNjBjMTgwNjEwMTUwODMzOTAxOTA1NjViNjAzNTgwNjEwMGJkNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDE1OGRhMDliMmQyMGQyNDRiOWE2NDlmMThmMGM5NzFmODhiZjE3Y2Y1ZWFkNzBkYzU4MTNlOGQzZjllNjBmZmQwMDI5NjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDAxNTE1NjAxYzU3NjAwMDgwZmQ1YjYwMzU4MDYwMjk2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDAwODBmZDAwYTE2NTYyN2E3YTcyMzA1ODIwMzE4ZDE3OTZlYjQxODI2Nzk2NTBlZDFmMzY5ZThlNWJjZGFjM2U2NTRlNDJkZTYzNTExYzUxODRjYmI0NzM2YzAwMjk2MDgwNjA0MDUyNjAwMTYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwOWU4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwMjYzYWQ2ZTE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNTQ4MTU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxYzllZDkzNDk2NTRhNWRhM2ZkYjgxNzlkZTJhNTVhNzIwMDRhMjA4YzU0ZDFiNmY2MzFiNjFhNjc3YTJkZWQ3MDAyOTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDAwMTUxNTYwMWM1NzYwMDA4MGZkNWI2MDM1ODA2MDI5NjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDMxOGQxNzk2ZWI0MTgyNjc5NjUwZWQxZjM2OWU4ZTViY2RhYzNlNjU0ZTQyZGU2MzUxMWM1MTg0Y2JiNDczNmMwMDI5YTE2NTYyN2E3YTcyMzA1ODIwNTM0NmVhYjgxOGYwNGM0ZDQxNmZhMTBiN2ZjZjA5Yzc0MTNiYzBmNGMzZGViMTY2NDJjOGFkMzIxMDU4MmJmOTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw9tPP5SF6l9xhZNpyYpynIwMVTHLvfQ0N+CRRml7MI+yvzro5ZWODBVNRM9rxbi1zGgwIicyBrAYQ+7DqvAIiDwoJCM3LgawGEN4EEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjOy4GsBhDgBBICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGIIYGiISIN/qmAeBD0/SzbPfKTKv4b49PzBuT/nhTut0sRLwS3b6IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGIMYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCVALslyfoHx1wJcABo49a+xKjVFCVT3NKw+4phnkMHKoKo7gAV5caZBjxp5ijIUwsaCwiKzIGsBhCD4LRhIg8KCQjOy4GsBhDgBBICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCmQ0KAxiDGBLkCmCAYEBSYAQ2EGEAbVdgADV8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQBGP/////FoBjKbSnqRRhAHJXgGMw8/eNFGEAiVeAY0YAAo8UYQCgV4BjV0ZTERRhALdXgGOZ7vBVFGEAzldbYACA/Vs0gBVhAH5XYACA/VtQYQCHYQDlVlsAWzSAFWEAlVdgAID9W1BhAJ5hARpWWwBbNIAVYQCsV2AAgP1bUGEAtWEBUlZbAFs0gBVhAMNXYACA/VtQYQDMYQF6VlsAWzSAFWEA2ldgAID9W1BhAONhAaJWWwBbYQDtYQHaVltgQFGAkQOQYADwgBWAFWEBCVc9YACAPj1gAP1bUFBgABUVYQEYV2AAgP1bVltgAGEBJGEB6VZbgIIVFRUVgVJgIAGRUFBgQFGAkQOQYADwgBWAFWEBTlc9YACAPj1gAP1bUFBWW2EBWmEB2lZbYEBRgJEDkGAA8IAVgBVhAXZXPWAAgD49YAD9W1BQVlthAYJhAflWW2BAUYCRA5BgAPCAFYAVYQGeVz1gAIA+PWAA/VtQUFZbYAFhAaxhAelWW4CCFRUVFYFSYCABkVBQYEBRgJEDkGAA8IAVgBVhAdZXPWAAgD49YAD9W1BQVltgQFFgwYBhAgmDOQGQVltgQFFhAhGAYQLKgzkBkFZbYEBRYF6AYQTbgzkBkFYAYIBgQFJgAWAAVTSAFWAUV2AAgP1bUGCegGEAI2AAOWAA8wBggGBAUmAENhBgP1dgADV8AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQBGP/////FoBjAmOtbhRgRFdbYACA/Vs0gBVgT1dgAID9W1BgVmBsVltgQFGAgoFSYCABkVBQYEBRgJEDkPNbYABUgVYAoWVienpyMFggHJ7ZNJZUpdo/24F53ipVpyAEogjFTRtvYxthpnei3tcAKWCAYEBSNIAVYQAQV2AAgP1bUGBAUWAggGECEYM5gQGAYEBSgQGQgIBRkGAgAZCSkZBQUFCAFWEAZFdhAEFhAJFWW2BAUYCRA5BgAPCAFYAVYQBdVz1gAIA+PWAA/VtQUGEAi1ZbYQBsYQCgVltgQFGAkQOQYADwgBWAFWEAiFc9YACAPj1gAP1bUFBbUGEAr1ZbYEBRYF6AYQDygzkBkFZbYEBRYMGAYQFQgzkBkFZbYDWAYQC9YAA5YADzAGCAYEBSYACA/QChZWJ6enIwWCAVjaCbLSDSRLmmSfGPDJcfiL8Xz16tcNxYE+jT+eYP/QApYIBgQFI0gBVgD1dgAID9W1BgABUVYBxXYACA/VtgNYBgKWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggMY0XlutBgmeWUO0fNp6OW82sPmVOQt5jURxRhMu0c2wAKWCAYEBSYAFgAFU0gBVgFFdgAID9W1BgnoBhACNgADlgAPMAYIBgQFJgBDYQYD9XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYwJjrW4UYERXW2AAgP1bNIAVYE9XYACA/VtQYFZgbFZbYEBRgIKBUmAgAZFQUGBAUYCRA5DzW2AAVIFWAKFlYnp6cjBYIBye2TSWVKXaP9uBed4qVacgBKIIxU0bb2MbYaZ3ot7XAClggGBAUjSAFWAPV2AAgP1bUGAAFRVgHFdgAID9W2A1gGApYAA5YADzAGCAYEBSYACA/QChZWJ6enIwWCAxjReW60GCZ5ZQ7R82no5bzaw+ZU5C3mNRHFGEy7RzbAApoWVienpyMFggU0bquBjwTE1Bb6ELf88Jx0E7wPTD3rFmQsitMhBYK/kAKSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYgxhKFgoUAAAAAAAAAAAAAAAAAAAAAAAADANyBwoDGIMYEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQjOy4GsBhDiBBICGAISAhgDGMC/7SEiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYgxgQwIQ9IgQw8/eN","b64Record":"CiUIFiIDGIMYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCddiui6fMBotZXhG8rUaEN9h76hB3gJHubT/ZQW4sDFDk6mn+QWv7yfB9gbTLC3IQaDAiKzIGsBhCrgNfGAiIPCgkIzsuBrAYQ4gQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCA5oobOrECCgMYgxgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKIDqMDoDGIQYOgMYhRhyBwoDGIMYEAJyBwoDGIQYEAJyBwoDGIUYEAFSFgoJCgIYAhD/y5U2CgkKAhhiEIDMlTY="},{"b64Body":"ChEKCQjOy4GsBhDiBBICGAIgAUI4GiISIN/qmAeBD0/SzbPfKTKv4b49PzBuT/nhTut0sRLwS3b6QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGIUYEjB9koeP9L547zRMch5AXRSN3cHHgfErYDy1qXoRoDL24FgpkVUB+VetJzLqqquPLMcaDAiKzIGsBhCsgNfGAiIRCgkIzsuBrAYQ4gQSAhgCIAFCHQoDGIUYShYKFDQbS1btep9IwzEqZENJ0sxpZ+v1UgB6DAiKzIGsBhCrgNfGAg=="},{"b64Body":"ChEKCQjOy4GsBhDiBBICGAIgAkI4GiISIN/qmAeBD0/SzbPfKTKv4b49PzBuT/nhTut0sRLwS3b6QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGIQYEjD29uKZFil0PVXxrygBm0EoKhgQ59RsAYAWSXEkC1YtTwxMF0kYXq0C5QljlPySTaYaDAiKzIGsBhCtgNfGAiIRCgkIzsuBrAYQ4gQSAhgCIAJCHQoDGIQYShYKFAYqYn4/U0b5+baYLRSssqi4SUMBUgB6DAiKzIGsBhCrgNfGAg=="}]},"ResetOnFactoryFailureWorks":{"placeholderNum":3078,"encodedItems":[{"b64Body":"Cg8KCQjTy4GsBhD8BBICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiPmtyvBhDwgYaAARptCiISIL5Ke+O4b0cFRZMKRCHLWBQQdQG4q2dn0jQF2KYmgraQCiM6IQKRV880uyMef8B38belce0UjRVD4zdLrgbSIzA+lL4EyQoiEiC6mfEzhcLU10yqOwfGni4hbuERPsBnGf7/LzIjozgxaSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIcYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBVojXmiD5s920mLLyocEVvrSHJ2lbnhVHMvWDRaaGLi8OldpU7XvbpPW/4Z+HX7WUaDAiPzIGsBhDj24mWASIPCgkI08uBrAYQ/AQSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjTy4GsBhCABRICGAISAhgDGMLTyDgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkBYKAxiHGCKIFjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDU2NDgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNmQ1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMjliNGE3YTkxNDYxMDA3MjU3ODA2MzMwZjNmNzhkMTQ2MTAwODk1NzgwNjM0NjAwMDI4ZjE0NjEwMGEwNTc4MDYzNTc0NjUzMTExNDYxMDBiNzU3ODA2Mzk5ZWVmMDU1MTQ2MTAwY2U1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA3ZTU3NjAwMDgwZmQ1YjUwNjEwMDg3NjEwMGU1NTY1YjAwNWIzNDgwMTU2MTAwOTU1NzYwMDA4MGZkNWI1MDYxMDA5ZTYxMDExYTU2NWIwMDViMzQ4MDE1NjEwMGFjNTc2MDAwODBmZDViNTA2MTAwYjU2MTAxNTI1NjViMDA1YjM0ODAxNTYxMDBjMzU3NjAwMDgwZmQ1YjUwNjEwMGNjNjEwMTdhNTY1YjAwNWIzNDgwMTU2MTAwZGE1NzYwMDA4MGZkNWI1MDYxMDBlMzYxMDFhMjU2NWIwMDViNjEwMGVkNjEwMWRhNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxMDk1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDYwMDAxNTE1NjEwMTE4NTc2MDAwODBmZDViNTY1YjYwMDA2MTAxMjQ2MTAxZTk1NjViODA4MjE1MTUxNTE1ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxNGU1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDU2NWI2MTAxNWE2MTAxZGE1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE3NjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYxMDE4MjYxMDFmOTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTllNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViNjAwMTYxMDFhYzYxMDFlOTU2NWI4MDgyMTUxNTE1MTU4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDFkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYwNDA1MTYwYzE4MDYxMDIwOTgzMzkwMTkwNTY1YjYwNDA1MTYxMDIxMTgwNjEwMmNhODMzOTAxOTA1NjViNjA0MDUxNjA1ZTgwNjEwNGRiODMzOTAxOTA1NjAwNjA4MDYwNDA1MjYwMDE2MDAwNTUzNDgwMTU2MDE0NTc2MDAwODBmZDViNTA2MDllODA2MTAwMjM2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDA0MzYxMDYwM2Y1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMDI2M2FkNmUxNDYwNDQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwNGY1NzYwMDA4MGZkNWI1MDYwNTY2MDZjNTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjAwMDU0ODE1NjAwYTE2NTYyN2E3YTcyMzA1ODIwMWM5ZWQ5MzQ5NjU0YTVkYTNmZGI4MTc5ZGUyYTU1YTcyMDA0YTIwOGM1NGQxYjZmNjMxYjYxYTY3N2EyZGVkNzAwMjk2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MDIwODA2MTAyMTE4MzM5ODEwMTgwNjA0MDUyODEwMTkwODA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgwMTU2MTAwNjQ1NzYxMDA0MTYxMDA5MTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDVkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA2MTAwOGI1NjViNjEwMDZjNjEwMGEwNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwODg1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDViNTA2MTAwYWY1NjViNjA0MDUxNjA1ZTgwNjEwMGYyODMzOTAxOTA1NjViNjA0MDUxNjBjMTgwNjEwMTUwODMzOTAxOTA1NjViNjAzNTgwNjEwMGJkNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDE1OGRhMDliMmQyMGQyNDRiOWE2NDlmMThmMGM5NzFmODhiZjE3Y2Y1ZWFkNzBkYzU4MTNlOGQzZjllNjBmZmQwMDI5NjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDAxNTE1NjAxYzU3NjAwMDgwZmQ1YjYwMzU4MDYwMjk2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDAwODBmZDAwYTE2NTYyN2E3YTcyMzA1ODIwMzE4ZDE3OTZlYjQxODI2Nzk2NTBlZDFmMzY5ZThlNWJjZGFjM2U2NTRlNDJkZTYzNTExYzUxODRjYmI0NzM2YzAwMjk2MDgwNjA0MDUyNjAwMTYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwOWU4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwMjYzYWQ2ZTE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNTQ4MTU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxYzllZDkzNDk2NTRhNWRhM2ZkYjgxNzlkZTJhNTVhNzIwMDRhMjA4YzU0ZDFiNmY2MzFiNjFhNjc3YTJkZWQ3MDAyOTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDAwMTUxNTYwMWM1NzYwMDA4MGZkNWI2MDM1ODA2MDI5NjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDMxOGQxNzk2ZWI0MTgyNjc5NjUwZWQxZjM2OWU4ZTViY2RhYzNlNjU0ZTQyZGU2MzUxMWM1MTg0Y2JiNDczNmMwMDI5YTE2NTYyN2E3YTcyMzA1ODIwNTM0NmVhYjgxOGYwNGM0ZDQxNmZhMTBiN2ZjZjA5Yzc0MTNiYzBmNGMzZGViMTY2NDJjOGFkMzIxMDU4MmJmOTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwOsfnJdJTSyRIt7qawx9ey6ZYpzov3e4JytZek8qgbgCDZACTG5feROIQJpgRYhzxGgwIj8yBrAYQw7bK+wIiDwoJCNPLgawGEIAFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjUy4GsBhCCBRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGIcYGiISIGhMj3WfTt309czrXou0ovSHJvJhjXVYdjdf0X7nH5CIIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGIgYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCVzrIMXiTX/JxLcdGpKNpufpJqF0iNb/XTW/DCNCNgpeWoGthoHZpSmSxm0vL1sgcaDAiQzIGsBhCzx/efASIPCgkI1MuBrAYQggUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQpkNCgMYiBgS5ApggGBAUmAENhBhAG1XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYym0p6kUYQByV4BjMPP3jRRhAIlXgGNGAAKPFGEAoFeAY1dGUxEUYQC3V4Bjme7wVRRhAM5XW2AAgP1bNIAVYQB+V2AAgP1bUGEAh2EA5VZbAFs0gBVhAJVXYACA/VtQYQCeYQEaVlsAWzSAFWEArFdgAID9W1BhALVhAVJWWwBbNIAVYQDDV2AAgP1bUGEAzGEBelZbAFs0gBVhANpXYACA/VtQYQDjYQGiVlsAW2EA7WEB2lZbYEBRgJEDkGAA8IAVgBVhAQlXPWAAgD49YAD9W1BQYAAVFWEBGFdgAID9W1ZbYABhASRhAelWW4CCFRUVFYFSYCABkVBQYEBRgJEDkGAA8IAVgBVhAU5XPWAAgD49YAD9W1BQVlthAVphAdpWW2BAUYCRA5BgAPCAFYAVYQF2Vz1gAIA+PWAA/VtQUFZbYQGCYQH5VltgQFGAkQOQYADwgBWAFWEBnlc9YACAPj1gAP1bUFBWW2ABYQGsYQHpVluAghUVFRWBUmAgAZFQUGBAUYCRA5BgAPCAFYAVYQHWVz1gAIA+PWAA/VtQUFZbYEBRYMGAYQIJgzkBkFZbYEBRYQIRgGECyoM5AZBWW2BAUWBegGEE24M5AZBWAGCAYEBSYAFgAFU0gBVgFFdgAID9W1BgnoBhACNgADlgAPMAYIBgQFJgBDYQYD9XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYwJjrW4UYERXW2AAgP1bNIAVYE9XYACA/VtQYFZgbFZbYEBRgIKBUmAgAZFQUGBAUYCRA5DzW2AAVIFWAKFlYnp6cjBYIBye2TSWVKXaP9uBed4qVacgBKIIxU0bb2MbYaZ3ot7XAClggGBAUjSAFWEAEFdgAID9W1BgQFFgIIBhAhGDOYEBgGBAUoEBkICAUZBgIAGQkpGQUFBQgBVhAGRXYQBBYQCRVltgQFGAkQOQYADwgBWAFWEAXVc9YACAPj1gAP1bUFBhAItWW2EAbGEAoFZbYEBRgJEDkGAA8IAVgBVhAIhXPWAAgD49YAD9W1BQW1BhAK9WW2BAUWBegGEA8oM5AZBWW2BAUWDBgGEBUIM5AZBWW2A1gGEAvWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggFY2gmy0g0kS5pknxjwyXH4i/F89erXDcWBPo0/nmD/0AKWCAYEBSNIAVYA9XYACA/VtQYAAVFWAcV2AAgP1bYDWAYClgADlgAPMAYIBgQFJgAID9AKFlYnp6cjBYIDGNF5brQYJnllDtHzaejlvNrD5lTkLeY1EcUYTLtHNsAClggGBAUmABYABVNIAVYBRXYACA/VtQYJ6AYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMCY61uFGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAFSBVgChZWJ6enIwWCAcntk0llSl2j/bgXneKlWnIASiCMVNG29jG2Gmd6Le1wApYIBgQFI0gBVgD1dgAID9W1BgABUVYBxXYACA/VtgNYBgKWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggMY0XlutBgmeWUO0fNp6OW82sPmVOQt5jURxRhMu0c2wAKaFlYnp6cjBYIFNG6rgY8ExNQW+hC3/PCcdBO8D0w96xZkLIrTIQWCv5ACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIgYShYKFAAAAAAAAAAAAAAAAAAAAAAAAAwIcgcKAxiIGBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjUy4GsBhCEBRICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYiBgQ4M0vIgSZ7vBV","b64Record":"CiUIISIDGIgYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCWlLu1hYnmSK9GARf8GIFMNQxJQx1dyC6RHRvAJLwdjsCDrys3o9TqZ+sgFlwjW7MaDAiQzIGsBhD7je6EAyIPCgkI1MuBrAYQhAUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOggaAjB4KICLJlIWCgkKAhgCEP+ZoCoKCQoCGGIQgJqgKg=="},{"b64Body":"Cg8KCQjVy4GsBhCGBRICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYiBgQ4M0vIgRGAAKP","b64Record":"CiUIFiIDGIgYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAMW1X4SKUwUDsvPy9XbgrmJuvBExVeub73pZQhp6Ap0JU4mciaRsx++zkdeS6zmswaDAiRzIGsBhC7hf+oASIPCgkI1cuBrAYQhgUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOqMCCgMYiBgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICLJjoDGIkYcgcKAxiIGBACcgcKAxiJGBABUhYKCQoCGAIQ/5mgKgoJCgIYYhCAmqAq"},{"b64Body":"ChEKCQjVy4GsBhCGBRICGAIgAUI4GiISIGhMj3WfTt309czrXou0ovSHJvJhjXVYdjdf0X7nH5CIQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGIkYEjBldFFwmJ3ocXYOj/Em+niIrJM8OoO1KZfBvV+bvNB/9FIFYukGSJPCRb0Falnq5F8aDAiRzIGsBhC8hf+oASIRCgkI1cuBrAYQhgUSAhgCIAFCHQoDGIkYShYKFKBLO6a5ll0tDAvI5ePZmWxu20P2UgB6DAiRzIGsBhC7hf+oAQ=="}]},"ResetOnFactoryFailureAfterDeploymentWorks":{"placeholderNum":3082,"encodedItems":[{"b64Body":"Cg8KCQjZy4GsBhCkBRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiVmtyvBhCgxpmUAxptCiISIPInTPO993UFS8q+jSDQQocUTFHxCSxoEZXs998kcOK0CiM6IQKsd/12b73KvldKj4CrxKcGpo4DpL6hIpUx0E95LlCorAoiEiDw4LxLza2wTeAjzk1gX9UCbt4SWYMaPbKSvNy3zsGTWiIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGIsYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDk1Ab0li3QZ8iQ0PhpNbusvJ7bSHSzJACjQGwCeabCQCQm9S8XtL5tAgfQlWp2y3oaDAiVzIGsBhDrgvKXAyIPCgkI2cuBrAYQpAUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjay4GsBhCoBRICGAISAhgDGMLTyDgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkBYKAxiLGCKIFjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDU2NDgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNmQ1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMjliNGE3YTkxNDYxMDA3MjU3ODA2MzMwZjNmNzhkMTQ2MTAwODk1NzgwNjM0NjAwMDI4ZjE0NjEwMGEwNTc4MDYzNTc0NjUzMTExNDYxMDBiNzU3ODA2Mzk5ZWVmMDU1MTQ2MTAwY2U1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA3ZTU3NjAwMDgwZmQ1YjUwNjEwMDg3NjEwMGU1NTY1YjAwNWIzNDgwMTU2MTAwOTU1NzYwMDA4MGZkNWI1MDYxMDA5ZTYxMDExYTU2NWIwMDViMzQ4MDE1NjEwMGFjNTc2MDAwODBmZDViNTA2MTAwYjU2MTAxNTI1NjViMDA1YjM0ODAxNTYxMDBjMzU3NjAwMDgwZmQ1YjUwNjEwMGNjNjEwMTdhNTY1YjAwNWIzNDgwMTU2MTAwZGE1NzYwMDA4MGZkNWI1MDYxMDBlMzYxMDFhMjU2NWIwMDViNjEwMGVkNjEwMWRhNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxMDk1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDYwMDAxNTE1NjEwMTE4NTc2MDAwODBmZDViNTY1YjYwMDA2MTAxMjQ2MTAxZTk1NjViODA4MjE1MTUxNTE1ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxNGU1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDU2NWI2MTAxNWE2MTAxZGE1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE3NjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYxMDE4MjYxMDFmOTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTllNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViNjAwMTYxMDFhYzYxMDFlOTU2NWI4MDgyMTUxNTE1MTU4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDFkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYwNDA1MTYwYzE4MDYxMDIwOTgzMzkwMTkwNTY1YjYwNDA1MTYxMDIxMTgwNjEwMmNhODMzOTAxOTA1NjViNjA0MDUxNjA1ZTgwNjEwNGRiODMzOTAxOTA1NjAwNjA4MDYwNDA1MjYwMDE2MDAwNTUzNDgwMTU2MDE0NTc2MDAwODBmZDViNTA2MDllODA2MTAwMjM2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDA0MzYxMDYwM2Y1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMDI2M2FkNmUxNDYwNDQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwNGY1NzYwMDA4MGZkNWI1MDYwNTY2MDZjNTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjAwMDU0ODE1NjAwYTE2NTYyN2E3YTcyMzA1ODIwMWM5ZWQ5MzQ5NjU0YTVkYTNmZGI4MTc5ZGUyYTU1YTcyMDA0YTIwOGM1NGQxYjZmNjMxYjYxYTY3N2EyZGVkNzAwMjk2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MDIwODA2MTAyMTE4MzM5ODEwMTgwNjA0MDUyODEwMTkwODA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgwMTU2MTAwNjQ1NzYxMDA0MTYxMDA5MTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDVkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA2MTAwOGI1NjViNjEwMDZjNjEwMGEwNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwODg1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDViNTA2MTAwYWY1NjViNjA0MDUxNjA1ZTgwNjEwMGYyODMzOTAxOTA1NjViNjA0MDUxNjBjMTgwNjEwMTUwODMzOTAxOTA1NjViNjAzNTgwNjEwMGJkNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDE1OGRhMDliMmQyMGQyNDRiOWE2NDlmMThmMGM5NzFmODhiZjE3Y2Y1ZWFkNzBkYzU4MTNlOGQzZjllNjBmZmQwMDI5NjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDAxNTE1NjAxYzU3NjAwMDgwZmQ1YjYwMzU4MDYwMjk2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDAwODBmZDAwYTE2NTYyN2E3YTcyMzA1ODIwMzE4ZDE3OTZlYjQxODI2Nzk2NTBlZDFmMzY5ZThlNWJjZGFjM2U2NTRlNDJkZTYzNTExYzUxODRjYmI0NzM2YzAwMjk2MDgwNjA0MDUyNjAwMTYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwOWU4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwMjYzYWQ2ZTE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNTQ4MTU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxYzllZDkzNDk2NTRhNWRhM2ZkYjgxNzlkZTJhNTVhNzIwMDRhMjA4YzU0ZDFiNmY2MzFiNjFhNjc3YTJkZWQ3MDAyOTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDAwMTUxNTYwMWM1NzYwMDA4MGZkNWI2MDM1ODA2MDI5NjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDMxOGQxNzk2ZWI0MTgyNjc5NjUwZWQxZjM2OWU4ZTViY2RhYzNlNjU0ZTQyZGU2MzUxMWM1MTg0Y2JiNDczNmMwMDI5YTE2NTYyN2E3YTcyMzA1ODIwNTM0NmVhYjgxOGYwNGM0ZDQxNmZhMTBiN2ZjZjA5Yzc0MTNiYzBmNGMzZGViMTY2NDJjOGFkMzIxMDU4MmJmOTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwWnrIARp+TG+4fUYbUYU2hRc0RRblEpOyPEVr4nj7pPMfMooXpjEJZOgCuwabt+6HGgwIlsyBrAYQo4WcvAEiDwoJCNrLgawGEKgFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjay4GsBhCqBRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGIsYGiISIH0jPOW+KFeHN3C5jECg/ADHqi6Iz4+PYNZ7l8cxHQi3IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGIwYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDz35GWXb4AnQULyYq5r4wvc0Ql/a2/eBnap+b3vrbe3/PHD5GVXxKeqsJl/w+UOEgaDAiWzIGsBhCbs769AyIPCgkI2suBrAYQqgUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQpkNCgMYjBgS5ApggGBAUmAENhBhAG1XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYym0p6kUYQByV4BjMPP3jRRhAIlXgGNGAAKPFGEAoFeAY1dGUxEUYQC3V4Bjme7wVRRhAM5XW2AAgP1bNIAVYQB+V2AAgP1bUGEAh2EA5VZbAFs0gBVhAJVXYACA/VtQYQCeYQEaVlsAWzSAFWEArFdgAID9W1BhALVhAVJWWwBbNIAVYQDDV2AAgP1bUGEAzGEBelZbAFs0gBVhANpXYACA/VtQYQDjYQGiVlsAW2EA7WEB2lZbYEBRgJEDkGAA8IAVgBVhAQlXPWAAgD49YAD9W1BQYAAVFWEBGFdgAID9W1ZbYABhASRhAelWW4CCFRUVFYFSYCABkVBQYEBRgJEDkGAA8IAVgBVhAU5XPWAAgD49YAD9W1BQVlthAVphAdpWW2BAUYCRA5BgAPCAFYAVYQF2Vz1gAIA+PWAA/VtQUFZbYQGCYQH5VltgQFGAkQOQYADwgBWAFWEBnlc9YACAPj1gAP1bUFBWW2ABYQGsYQHpVluAghUVFRWBUmAgAZFQUGBAUYCRA5BgAPCAFYAVYQHWVz1gAIA+PWAA/VtQUFZbYEBRYMGAYQIJgzkBkFZbYEBRYQIRgGECyoM5AZBWW2BAUWBegGEE24M5AZBWAGCAYEBSYAFgAFU0gBVgFFdgAID9W1BgnoBhACNgADlgAPMAYIBgQFJgBDYQYD9XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYwJjrW4UYERXW2AAgP1bNIAVYE9XYACA/VtQYFZgbFZbYEBRgIKBUmAgAZFQUGBAUYCRA5DzW2AAVIFWAKFlYnp6cjBYIBye2TSWVKXaP9uBed4qVacgBKIIxU0bb2MbYaZ3ot7XAClggGBAUjSAFWEAEFdgAID9W1BgQFFgIIBhAhGDOYEBgGBAUoEBkICAUZBgIAGQkpGQUFBQgBVhAGRXYQBBYQCRVltgQFGAkQOQYADwgBWAFWEAXVc9YACAPj1gAP1bUFBhAItWW2EAbGEAoFZbYEBRgJEDkGAA8IAVgBVhAIhXPWAAgD49YAD9W1BQW1BhAK9WW2BAUWBegGEA8oM5AZBWW2BAUWDBgGEBUIM5AZBWW2A1gGEAvWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggFY2gmy0g0kS5pknxjwyXH4i/F89erXDcWBPo0/nmD/0AKWCAYEBSNIAVYA9XYACA/VtQYAAVFWAcV2AAgP1bYDWAYClgADlgAPMAYIBgQFJgAID9AKFlYnp6cjBYIDGNF5brQYJnllDtHzaejlvNrD5lTkLeY1EcUYTLtHNsAClggGBAUmABYABVNIAVYBRXYACA/VtQYJ6AYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMCY61uFGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAFSBVgChZWJ6enIwWCAcntk0llSl2j/bgXneKlWnIASiCMVNG29jG2Gmd6Le1wApYIBgQFI0gBVgD1dgAID9W1BgABUVYBxXYACA/VtgNYBgKWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggMY0XlutBgmeWUO0fNp6OW82sPmVOQt5jURxRhMu0c2wAKaFlYnp6cjBYIFNG6rgY8ExNQW+hC3/PCcdBO8D0w96xZkLIrTIQWCv5ACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGIwYShYKFAAAAAAAAAAAAAAAAAAAAAAAAAwMcgcKAxiMGBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjby4GsBhCsBRICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYjBgQ4M0vIgQptKep","b64Record":"CiUIISIDGIwYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCIipAOEbYohVjZmIebcEje2K+jcgYoVPdRieZRT6tH2g/kTkbuc4Tm6dzUJM1rejEaDAiXzIGsBhCztYjGASIPCgkI28uBrAYQrAUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOggaAjB4KICLJlIWCgkKAhgCEP+ZoCoKCQoCGGIQgJqgKg=="},{"b64Body":"Cg8KCQjby4GsBhCuBRICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYjBgQ4M0vIgRGAAKP","b64Record":"CiUIFiIDGIwYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAfA2zFj+B5Braq8VKiC5cHollAx3dsiYuHZiQSkUvfEDdW/txfJ5YpdrecngbjmYoaDAiXzIGsBhCD2ZbHAyIPCgkI28uBrAYQrgUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOqMCCgMYjBgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICLJjoDGI0YcgcKAxiMGBACcgcKAxiNGBABUhYKCQoCGAIQ/5mgKgoJCgIYYhCAmqAq"},{"b64Body":"ChEKCQjby4GsBhCuBRICGAIgAUI4GiISIH0jPOW+KFeHN3C5jECg/ADHqi6Iz4+PYNZ7l8cxHQi3QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGI0YEjCiZ6/jdvHKgPCQ5dpMXFAmaHu1QeTLtyidH/l/2E7xCl4E94Z3/GtgRIqnK2MKNFkaDAiXzIGsBhCE2ZbHAyIRCgkI28uBrAYQrgUSAhgCIAFCHQoDGI0YShYKFNRjtiFVDto10O/2mOYYcPiTrAytUgB6DAiXzIGsBhCD2ZbHAw=="}]},"ResetOnStackedFactoryFailureWorks":{"placeholderNum":3086,"encodedItems":[{"b64Body":"Cg8KCQjgy4GsBhDMBRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAicmtyvBhCIs7O+ARptCiISICyiSgHSn6dVjEBSazF9qXWE0q2xv0wVLNqZx5IEHB7TCiM6IQOl33WV1NkwfHj1mMfAAAs897rgwPtwK9NhHtsCIRDwIAoiEiBMp3U5/mekSCbuYRgQzpi0FsPeDmqFhlf2S/iY2DrhAyIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGI8YKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB6B6C06py4tizb8ZQnKSadnBZT3uIeXe0uNlOwj11PuDXY324GCKIn1WsXalwm+BUaDAiczIGsBhDb1NzYASIPCgkI4MuBrAYQzAUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjgy4GsBhDQBRICGAISAhgDGMLTyDgiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBkBYKAxiPGCKIFjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDU2NDgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNmQ1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMjliNGE3YTkxNDYxMDA3MjU3ODA2MzMwZjNmNzhkMTQ2MTAwODk1NzgwNjM0NjAwMDI4ZjE0NjEwMGEwNTc4MDYzNTc0NjUzMTExNDYxMDBiNzU3ODA2Mzk5ZWVmMDU1MTQ2MTAwY2U1NzViNjAwMDgwZmQ1YjM0ODAxNTYxMDA3ZTU3NjAwMDgwZmQ1YjUwNjEwMDg3NjEwMGU1NTY1YjAwNWIzNDgwMTU2MTAwOTU1NzYwMDA4MGZkNWI1MDYxMDA5ZTYxMDExYTU2NWIwMDViMzQ4MDE1NjEwMGFjNTc2MDAwODBmZDViNTA2MTAwYjU2MTAxNTI1NjViMDA1YjM0ODAxNTYxMDBjMzU3NjAwMDgwZmQ1YjUwNjEwMGNjNjEwMTdhNTY1YjAwNWIzNDgwMTU2MTAwZGE1NzYwMDA4MGZkNWI1MDYxMDBlMzYxMDFhMjU2NWIwMDViNjEwMGVkNjEwMWRhNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxMDk1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDYwMDAxNTE1NjEwMTE4NTc2MDAwODBmZDViNTY1YjYwMDA2MTAxMjQ2MTAxZTk1NjViODA4MjE1MTUxNTE1ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAxNGU1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDU2NWI2MTAxNWE2MTAxZGE1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDE3NjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYxMDE4MjYxMDFmOTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMTllNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA1NjViNjAwMTYxMDFhYzYxMDFlOTU2NWI4MDgyMTUxNTE1MTU4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDFkNjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTY1YjYwNDA1MTYwYzE4MDYxMDIwOTgzMzkwMTkwNTY1YjYwNDA1MTYxMDIxMTgwNjEwMmNhODMzOTAxOTA1NjViNjA0MDUxNjA1ZTgwNjEwNGRiODMzOTAxOTA1NjAwNjA4MDYwNDA1MjYwMDE2MDAwNTUzNDgwMTU2MDE0NTc2MDAwODBmZDViNTA2MDllODA2MTAwMjM2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDA0MzYxMDYwM2Y1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMDI2M2FkNmUxNDYwNDQ1NzViNjAwMDgwZmQ1YjM0ODAxNTYwNGY1NzYwMDA4MGZkNWI1MDYwNTY2MDZjNTY1YjYwNDA1MTgwODI4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBmMzViNjAwMDU0ODE1NjAwYTE2NTYyN2E3YTcyMzA1ODIwMWM5ZWQ5MzQ5NjU0YTVkYTNmZGI4MTc5ZGUyYTU1YTcyMDA0YTIwOGM1NGQxYjZmNjMxYjYxYTY3N2EyZGVkNzAwMjk2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDQwNTE2MDIwODA2MTAyMTE4MzM5ODEwMTgwNjA0MDUyODEwMTkwODA4MDUxOTA2MDIwMDE5MDkyOTE5MDUwNTA1MDgwMTU2MTAwNjQ1NzYxMDA0MTYxMDA5MTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDVkNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwNTA2MTAwOGI1NjViNjEwMDZjNjEwMGEwNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwODg1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDViNTA2MTAwYWY1NjViNjA0MDUxNjA1ZTgwNjEwMGYyODMzOTAxOTA1NjViNjA0MDUxNjBjMTgwNjEwMTUwODMzOTAxOTA1NjViNjAzNTgwNjEwMGJkNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDE1OGRhMDliMmQyMGQyNDRiOWE2NDlmMThmMGM5NzFmODhiZjE3Y2Y1ZWFkNzBkYzU4MTNlOGQzZjllNjBmZmQwMDI5NjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwMDAxNTE1NjAxYzU3NjAwMDgwZmQ1YjYwMzU4MDYwMjk2MDAwMzk2MDAwZjMwMDYwODA2MDQwNTI2MDAwODBmZDAwYTE2NTYyN2E3YTcyMzA1ODIwMzE4ZDE3OTZlYjQxODI2Nzk2NTBlZDFmMzY5ZThlNWJjZGFjM2U2NTRlNDJkZTYzNTExYzUxODRjYmI0NzM2YzAwMjk2MDgwNjA0MDUyNjAwMTYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwOWU4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwMjYzYWQ2ZTE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNTQ4MTU2MDBhMTY1NjI3YTdhNzIzMDU4MjAxYzllZDkzNDk2NTRhNWRhM2ZkYjgxNzlkZTJhNTVhNzIwMDRhMjA4YzU0ZDFiNmY2MzFiNjFhNjc3YTJkZWQ3MDAyOTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDAwMTUxNTYwMWM1NzYwMDA4MGZkNWI2MDM1ODA2MDI5NjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwMDgwZmQwMGExNjU2MjdhN2E3MjMwNTgyMDMxOGQxNzk2ZWI0MTgyNjc5NjUwZWQxZjM2OWU4ZTViY2RhYzNlNjU0ZTQyZGU2MzUxMWM1MTg0Y2JiNDczNmMwMDI5YTE2NTYyN2E3YTcyMzA1ODIwNTM0NmVhYjgxOGYwNGM0ZDQxNmZhMTBiN2ZjZjA5Yzc0MTNiYzBmNGMzZGViMTY2NDJjOGFkMzIxMDU4MmJmOTAwMjk=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw67tfDnm0g1rpiy/JysCila3YLewj/SMz8BR33JTcF1viFfVpB16ITOJTwe2W49pGGgwInMyBrAYQw97HvQMiDwoJCODLgawGENAFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjhy4GsBhDSBRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGI8YGiISIAztFEPKtx0TsTnRZEQsq/+OWppddZR4By2hyLDQOpFOIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJAYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBcbv29GA/wBcQO3SLZw8juy1yhm067sO9jowHtxxeukC5HsvvifUhzAeMPZ/+DoeEaDAidzIGsBhDzlKfiASIPCgkI4cuBrAYQ0gUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQpkNCgMYkBgS5ApggGBAUmAENhBhAG1XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYym0p6kUYQByV4BjMPP3jRRhAIlXgGNGAAKPFGEAoFeAY1dGUxEUYQC3V4Bjme7wVRRhAM5XW2AAgP1bNIAVYQB+V2AAgP1bUGEAh2EA5VZbAFs0gBVhAJVXYACA/VtQYQCeYQEaVlsAWzSAFWEArFdgAID9W1BhALVhAVJWWwBbNIAVYQDDV2AAgP1bUGEAzGEBelZbAFs0gBVhANpXYACA/VtQYQDjYQGiVlsAW2EA7WEB2lZbYEBRgJEDkGAA8IAVgBVhAQlXPWAAgD49YAD9W1BQYAAVFWEBGFdgAID9W1ZbYABhASRhAelWW4CCFRUVFYFSYCABkVBQYEBRgJEDkGAA8IAVgBVhAU5XPWAAgD49YAD9W1BQVlthAVphAdpWW2BAUYCRA5BgAPCAFYAVYQF2Vz1gAIA+PWAA/VtQUFZbYQGCYQH5VltgQFGAkQOQYADwgBWAFWEBnlc9YACAPj1gAP1bUFBWW2ABYQGsYQHpVluAghUVFRWBUmAgAZFQUGBAUYCRA5BgAPCAFYAVYQHWVz1gAIA+PWAA/VtQUFZbYEBRYMGAYQIJgzkBkFZbYEBRYQIRgGECyoM5AZBWW2BAUWBegGEE24M5AZBWAGCAYEBSYAFgAFU0gBVgFFdgAID9W1BgnoBhACNgADlgAPMAYIBgQFJgBDYQYD9XYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYwJjrW4UYERXW2AAgP1bNIAVYE9XYACA/VtQYFZgbFZbYEBRgIKBUmAgAZFQUGBAUYCRA5DzW2AAVIFWAKFlYnp6cjBYIBye2TSWVKXaP9uBed4qVacgBKIIxU0bb2MbYaZ3ot7XAClggGBAUjSAFWEAEFdgAID9W1BgQFFgIIBhAhGDOYEBgGBAUoEBkICAUZBgIAGQkpGQUFBQgBVhAGRXYQBBYQCRVltgQFGAkQOQYADwgBWAFWEAXVc9YACAPj1gAP1bUFBhAItWW2EAbGEAoFZbYEBRgJEDkGAA8IAVgBVhAIhXPWAAgD49YAD9W1BQW1BhAK9WW2BAUWBegGEA8oM5AZBWW2BAUWDBgGEBUIM5AZBWW2A1gGEAvWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggFY2gmy0g0kS5pknxjwyXH4i/F89erXDcWBPo0/nmD/0AKWCAYEBSNIAVYA9XYACA/VtQYAAVFWAcV2AAgP1bYDWAYClgADlgAPMAYIBgQFJgAID9AKFlYnp6cjBYIDGNF5brQYJnllDtHzaejlvNrD5lTkLeY1EcUYTLtHNsAClggGBAUmABYABVNIAVYBRXYACA/VtQYJ6AYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMCY61uFGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAFSBVgChZWJ6enIwWCAcntk0llSl2j/bgXneKlWnIASiCMVNG29jG2Gmd6Le1wApYIBgQFI0gBVgD1dgAID9W1BgABUVYBxXYACA/VtgNYBgKWAAOWAA8wBggGBAUmAAgP0AoWVienpyMFggMY0XlutBgmeWUO0fNp6OW82sPmVOQt5jURxRhMu0c2wAKaFlYnp6cjBYIFNG6rgY8ExNQW+hC3/PCcdBO8D0w96xZkLIrTIQWCv5ACkigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGJAYShYKFAAAAAAAAAAAAAAAAAAAAAAAAAwQcgcKAxiQGBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQjhy4GsBhDUBRICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYkBgQ4M0vIgSZ7vBV","b64Record":"CiUIISIDGJAYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCfOqKSfYpe1G2aKju4iu65On7bhAkbz3wPAn0dE1pg57h8JRRdNL0V2foRVn/T/OsaDAidzIGsBhCDlK3HAyIPCgkI4cuBrAYQ1AUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOggaAjB4KICLJlIWCgkKAhgCEP+ZoCoKCQoCGGIQgJqgKg=="},{"b64Body":"Cg8KCQjiy4GsBhDWBRICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYkBgQ4M0vIgRGAAKP","b64Record":"CiUIFiIDGJAYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBEXUNKEZOrMhwPEz3lv+0D4MW6MCd95w9KzRQWSfMsHeHsr9tl7bcgEM0Qubjruc4aDAiezIGsBhDjnr/sASIPCgkI4suBrAYQ1gUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOqMCCgMYkBgigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKICLJjoDGJEYcgcKAxiQGBACcgcKAxiRGBABUhYKCQoCGAIQ/5mgKgoJCgIYYhCAmqAq"},{"b64Body":"ChEKCQjiy4GsBhDWBRICGAIgAUI4GiISIAztFEPKtx0TsTnRZEQsq/+OWppddZR4By2hyLDQOpFOQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJEYEjDuSmdSSqzn9iQmKfw6zOljvbMe7EgLh5PwiRf2kCRvdO3+lZibAiTEOMqSbQuiVnAaDAiezIGsBhDknr/sASIRCgkI4suBrAYQ1gUSAhgCIAFCHQoDGJEYShYKFL+QkevFQ0NlCEet9JCd31yAbO8bUgB6DAiezIGsBhDjnr/sAQ=="}]},"InheritanceOfNestedCreatedContracts":{"placeholderNum":3090,"encodedItems":[{"b64Body":"Cg8KCQjmy4GsBhD0BRICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAiimtyvBhDYt87MAxptCiISIH0Xk1mgI0CVmhEknpsPkG/QXQ8K/SOZ9TGWx1idMvagCiM6IQP8qTGqwiKKRkeJNYinZBQnWKKq5fWJ4S1puJSfn8rLpwoiEiB+z8uNMXpvxbdKwzeKSjlfX14pFuF5QdbaURzvNB6AkSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJMYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAFJxlWVRvhHLjFvKCVxaN5et8m6Ds8lUUO3AW2DeikFvyHAQxKBMQVnKqDdoV44mUaDAiizIGsBhDblq/aAyIPCgkI5suBrAYQ9AUSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQjny4GsBhD4BRICGAISAhgDGLDV0DUiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB7hAKAxiTGCLmEDYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYwMDA2MDQwNTE2MTAwMWY5MDYxMDA5MTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDNiNTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwOTA1MDdmN2I0YjU1NzY4ODIzMThmMzAyNWVkZTNiNDUyNWE2OTJiOWE5NzkyNjc0YzZiZDBmODJjZTYyODQ1Mzc0YTkyMTgxNjA0MDUxODA4MjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTE1MDYxMDA5ZTU2NWI2MTAxNDY4MDYxMDJlZDgzMzkwMTkwNTY1YjYxMDI0MDgwNjEwMGFkNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2MzM3NGQ1MTUxMTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDAzODYxMDAzYTU2NWIwMDViNjAwMDYwNDA1MTYxMDA0ODkwNjEwMGI3NTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwNjQ1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwN2Y3YjRiNTU3Njg4MjMxOGYzMDI1ZWRlM2I0NTI1YTY5MmI5YTk3OTI2NzRjNmJkMGY4MmNlNjI4NDUzNzRhOTIxODE2MDQwNTE4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBhMTUwNTY1YjYxMDE0NjgwNjEwMGM1ODMzOTAxOTA1NmZlNjA4MDYwNDA1MjM0ODAxNTYxMDAxMDU3NjAwMDgwZmQ1YjUwNjAwMDYwNDA1MTYxMDAxZjkwNjEwMDkxNTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwM2I1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA5MDUwN2Y0OGFlMTljMTI3MTU1ZDBhMzRkNjA4ODNhZmVhM2Y5ZjU5ODU4ZjQ3ZDE1YTAyYzRhYTQzN2QxZmY3NmY1ZDZmODE2MDQwNTE4MDgyNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY4MTUyNjAyMDAxOTE1MDUwNjA0MDUxODA5MTAzOTBhMTUwNjEwMDlkNTY1YjYwNWM4MDYxMDBlYTgzMzkwMTkwNTY1YjYwM2Y4MDYxMDBhYjYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDA4MGZkZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDk0NzJkNjdhMGNjY2E5ZGZhNzI0OGUwNTJmMzg5NDZkYjdiYWE1YzY5ZjMyOWFjMDg4NDRhYzZiOTBiM2RjMzE2NDczNmY2YzYzNDMwMDA3MDYwMDMzNjA4MDYwNDA1MjM0ODAxNTYwMGY1NzYwMDA4MGZkNWI1MDYwM2Y4MDYwMWQ2MDAwMzk2MDAwZjNmZTYwODA2MDQwNTI2MDAwODBmZGZlYTI2NDY5NzA2NjczNTgyMjEyMjA4MGQxOTRhNTkwNWM2NDIwYTJhNThlNDVmZGU0ZTJlNzQyNjRlYTQ1YzIyYzUzMmNhYTVjZDA2NDVmMGY4NDY0NjQ3MzZmNmM2MzQzMDAwNzA2MDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwNjdmNzFkNWU3NTA4YjBhMTg1ZDM5NGViNmJhZmJjZDRlMmFmOWMzMThlMWFhNDcwMzAwOWNjNWY4YWM4ZTA5YTY0NzM2ZjZjNjM0MzAwMDcwNjAwMzM2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDAwNjA0MDUxNjEwMDFmOTA2MTAwOTE1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDAzYjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA3ZjQ4YWUxOWMxMjcxNTVkMGEzNGQ2MDg4M2FmZWEzZjlmNTk4NThmNDdkMTVhMDJjNGFhNDM3ZDFmZjc2ZjVkNmY4MTYwNDA1MTgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGExNTA2MTAwOWQ1NjViNjA1YzgwNjEwMGVhODMzOTAxOTA1NjViNjAzZjgwNjEwMGFiNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwMDgwZmRmZWEyNjQ2OTcwNjY3MzU4MjIxMjIwOTQ3MmQ2N2EwY2NjYTlkZmE3MjQ4ZTA1MmYzODk0NmRiN2JhYTVjNjlmMzI5YWMwODg0NGFjNmI5MGIzZGMzMTY0NzM2ZjZjNjM0MzAwMDcwNjAwMzM2MDgwNjA0MDUyMzQ4MDE1NjAwZjU3NjAwMDgwZmQ1YjUwNjAzZjgwNjAxZDYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDA4MGZkZmVhMjY0Njk3MDY2NzM1ODIyMTIyMDgwZDE5NGE1OTA1YzY0MjBhMmE1OGU0NWZkZTRlMmU3NDI2NGVhNDVjMjJjNTMyY2FhNWNkMDY0NWYwZjg0NjQ2NDczNmY2YzYzNDMwMDA3MDYwMDMz","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwSu866mbo2cD4sXsUWmw/qNG0GvZNSU7tWAG9P1ERc4cohWOFfyYbotokhekJG9qxGgwIo8yBrAYQk5iD/wEiDwoJCOfLgawGEPgFEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjny4GsBhD6BRICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJMYGiISIJBrTFHd40jiBBNZxtW6OP9nVeVrpm6epq72hlGCiL+9IJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJQYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBE8XAoDKZe3V9x/cAvhTPJO+fg1VF0KZI86xqhLD/Af21CSfPkmnnRziIiQ3v02JsaCwikzIGsBhCT4+QGIg8KCQjny4GsBhD6BRICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMMDZ4gZCrwwKAxiUGBLABGCAYEBSNIAVYQAQV2AAgP1bUGAENhBhACtXYAA1YOAcgGM3TVFRFGEAMFdbYACA/VthADhhADpWWwBbYABgQFFhAEiQYQC3VltgQFGAkQOQYADwgBWAFWEAZFc9YACAPj1gAP1bUJBQf3tLVXaIIxjzAl7eO0UlppK5qXkmdMa9D4LOYoRTdKkhgWBAUYCCc///////////////////////////FoFSYCABkVBQYEBRgJEDkKFQVlthAUaAYQDFgzkBkFb+YIBgQFI0gBVhABBXYACA/VtQYABgQFFhAB+QYQCRVltgQFGAkQOQYADwgBWAFWEAO1c9YACAPj1gAP1bUJBQf0iuGcEnFV0KNNYIg6/qP59ZhY9H0VoCxKpDfR/3b11vgWBAUYCCc///////////////////////////FoFSYCABkVBQYEBRgJEDkKFQYQCdVltgXIBhAOqDOQGQVltgP4BhAKtgADlgAPP+YIBgQFJgAID9/qJkaXBmc1giEiCUctZ6DMyp36ckjgUvOJRtt7qlxp8ymsCIRKxrkLPcMWRzb2xjQwAHBgAzYIBgQFI0gBVgD1dgAID9W1BgP4BgHWAAOWAA8/5ggGBAUmAAgP3+omRpcGZzWCISIIDRlKWQXGQgoqWORf3k4udCZOpFwixTLKpc0GRfD4RkZHNvbGNDAAcGADOiZGlwZnNYIhIgZ/cdXnUIsKGF05Tra6+81OKvnDGOGqRwMAnMX4rI4Jpkc29sY0MABwYAMyKAAgAAAAAAAAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAARAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAQAAAAAAowJoMMswCCgMYlRgSgAIAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGiBIrhnBJxVdCjTWCIOv6j+fWYWPR9FaAsSqQ30f929dbyIgAAAAAAAAAAAAAAAAftUIJVlC2+bFL4n4jHczwYFQjAgyzAIKAxiUGBKAAgAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAQAAAAAAaIHtLVXaIIxjzAl7eO0UlppK5qXkmdMa9D4LOYoRTdKkhIiAAAAAAAAAAAAAAAAClwOM39edFfloEIvMAnVgOlfJeZzoDGJQYOgMYlRg6AxiWGEoWChQAAAAAAAAAAAAAAAAAAAAAAAAMFHIHCgMYlBgQAnIHCgMYlRgQAnIHCgMYlhgQAVIWCgkKAhgCEP+yxQ0KCQoCGGIQgLPFDQ=="},{"b64Body":"ChEKCQjny4GsBhD6BRICGAIgAUI4GiISIJBrTFHd40jiBBNZxtW6OP9nVeVrpm6epq72hlGCiL+9QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJYYEjCzPypOZQufTPOKue8mMMO7pbrFuE2ztl+nTxxo3owRbpDPjshdkuMEJbq9yEOvqHAaCwikzIGsBhCU4+QGIhEKCQjny4GsBhD6BRICGAIgAUIdCgMYlhhKFgoUftUIJVlC2+bFL4n4jHczwYFQjAhSAHoLCKTMgawGEJPj5AY="},{"b64Body":"ChEKCQjny4GsBhD6BRICGAIgAkI4GiISIJBrTFHd40jiBBNZxtW6OP9nVeVrpm6epq72hlGCiL+9QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJUYEjC3ZtszPsqFS02cyIuJiQuGfJ04XSlBIIlP6gvm1YzxxqCeJUzoGhlfpz1irm+Nl9YaCwikzIGsBhCV4+QGIhEKCQjny4GsBhD6BRICGAIgAkIdCgMYlRhKFgoUpcDjN/XnRX5aBCLzAJ1YDpXyXmdSAHoLCKTMgawGEJPj5AY="},{"b64Body":"Cg8KCQjoy4GsBhCCBhICGAISAhgDGKCQtBoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYlBgQ4M0vIgQ3TVFR","b64Record":"CiUIFiIDGJQYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBE6PFwjrRZ3ZRd8hq3O5ztthy5gKtWn3wqumCSBpsZrHzodu9gWKaNCgLzJUGLfy8aDAikzIGsBhD7jsGIAiIPCgkI6MuBrAYQggYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjCAjZAVOs8HCgMYlBgigAIAAAAAAAAAAgAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAAAAAAAAAAEAAAAAAAAAEQAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAEAAAAAAKICLJjLMAgoDGJcYEoACAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABogSK4ZwScVXQo01giDr+o/n1mFj0fRWgLEqkN9H/dvXW8iIAAAAAAAAAAAAAAAAEkoETM2Whw+64k232D4p2+avLawMswCCgMYlBgSgAIAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAEAAAAAAGiB7S1V2iCMY8wJe3jtFJaaSual5JnTGvQ+CzmKEU3SpISIgAAAAAAAAAAAAAAAAuV6gzN2qm+0LWmn5VZgUbc4xfco6AxiXGDoDGJgYcgcKAxiUGBADcgcKAxiXGBACcgcKAxiYGBABUhYKCQoCGAIQ/5mgKgoJCgIYYhCAmqAq"},{"b64Body":"ChEKCQjoy4GsBhCCBhICGAIgAUI4GiISIJBrTFHd40jiBBNZxtW6OP9nVeVrpm6epq72hlGCiL+9QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJgYEjCXDuSKrcpsntupMjl7PQfqltFHebvCpifqt5UlkBrSklWoYl4+q8MP4ms8FZjgJ8IaDAikzIGsBhD8jsGIAiIRCgkI6MuBrAYQggYSAhgCIAFCHQoDGJgYShYKFEkoETM2Whw+64k232D4p2+avLawUgB6DAikzIGsBhD7jsGIAg=="},{"b64Body":"ChEKCQjoy4GsBhCCBhICGAIgAkI4GiISIJBrTFHd40jiBBNZxtW6OP9nVeVrpm6epq72hlGCiL+9QgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGJcYEjBLCjEwEQR6aAdXVVx+KVouR/s92luNvsDIujlr0Y+XUuQqGj/4/y52zRz3plWOP8QaDAikzIGsBhD9jsGIAiIRCgkI6MuBrAYQggYSAhgCIAJCHQoDGJcYShYKFLleoMzdqpvtC1pp+VWYFG3OMX3KUgB6DAikzIGsBhD7jsGIAg=="}]},"FactoryAndSelfDestructInConstructorContract":{"placeholderNum":3097,"encodedItems":[{"b64Body":"Cg8KCQjty4GsBhC4BhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwipmtyvBhDwhboNGm0KIhIgjk5TCRHAZ1WaFLT+rP2+J2or87Wgoj+2PXxXKpsmziQKIzohAvbAIVB7GnLh6MkhAesL2R/jCsLQPQh3trbzXJT+D+haCiISIIiQ8cYLwzBb79/AHC4G1XWttEgtms2JewLzYSgP0H5LIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGJoYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjB3zbs1i6itVZ3bvV/YuhVyxAmYbXEdWIVlbD3h5zIBtVVeHS4E2G609baSx5xySvMaCwipzIGsBhDD5oQWIg8KCQjty4GsBhC4BhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQjty4GsBhC8BhICGAISAhgDGMeA0y4iAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBrAQKAxiaGCKkBDYwODA2MDQwNTI2MDAwNjA0MDUxNjAxMDkwNjBhYTU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjAyYjU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDkwNTA3ZjdiNGI1NTc2ODgyMzE4ZjMwMjVlZGUzYjQ1MjVhNjkyYjlhOTc5MjY3NGM2YmQwZjgyY2U2Mjg0NTM3NGE5MjE4MTYwNDA1MTgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwYTEzMzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ZmY1YjYwNWI4MDYxMDBiNzgzMzkwMTkwNTZmZTYwODA2MDQwNTIzNDgwMTU2MDBmNTc2MDAwODBmZDViNTA2MDNlODA2MDFkNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwMDgwZmRmZWEyNjU2MjdhN2E3MjMxNTgyMGE3ZDg0ZjhiNzEwNjMxMjA3NWNmN2E5YTgzODI2OTRjNzc4MWYxOWQ3MTExZGRhZDQxMTc4NjZkM2MzNTVmNzQ2NDczNmY2YzYzNDMwMDA1MTEwMDMy","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwc2c/XZE5AUuAq8lANx3+K7pitmmygtzrOBv8ABeIU7HclucceeCf5L+16wHIcyikGgwIqcyBrAYQq5GOlwIiDwoJCO3LgawGELwGEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQjuy4GsBhC+BhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIGS5vdkKbINqz40eFxZ6ag8X9H65mwy4uBNmCg0hdhtEEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGJsYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCsdUaqbKh+SYyfG0VIgIuY9BkV5Wtqxnoy3MzoPUp7SLn5E4XRcEuD09eqMIkV5r0aCwiqzIGsBhCT8+oeIg8KCQjuy4GsBhC+BhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGJsYEICQ38BK"},{"b64Body":"ChAKCQjuy4GsBhDABhIDGJsYEgIYAxiW+66dAiICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOQkcKAxiaGBoiEiA9RRe387MUjUQLkmps/1CKpV4kDc/Fa5bMs3/PcpfqbyCQoQ8oCkIFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGJwYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBewLk0sqofbJ2WLqcdLftYPKyeKoL9Ypzqak/8iZdRfKlHpWreG2AdSK2vrsbNG00aDAiqzIGsBhCT6KygAiIQCgkI7suBrAYQwAYSAxibGCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4w1tSRpAJChgUKAxicGCKAAgAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAowJoMMswCCgMYnBgSgAIAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAGiB7S1V2iCMY8wJe3jtFJaaSual5JnTGvQ+CzmKEU3SpISIgAAAAAAAAAAAAAAAAnVLKgXnj2ns3v1v8r/8/xtqQNs86AxicGDoDGJ0YShYKFAAAAAAAAAAAAAAAAAAAAAAAAAwccgcKAxidGBABUjAKCQoCGAMQmOKJFAoKCgIYYhD6kZH9AwoKCgMYoAYQmrWINwoLCgMYmxgQq6mjyAQ="},{"b64Body":"ChIKCQjuy4GsBhDABhIDGJsYIAFCOBoiEiA9RRe387MUjUQLkmps/1CKpV4kDc/Fa5bMs3/Pcpfqb0IFCIDO2gNqC2NlbGxhciBkb29y","b64Record":"CgcIFiIDGJ0YEjAaPf/2msCUvNXyYnHkudIecenQHFPbkuAS7SUxRUxWfRRi0+i8wWyH9jZDmxFnMg4aDAiqzIGsBhCU6KygAiISCgkI7suBrAYQwAYSAxibGCABQh0KAxidGEoWChSdUsqBeePaeze/W/yv/z/G2pA2z1IAegwIqsyBrAYQk+isoAI="}]},"FactoryQuickSelfDestructContract":{"placeholderNum":3102,"encodedItems":[{"b64Body":"Cg8KCQjzy4GsBhDUBhICGAISAhgDGLXNziwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAivmtyvBhCIncGdAhptCiISIBXpvMF3qsuLfPAr3FaPgpV6G8YsrC9qgK+Ss7YtN3I7CiM6IQN7eJTgfjOSUwEJQyxJUcLt1fvvZ/IhaytDzvup0memIgoiEiAKseMjIPiNk1IFUT05bURSG0DVC5LkL79aaYtxTgyzPSIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGJ8YKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAZ5wRd0smJU5DID92ZWiaekmemL0OynjeVQfJncrS1UftiaZjWTazD9nJ7cttEz4waDAivzIGsBhCjycGzAiIPCgkI88uBrAYQ1AYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj0y4GsBhDYBhICGAISAhgDGLWzoTQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBsg4KAxifGCKqDjYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM3NTgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyNjAwNDM2MTA2MTAwMWU1NzYwMDAzNTYwZTAxYzgwNjMzZjU1MzRmMjE0NjEwMDIzNTc1YjYwMDA4MGZkNWI2MTAwMmI2MTAwMmQ1NjViMDA1YjYwMDA2MDQwNTE2MTAwM2I5MDYxMDBmZjU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDU3NTczZDYwMDA4MDNlM2Q2MDAwZmQ1YjUwOTA1MDdmN2I0YjU1NzY4ODIzMThmMzAyNWVkZTNiNDUyNWE2OTJiOWE5NzkyNjc0YzZiZDBmODJjZTYyODQ1Mzc0YTkyMTgxNjA0MDUxNjEwMDg5OTE5MDYxMDEyYTU2NWI2MDQwNTE4MDkxMDM5MGExODA3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzYjljYWViZjQzMzYwNDA1MTgyNjNmZmZmZmZmZjE2NjBlMDFiODE1MjYwMDQwMTYxMDBjYTkxOTA2MTAxNDU1NjViNjAwMDYwNDA1MTgwODMwMzgxNjAwMDg3ODAzYjE1ODAxNTYxMDBlNDU3NjAwMDgwZmQ1YjUwNWFmMTE1ODAxNTYxMDBmODU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDUwNTA1MDUwNTY1YjYxMDE3NzgwNjEwMWM5ODMzOTAxOTA1NjViNjEwMTE1ODE2MTAxOTI1NjViODI1MjUwNTA1NjViNjEwMTI0ODE2MTAxNjA1NjViODI1MjUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDEzZjYwMDA4MzAxODQ2MTAxMWI1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4MjAxOTA1MDYxMDE1YTYwMDA4MzAxODQ2MTAxMGM1NjViOTI5MTUwNTA1NjViNjAwMDYxMDE2YjgyNjEwMTcyNTY1YjkwNTA5MTkwNTA1NjViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA2MTAxOWQ4MjYxMDFhNDU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTAxYWY4MjYxMDFiNjU2NWI5MDUwOTE5MDUwNTY1YjYwMDA2MTAxYzE4MjYxMDE3MjU2NWI5MDUwOTE5MDUwNTZmZTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDE1NzgwNjEwMDIwNjAwMDM5NjAwMGYzZmU2MDgwNjA0MDUyMzQ4MDE1NjEwMDEwNTc2MDAwODBmZDViNTA2MDA0MzYxMDYxMDAyYjU3NjAwMDM1NjBlMDFjODA2M2I5Y2FlYmY0MTQ2MTAwMzA1NzViNjAwMDgwZmQ1YjYxMDA0YTYwMDQ4MDM2MDM4MTAxOTA2MTAwNDU5MTkwNjEwMGE2NTY1YjYxMDA0YzU2NWIwMDViN2ZhZTM3ZGEzMmJjNWUxODQ2NGZmODMxZTE4YjYwODQ3NzBjNmJmNmMwYzc2YTljYjgyZDNjNzZlODdiNGE3MmRhNjA0MDUxNjA0MDUxODA5MTAzOTBhMTgwNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTZmZjViNjAwMDgxMzU5MDUwNjEwMGEwODE2MTAxMGE1NjViOTI5MTUwNTA1NjViNjAwMDYwMjA4Mjg0MDMxMjE1NjEwMGJjNTc2MTAwYmI2MTAxMDU1NjViNWI2MDAwNjEwMGNhODQ4Mjg1MDE2MTAwOTE1NjViOTE1MDUwOTI5MTUwNTA1NjViNjAwMDYxMDBkZTgyNjEwMGU1NTY1YjkwNTA5MTkwNTA1NjViNjAwMDczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjgyMTY5MDUwOTE5MDUwNTY1YjYwMDA4MGZkNWI2MTAxMTM4MTYxMDBkMzU2NWI4MTE0NjEwMTFlNTc2MDAwODBmZDViNTA1NmZlYTI2NDY5NzA2NjczNTgyMjEyMjAzMmFlNjhkYzI1YjFkZmQ5NTkxZmJiNWQwOGZjOWVhYzRhNjFhZWU4MjczNTM1NDZhZjNkNjk2MjA5YzE2MTY4NjQ3MzZmNmM2MzQzMDAwODA3MDAzM2EyNjQ2OTcwNjY3MzU4MjIxMjIwZjExMjcwN2MzMDVlN2MwM2MxZjE1ZjI3MDEzNmFiNWRjMDBmZDAwYWU2YmUxNDBkYjk4NzRjOGViMWRjNDhhNDY0NzM2ZjZjNjM0MzAwMDgwNzAwMzM=","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwhkYS8fkjbahlCvtoHbC1EDVWnc88S+EibXDqnZ97wmsXnRPoBli+Qheuv89rFgRDGgsIsMyBrAYQ87KbPCIPCgkI9MuBrAYQ2AYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQj0y4GsBhDaBhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGJ8YGiISIL8xMoNRODzTUD8syg/cAMmk5qOLEnCKQtcEzHkLMmIGIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKAYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAPs1UmJurbrNHVSV87nSEGY5v8sjZE3zzLXFR2xJOerZliAKUYVl7heb5uy0UZbAQaDAiwzIGsBhC7n9u8AiIPCgkI9MuBrAYQ2gYSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQqoJCgMYoBgS9QZggGBAUmAENhBhAB5XYAA1YOAcgGM/VTTyFGEAI1dbYACA/VthACthAC1WWwBbYABgQFFhADuQYQD/VltgQFGAkQOQYADwgBWAFWEAV1c9YACAPj1gAP1bUJBQf3tLVXaIIxjzAl7eO0UlppK5qXkmdMa9D4LOYoRTdKkhgWBAUWEAiZGQYQEqVltgQFGAkQOQoYBz//////////////////////////8WY7nK6/QzYEBRgmP/////FmDgG4FSYAQBYQDKkZBhAUVWW2AAYEBRgIMDgWAAh4A7FYAVYQDkV2AAgP1bUFrxFYAVYQD4Vz1gAIA+PWAA/VtQUFBQUFZbYQF3gGEByYM5AZBWW2EBFYFhAZJWW4JSUFBWW2EBJIFhAWBWW4JSUFBWW2AAYCCCAZBQYQE/YACDAYRhARtWW5KRUFBWW2AAYCCCAZBQYQFaYACDAYRhAQxWW5KRUFBWW2AAYQFrgmEBclZbkFCRkFBWW2AAc///////////////////////////ghaQUJGQUFZbYABhAZ2CYQGkVluQUJGQUFZbYABhAa+CYQG2VluQUJGQUFZbYABhAcGCYQFyVluQUJGQUFb+YIBgQFI0gBVhABBXYACA/VtQYQFXgGEAIGAAOWAA8/5ggGBAUjSAFWEAEFdgAID9W1BgBDYQYQArV2AANWDgHIBjucrr9BRhADBXW2AAgP1bYQBKYASANgOBAZBhAEWRkGEAplZbYQBMVlsAW3+uN9oyvF4YRk/4MeGLYIR3DGv2wMdqnLgtPHboe0py2mBAUWBAUYCRA5ChgHP//////////////////////////xb/W2AAgTWQUGEAoIFhAQpWW5KRUFBWW2AAYCCChAMSFWEAvFdhALthAQVWW1tgAGEAyoSChQFhAJFWW5FQUJKRUFBWW2AAYQDegmEA5VZbkFCRkFBWW2AAc///////////////////////////ghaQUJGQUFZbYACA/VthAROBYQDTVluBFGEBHldgAID9W1BW/qJkaXBmc1giEiAyrmjcJbHf2Vkfu10I/J6sSmGu6Cc1NUavPWliCcFhaGRzb2xjQwAIBwAzomRpcGZzWCISIPEScHwwXnwDwfFfJwE2q13AD9AK5r4UDbmHTI6x3EikZHNvbGNDAAgHADMigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKMCaDDoDGKAYShYKFAAAAAAAAAAAAAAAAAAAAAAAAAwgcgcKAxigGBABUhYKCQoCGAIQ/7LFDQoJCgIYYhCAs8UN"},{"b64Body":"Cg8KCQj1y4GsBhDcBhICGAISAhgDGPuV9hQiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjloxCiISIGaVLmjMlCsqD0HqtO6tKBES2kbe/kEn2kzMB+l74DcrEIDIr6AlSgUIgM7aAw==","b64Record":"CiUIFhIDGKEYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjAbn0LpfnqItBVJqqC/W00otkexjjr1QDeLV4GVlxFC+VsJcOENfbZM7PtnZsX2dkMaCwixzIGsBhDjk5pFIg8KCQj1y4GsBhDcBhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUhkKCgoCGAIQ/4/fwEoKCwoDGKEYEICQ38BK"},{"b64Body":"ChAKCQj1y4GsBhDeBhIDGKEYEgIYAxiA/rWHASICCHgyIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOOhAKAxigGBCAkvQBIgQ/VTTy","b64Record":"CiUIFiIDGKAYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjBTVt0ig5Ltqg65KwLbKgsiQDti8lGHIpbof3P3CIOShY8Tckyr87n8pl1+tCZDoJQaDAixzIGsBhDL++LGAiIQCgkI9cuBrAYQ3gYSAxihGCogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo4wgJirbDqXBwoDGKAYIoACAAAAAAAAAAAAAAAIAAAAAAAAAAAQAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAEAABAAAAAACiAqMMBMswCCgMYoBgSgAIAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAEAAAAAAGiB7S1V2iCMY8wJe3jtFJaaSual5JnTGvQ+CzmKEU3SpISIgAAAAAAAAAAAAAAAAONE3Uo8bULS6OOW7O/+cIYJ1ekEyqgIKAxiiGBKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAaIK432jK8XhhGT/gx4YtghHcMa/bAx2qcuC08duh7SnLaOgMYohhyBwoDGKAYEAJSGQoKCgIYYhCAsNbYAQoLCgMYoRgQ/6/W2AE="},{"b64Body":"ChIKCQj1y4GsBhDeBhIDGKEYIAFCOBoiEiC/MTKDUTg801A/LMoP3ADJpOajixJwikLXBMx5CzJiBkIFCIDO2gNqC2NlbGxhciBkb29y","b64Record":"CgcIFiIDGKIYEjDkHY6i9A8vRnRKdcmf3PI4u/NYVJBEyYcyBTue3TVfgJC0As733JgnluqTfnvnThcaDAixzIGsBhDM++LGAiISCgkI9cuBrAYQ3gYSAxihGCABQh0KAxiiGEoWChQ40TdSjxtQtLo45bs7/5whgnV6QVIAegwIscyBrAYQy/vixgI="}]},"contractCreateWithNewOpInConstructorAbandoningParent":{"placeholderNum":3107,"encodedItems":[{"b64Body":"Cg8KCQj6y4GsBhDyBhICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjgESCwi2mtyvBhCYiNhQGm0KIhIguANy1PoAsD53BOS6MEuJUf91HZBGRlo2CHwFI3o5i/sKIzohA0vDo0ZQxF7B24RSktoufNnczbPFoy2YZgU53qOFifh+CiISIBXqLPtyeDMNacokjWRByYrNedKhiF9uuZIg5Lr/ObENIgxIZWxsbyBXb3JsZCEqADIA","b64Record":"CiUIFhoDGKQYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjADA7BIGB6Q+O2QhvGkQJ56O7WSwogsW4AnRuGb0jPI+QyD0C5rBbnX18njUBy09J4aCwi2zIGsBhDD54tZIg8KCQj6y4GsBhDyBhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOUgA="},{"b64Body":"Cg8KCQj6y4GsBhD2BhICGAISAhgDGK7gpDAiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoBpAcKAxikGCKcBzYwODA2MDQwNTIzNDgwMTU2MTAwMTE1NzYwMDA2MDAwZmQ1YjUwNWI2MDAwNjA0MDUxNjEwMDIxOTA2MTAxMDQ1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDAzZTU3M2Q2MDAwNjAwMDNlM2Q2MDAwZmQ1YjUwOTA1MDYwMDA2MDQwNTE2MTAwNGY5MDYxMDEwNDU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMDZjNTczZDYwMDA2MDAwM2UzZDYwMDBmZDViNTA5MDUwNjAwMDYwNDA1MTYxMDA3ZDkwNjEwMTA0NTY1YjYwNDA1MTgwOTEwMzkwNjAwMGYwODAxNTgwMTU2MTAwOWE1NzNkNjAwMDYwMDAzZTNkNjAwMGZkNWI1MDkwNTA2MDAwNjA0MDUxNjEwMGFiOTA2MTAxMDQ1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDBjODU3M2Q2MDAwNjAwMDNlM2Q2MDAwZmQ1YjUwOTA1MDYwMDA2MDQwNTE2MTAwZDk5MDYxMDEwNDU2NWI2MDQwNTE4MDkxMDM5MDYwMDBmMDgwMTU4MDE1NjEwMGY2NTczZDYwMDA2MDAwM2UzZDYwMDBmZDViNTA5MDUwNTA1MDUwNTA1MDViNjEwMTEwNTY1YjYwNjk4MDYxMDE2NTgzMzkwMTkwNTY1YjYwNDc4MDYxMDExZTYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjAwYzU3NWI2MDAwNjAwMGZkZmVhMjY1NjI3YTdhNzIzMTU4MjAwM2NmZTQ1ODYxNjlkMTc1Zjk5ZWQxOTNkZTkyNWJhMzE5MDk3Y2JjODJjZWYwMDEzN2NkYjRjMTY2M2Y4NTZhNjQ3MzZmNmM2MzQzMDAwNTBiMDAzMjYwODA2MDQwNTIzNDgwMTU2MDEwNTc2MDAwNjAwMGZkNWI1MDYwMTU1NjViNjA0NzgwNjAyMjYwMDAzOTYwMDBmM2ZlNjA4MDYwNDA1MjYwMDQzNjEwNjAwYzU3NWI2MDAwNjAwMGZkZmVhMjY1NjI3YTdhNzIzMTU4MjA0ZWE4NzE1YWQ0MWYzOTRkNGZjYzRjYWIyMjZkM2Q3NDExNDFlZTdhZWM0ZTNhZTFkNDQ1NmE0ZDE2YTgzODQ4NjQ3MzZmNmM2MzQzMDAwNTBiMDAzMg==","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIw5s7C4wby1FZLMIte29S6Anv3xFjhfYn/P0BLhxe25nuS5zf3Xm5HZZ06PvEDH0YKGgwItsyBrAYQy7m/2QIiDwoJCPrLgawGEPYGEgIYAiogw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5SAA=="},{"b64Body":"Cg8KCQj7y4GsBhD4BhICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKQYGiISIOFPf74mOiaoiJf77fhoKcJkivwWN7UafV/OXdcwXnQCIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGKUYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDga2hD9xPQi1Hr1oeVftYZ71+HbL31g+Jwq/hDof8xSwKsvAgzMgD1Tq5eKVoKoH8aCwi3zIGsBhCj4OhhIg8KCQj7y4GsBhD4BhICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMKCgvQdCwQMKAxilGBJHYIBgQFJgBDYQYAxXW2AAYAD9/qJlYnp6cjFYIAPP5FhhadF1+Z7Rk96SW6MZCXy8gs7wATfNtMFmP4VqZHNvbGNDAAULADIigAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKOC9DToDGKUYOgMYphg6AxinGDoDGKgYOgMYqRg6AxiqGEoWChQAAAAAAAAAAAAAAAAAAAAAAAAMJXIHCgMYpRgQBnIHCgMYphgQAXIHCgMYpxgQAXIHCgMYqBgQAXIHCgMYqRgQAXIHCgMYqhgQAVIWCgkKAhgCEL/A+g4KCQoCGGIQwMD6Dg=="},{"b64Body":"ChEKCQj7y4GsBhD4BhICGAIgAUI4GiISIOFPf74mOiaoiJf77fhoKcJkivwWN7UafV/OXdcwXnQCQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKYYEjAuYRRNcTFgvr9EnelUGISyL1xirBth3pKlmCSkU1EBZpq1wQQpfWB994JZZnsoCkYaCwi3zIGsBhCk4OhhIhEKCQj7y4GsBhD4BhICGAIgAUIdCgMYphhKFgoUNmPXboQza7MwCkDiehQa4+wp5AZSAHoLCLfMgawGEKPg6GE="},{"b64Body":"ChEKCQj7y4GsBhD4BhICGAIgAkI4GiISIOFPf74mOiaoiJf77fhoKcJkivwWN7UafV/OXdcwXnQCQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKcYEjAdk3GuBIulnvQTkhrNqyAMhIQBoNwjDPdQAxeQqdtJHOnGcljqmv7YWCugARYlRgIaCwi3zIGsBhCl4OhhIhEKCQj7y4GsBhD4BhICGAIgAkIdCgMYpxhKFgoU3GaYFCnKcbXBudrlLVif684wq9BSAHoLCLfMgawGEKPg6GE="},{"b64Body":"ChEKCQj7y4GsBhD4BhICGAIgA0I4GiISIOFPf74mOiaoiJf77fhoKcJkivwWN7UafV/OXdcwXnQCQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKgYEjAqsaV9FbEytnBz/9rjgMxfgs7liQ9rNh8Ec2L+ru/w2uPkJ95e9Fj1QZbUtH6byf8aCwi3zIGsBhCm4OhhIhEKCQj7y4GsBhD4BhICGAIgA0IdCgMYqBhKFgoUQF8B+CFDrH5Ud6EwvM5LANeQ6NVSAHoLCLfMgawGEKPg6GE="},{"b64Body":"ChEKCQj7y4GsBhD4BhICGAIgBEI4GiISIOFPf74mOiaoiJf77fhoKcJkivwWN7UafV/OXdcwXnQCQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKkYEjDX1mBAc3dTFEKcZiRcFkgQr52sv2qJmgMeixKXXnrILzGZqXYRnwa01c6suBKg+BcaCwi3zIGsBhCn4OhhIhEKCQj7y4GsBhD4BhICGAIgBEIdCgMYqRhKFgoUWK63AtHxdjkLLj/BKbrbpn3P8kdSAHoLCLfMgawGEKPg6GE="},{"b64Body":"ChEKCQj7y4GsBhD4BhICGAIgBUI4GiISIOFPf74mOiaoiJf77fhoKcJkivwWN7UafV/OXdcwXnQCQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGKoYEjBM3h0XAbvIEGjG9mqHAyUjoiNkHBdO16c03UVZR/YlQPVS4cTP1HSK6V6OoZgn+wYaCwi3zIGsBhCo4OhhIhEKCQj7y4GsBhD4BhICGAIgBUIdCgMYqhhKFgoUn9NG/2E0b6NJJ3+CEm+dgGOhg0lSAHoLCLfMgawGEKPg6GE="}]},"childContractStorageWorks":{"placeholderNum":3115,"encodedItems":[{"b64Body":"Cg8KCQj/y4GsBhC2BxICGAISAhgDGP7LzSwiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjooBjwESDAi7mtyvBhDI7s7cAhptCiISIPsf4+Mslu3BgBo02eoTM3i8HfSKVPG5C2+mhUQHHXK4CiM6IQKUOvoYu2gtkV29owXkb06whfyDXBGdLhJf7fHs7nyDOQoiEiDXCJw3NBTQkTXMvke7jsOPN0GZ5saNxMrWB3JFyvvN/CIMSGVsbG8gV29ybGQhKgAyAA==","b64Record":"CiUIFhoDGKwYKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjCDxp70N7nsJBSHaJatSZzvfdjHuuBl2Ars0nL/llRLIwXwwjJKCD596HdGaaWkd/kaDAi7zIGsBhDDhN7sAiIPCgkI/8uBrAYQtgcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiAzIGsBhC6BxICGAISAhgDGIi18DMiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjpoB3A0KAxisGCLUDTYwODA2MDQwNTIzNDgwMTU2MTAwMTA1NzYwMDA4MGZkNWI1MDYxMDM0YTgwNjEwMDIwNjAwMDM5NjAwMGYzMDA2MDgwNjA0MDUyNjAwNDM2MTA2MTAwNTc1NzYwMDAzNTdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDkwMDQ2M2ZmZmZmZmZmMTY4MDYzMmYxOWMwNGExNDYxMDA1YzU3ODA2MzM4Y2M0ODMxMTQ2MTAwODc1NzgwNjNlZmM4MWE4YzE0NjEwMGRlNTc1YjYwMDA4MGZkNWIzNDgwMTU2MTAwNjg1NzYwMDA4MGZkNWI1MDYxMDA3MTYxMDBmNTU2NWI2MDQwNTE4MDgyODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDA5MzU3NjAwMDgwZmQ1YjUwNjEwMDljNjEwMWJjNTY1YjYwNDA1MTgwODI3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2ODE1MjYwMjAwMTkxNTA1MDYwNDA1MTgwOTEwMzkwZjM1YjM0ODAxNTYxMDBlYTU3NjAwMDgwZmQ1YjUwNjEwMGYzNjEwMWU1NTY1YjAwNWI2MDAwODA2MDAwOTA1NDkwNjEwMTAwMGE5MDA0NzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMTY3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjYzMDg2OTQ5Yjc2MDQwNTE4MTYzZmZmZmZmZmYxNjdjMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyODE1MjYwMDQwMTYwMjA2MDQwNTE4MDgzMDM4MTYwMDA4NzgwM2IxNTgwMTU2MTAxN2M1NzYwMDA4MGZkNWI1MDVhZjExNTgwMTU2MTAxOTA1NzNkNjAwMDgwM2UzZDYwMDBmZDViNTA1MDUwNTA2MDQwNTEzZDYwMjA4MTEwMTU2MTAxYTY1NzYwMDA4MGZkNWI4MTAxOTA4MDgwNTE5MDYwMjAwMTkwOTI5MTkwNTA1MDUwOTA1MDkwNTY1YjYwMDA4MDYwMDA5MDU0OTA2MTAxMDAwYTkwMDQ3M2ZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmYxNjkwNTA5MDU2NWI2MTAxZWQ2MTAyNGI1NjViNjA0MDUxODA5MTAzOTA2MDAwZjA4MDE1ODAxNTYxMDIwOTU3M2Q2MDAwODAzZTNkNjAwMGZkNWI1MDYwMDA4MDYxMDEwMDBhODE1NDgxNzNmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmMDIxOTE2OTA4MzczZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZjE2MDIxNzkwNTU1MDU2NWI2MDQwNTE2MGM0ODA2MTAyNWI4MzM5MDE5MDU2MDA2MDgwNjA0MDUyNjAwODYwMDA1NTM0ODAxNTYwMTQ1NzYwMDA4MGZkNWI1MDYwYTE4MDYxMDAyMzYwMDAzOTYwMDBmMzAwNjA4MDYwNDA1MjYwMDQzNjEwNjAzZjU3NjAwMDM1N2MwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwOTAwNDYzZmZmZmZmZmYxNjgwNjMwODY5NDliNzE0NjA0NDU3NWI2MDAwODBmZDViMzQ4MDE1NjA0ZjU3NjAwMDgwZmQ1YjUwNjA1NjYwNmM1NjViNjA0MDUxODA4MjgxNTI2MDIwMDE5MTUwNTA2MDQwNTE4MDkxMDM5MGYzNWI2MDAwNjAwNzkwNTA5MDU2MDBhMTY1NjI3YTdhNzIzMDU4MjAyZTA5N2JiZTEyMmFkNWQ4NmU4NDBiZTYwYWFiNDFkMTYwYWQ1Yjg2NzQ1YWE3YWEwMDk5YTZiYmZjMjY1MjE4MDAyOWExNjU2MjdhN2E3MjMwNTgyMDZjZjdlYTlkNGU1MDY4ODZiNjAyZmY3YTYyODQwMTYxMTQzN2NiZmQwZGZjYmQ1YmVlYzM3NzU3MDcwZGE1YjMwMDI5","b64Record":"CiAIFiocCgwIARAMGgYIgK6ZpA8SDAgBEA8aBgiArpmkDxIwSXA4bz2RaqvruoAfOPgXULunqN0+uql7v15M5OiKcL2KcRf2AbSD0Gmfc2Qw35hgGgsIvMyBrAYQ24jUdCIPCgkIgMyBrAYQugcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjlIA"},{"b64Body":"Cg8KCQiAzIGsBhC8BxICGAISAhgDGJb7rp0CIgIIeDIgw4PCrsOCwrfDg8K5dEY4w4LCrkrDg8KLw4PCkMODwo5CRQoDGKwYGiISIEcMRguEkEGkB77cezl1JshIYzxm1KapVuuBu/JoieZpIJChD0IFCIDO2gNSAFoAagtjZWxsYXIgZG9vcg==","b64Record":"CiUIFiIDGK0YKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjDz+3WfoBLvpAzykxWBY2yGNv9qNWzw5TKWICBWhao9ITl8bzV395LCH8AzsCHxXwwaDAi8zIGsBhCr7fb1AiIPCgkIgMyBrAYQvAcSAhgCKiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjDA2eIGQv8ICgMYrRgSygZggGBAUmAENhBhAFdXYAA1fAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkARj/////xaAYy8ZwEoUYQBcV4BjOMxIMRRhAIdXgGPvyBqMFGEA3ldbYACA/Vs0gBVhAGhXYACA/VtQYQBxYQD1VltgQFGAgoFSYCABkVBQYEBRgJEDkPNbNIAVYQCTV2AAgP1bUGEAnGEBvFZbYEBRgIJz//////////////////////////8Wc///////////////////////////FoFSYCABkVBQYEBRgJEDkPNbNIAVYQDqV2AAgP1bUGEA82EB5VZbAFtgAIBgAJBUkGEBAAqQBHP//////////////////////////xZz//////////////////////////8WYwhpSbdgQFGBY/////8WfAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoFSYAQBYCBgQFGAgwOBYACHgDsVgBVhAXxXYACA/VtQWvEVgBVhAZBXPWAAgD49YAD9W1BQUFBgQFE9YCCBEBVhAaZXYACA/VuBAZCAgFGQYCABkJKRkFBQUJBQkFZbYACAYACQVJBhAQAKkARz//////////////////////////8WkFCQVlthAe1hAktWW2BAUYCRA5BgAPCAFYAVYQIJVz1gAIA+PWAA/VtQYACAYQEACoFUgXP//////////////////////////wIZFpCDc///////////////////////////FgIXkFVQVltgQFFgxIBhAluDOQGQVgBggGBAUmAIYABVNIAVYBRXYACA/VtQYKGAYQAjYAA5YADzAGCAYEBSYAQ2EGA/V2AANXwBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAEY/////8WgGMIaUm3FGBEV1tgAID9WzSAFWBPV2AAgP1bUGBWYGxWW2BAUYCCgVJgIAGRUFBgQFGAkQOQ81tgAGAHkFCQVgChZWJ6enIwWCAuCXu+EirV2G6EC+YKq0HRYK1bhnRap6oAmaa7/CZSGAApoWVienpyMFggbPfqnU5QaIa2Av96YoQBYRQ3y/0N/L1b7sN3VwcNpbMAKSKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAowJoMOgMYrRhKFgoUAAAAAAAAAAAAAAAAAAAAAAAADC1yBwoDGK0YEAFSFgoJCgIYAhD/ssUNCgkKAhhiEICzxQ0="},{"b64Body":"Cg8KCQiBzIGsBhC+BxICGAISAhgDGNjlyRoiAgh4MiDDg8Kuw4LCt8ODwrl0RjjDgsKuSsODwovDg8KQw4PCjjoPCgMYrRgQ6PQvIgTvyBqM","b64Record":"CiUIFiIDGK0YKhwKDAgBEAwaBgiArpmkDxIMCAEQDxoGCICumaQPEjD4MOBaW42PaUr6wxTUL8Wxp5XUtsEXltEok+xMigfUEO1buB5Cgl2FXAlZjUx+3PwaCwi9zIGsBhCT0s9+Ig8KCQiBzIGsBhC+BxICGAIqIMODwq7DgsK3w4PCuXRGOMOCwq5Kw4PCi8ODwpDDg8KOMOC3oRU6owIKAxitGCKAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAooKomOgMYrhhyBwoDGK0YEAJyBwoDGK4YEAFSFgoJCgIYAhC/78IqCgkKAhhiEMDvwio="},{"b64Body":"ChEKCQiBzIGsBhC+BxICGAIgAUI4GiISIEcMRguEkEGkB77cezl1JshIYzxm1KapVuuBu/JoieZpQgUIgM7aA2oLY2VsbGFyIGRvb3I=","b64Record":"CgcIFiIDGK4YEjD1Vip5aFfAXQVeUGPlTA/VqNq773r3sCQdqx/hZYX1UU2zkfsFGTP1fAYQgt55C8MaCwi9zIGsBhCU0s9+IhEKCQiBzIGsBhC+BxICGAIgAUIdCgMYrhhKFgoUQ9J78A2LpKU3MutC/vLpBcGRtzJSAHoLCL3MgawGEJPSz34="}]}}} \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomHollowAccount.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomHollowAccount.java index 95e50914bea3..918a5bb61ac8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomHollowAccount.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/infrastructure/providers/ops/hollow/RandomHollowAccount.java @@ -98,8 +98,10 @@ private HapiSpecOperation generateHollowAccount(String keyName) { allRunFor(spec, op, hapiGetTxnRecord); if (!hapiGetTxnRecord.getChildRecords().isEmpty()) { - final AccountID newAccountID = - hapiGetTxnRecord.getChildRecord(0).getReceipt().getAccountID(); + final var newAccountID = hapiGetTxnRecord + .getFirstNonStakingChildRecord() + .getReceipt() + .getAccountID(); spec.registry().saveAccountId(keyName + ACCOUNT_SUFFIX, newAccountID); } }); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java index ef28f67bf170..d67c79e83aa3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/crypto/HapiGetAccountRecords.java @@ -131,7 +131,7 @@ private void checkExpectations(HapiSpec spec, List records) { File countFile = new File(expectationsDir + "/n.txt"); CharSource charSource = Files.asCharSource(countFile, Charset.forName("UTF-8")); int n = Integer.parseInt(charSource.readFirstLine()); - Assertions.assertEquals(n, records.size(), "Bad number of records!"); + Assertions.assertEquals(n, records.size(), "Bad number of records - got " + records); for (int i = 0; i < n; i++) { File recordFile = new File(expectationsDir + "/record" + i + ".bin"); ByteSource byteSource = Files.asByteSource(recordFile); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java index c2a6c8f9bf71..6cb47c07f347 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/queries/meta/HapiGetTxnRecord.java @@ -442,6 +442,13 @@ public TransactionRecord getChildRecord(final int i) { return response.getTransactionGetRecord().getChildTransactionRecords(i); } + public TransactionRecord getFirstNonStakingChildRecord() { + return getChildRecords().stream() + .filter(child -> !isEndOfStakingPeriodRecord(child)) + .findFirst() + .orElseThrow(); + } + public List getChildRecords() { return response.getTransactionGetRecord().getChildTransactionRecordsList(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMatchMode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMatchMode.java index 7ca522916960..06a404483665 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMatchMode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotMatchMode.java @@ -70,5 +70,13 @@ public enum SnapshotMatchMode { * fuzzy-matching of records that have different ids. Also, when auto-creation fails the charged fee to payer is not re-claimed * in mono-service. So the transaction fee differs a lot. */ - ALLOW_SKIPPED_ENTITY_IDS + ALLOW_SKIPPED_ENTITY_IDS, + /** + * Skip checks on logs that contain EVM addresses. + */ + NONDETERMINISTIC_LOG_DATA, + /** + * Allows for non-deterministic ethereum data. + */ + NONDETERMINISTIC_ETHEREUM_DATA } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java index 8132eb36923d..17030e4316ec 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java @@ -28,7 +28,9 @@ import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static com.hedera.services.bdd.suites.TargetNetworkType.STANDALONE_MONO_NETWORK; @@ -37,7 +39,6 @@ import static java.util.stream.Collectors.toSet; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; import com.google.protobuf.GeneratedMessageV3; import com.hedera.services.bdd.junit.HapiTestEngine; @@ -138,7 +139,7 @@ public class SnapshotModeOp extends UtilOp implements SnapshotOp { "prng_bytes"); private static final String PLACEHOLDER_MEMO = ""; - private static final String MONO_STREAMS_LOC = "hedera-node/data/recordstreams/record0.0.3"; + private static final String MONO_STREAMS_LOC = "hedera-node/hedera-app/build/node/data/recordStreams/record0.0.3"; private static final String HAPI_TEST_STREAMS_LOC_TPL = "hedera-node/test-clients/build/hapi-test/node%d/data/recordStreams/record0.0.%d"; private static final String TEST_CLIENTS_SNAPSHOT_RESOURCES_LOC = "record-snapshots"; @@ -179,8 +180,8 @@ public class SnapshotModeOp extends UtilOp implements SnapshotOp { public static void main(String... args) throws IOException { // Helper to review the snapshot saved for a particular HapiSuite-HapiSpec combination - final var snapshotFileMeta = new SnapshotFileMeta( - "ContractKeysHTS", "DissociatePrecompileWithDelegateContractKeyForNonFungibleVanilla"); + final var snapshotFileMeta = + new SnapshotFileMeta("HelloWorldEthereum", "createWithSelfDestructInConstructorHasSaneRecord"); final var maybeSnapshot = suiteSnapshotsFrom( resourceLocOf(PROJECT_ROOT_SNAPSHOT_RESOURCES_LOC, snapshotFileMeta.suiteName())) .flatMap( @@ -740,7 +741,7 @@ private List hapiTestStreamLocs() { private boolean shouldSkip(@NonNull final String expectedName, @NonNull final Class expectedType) { requireNonNull(expectedName); requireNonNull(expectedType); - if ("contractCallResult".equals(expectedName) && ByteString.class.isAssignableFrom(expectedType)) { + if ("contractCallResult".equals(expectedName) /* && ByteString.class.isAssignableFrom(expectedType)*/) { return matchModes.contains(NONDETERMINISTIC_CONTRACT_CALL_RESULTS); } else if ("functionParameters".equals(expectedName)) { return matchModes.contains(NONDETERMINISTIC_FUNCTION_PARAMETERS); @@ -754,6 +755,10 @@ private boolean shouldSkip(@NonNull final String expectedName, @NonNull final Cl return matchModes.contains(NONDETERMINISTIC_NONCE); } else if ("gas".equals(expectedName) || "gasUsed".equals(expectedName)) { return matchModes.contains(ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE); + } else if ("logInfo".equals(expectedName)) { + return matchModes.contains(NONDETERMINISTIC_LOG_DATA); + } else if ("ethereum_data".equals(expectedName) || "ethereum_hash".equals(expectedName)) { + return matchModes.contains(NONDETERMINISTIC_ETHEREUM_DATA); } else { return FIELDS_TO_SKIP_IN_FUZZY_MATCH.contains(expectedName); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java index 56b4887a7113..bf93324c98b4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java @@ -66,6 +66,15 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ALLOW_SKIPPED_ENTITY_IDS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_CONTRACT_CALL_RESULTS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_ETHEREUM_DATA; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.accountId; @@ -203,7 +212,7 @@ final HapiSpec allLogOpcodesResolveExpectedContractId() { final var msg = new byte[] {(byte) 0xAB}; final var noisyTxn = "noisyTxn"; - return defaultHapiSpec("AllLogOpcodesResolveExpectedContractId") + return defaultHapiSpec("AllLogOpcodesResolveExpectedContractId", FULLY_NONDETERMINISTIC) .given( uploadInitCode(contract), contractCreate(contract) @@ -240,7 +249,8 @@ final HapiSpec inlineCreate2CanFailSafely() { final AtomicReference factoryEvmAddress = new AtomicReference<>(); final AtomicReference testContractInitcode = new AtomicReference<>(); - return defaultHapiSpec("InlineCreate2CanFailSafely") + return defaultHapiSpec( + "InlineCreate2CanFailSafely", NONDETERMINISTIC_FUNCTION_PARAMETERS, ALLOW_SKIPPED_ENTITY_IDS) .given( uploadInitCode(contract), contractCreate(contract) @@ -288,7 +298,8 @@ final HapiSpec inlineCreateCanFailSafely() { final AtomicReference factoryEvmAddress = new AtomicReference<>(); final AtomicReference testContractInitcode = new AtomicReference<>(); - return defaultHapiSpec("InlineCreateCanFailSafely") + return defaultHapiSpec( + "InlineCreateCanFailSafely", NONDETERMINISTIC_FUNCTION_PARAMETERS, ALLOW_SKIPPED_ENTITY_IDS) .given( uploadInitCode(contract), contractCreate(contract) @@ -329,7 +340,10 @@ final HapiSpec canAssociateInConstructor() { final var contract = "SelfAssociating"; final AtomicReference tokenMirrorAddr = new AtomicReference<>(); - return defaultHapiSpec("CanAssociateInConstructor") + return defaultHapiSpec( + "CanAssociateInConstructor", + NONDETERMINISTIC_CONSTRUCTOR_PARAMETERS, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS) .given( uploadInitCode(contract), tokenCreate(token) @@ -351,10 +365,8 @@ final HapiSpec payableCreate2WorksAsExpected() { AtomicReference tcMirrorAddr2 = new AtomicReference<>(); AtomicReference tcAliasAddr2 = new AtomicReference<>(); - return defaultHapiSpec("PayableCreate2WorksAsExpected") + return defaultHapiSpec("PayableCreate2WorksAsExpected", NONDETERMINISTIC_TRANSACTION_FEES) .given( - // FUTURE - enable this; current failure is missing transactionFee field - // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), uploadInitCode(contract), contractCreate(contract).payingWith(GENESIS).gas(1_000_000)) .when( @@ -388,7 +400,12 @@ final HapiSpec create2FactoryWorksAsExpected() { final AtomicReference bytecodeFromAlias = new AtomicReference<>(); final AtomicReference mirrorLiteralId = new AtomicReference<>(); - return defaultHapiSpec("Create2FactoryWorksAsExpected") + return defaultHapiSpec( + "Create2FactoryWorksAsExpected", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS, + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_LOG_DATA) .given( newKeyNamed(adminKey), newKeyNamed(replAdminKey), @@ -522,19 +539,13 @@ final HapiSpec canMergeCreate2ChildWithHollowAccount() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference partyAlias = new AtomicReference<>(); - return defaultHapiSpec("CanMergeCreate2ChildWithHollowAccount") + return defaultHapiSpec( + "CanMergeCreate2ChildWithHollowAccount", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS, + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_LOG_DATA) .given( - // // FUTURE - enable this; current failure is missing transactionFee - // field - // snapshotMode( - // FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - // // This contract uses entity addresses in its function - // parameters and - // // call results, which makes it excessively difficult to - // fuzzy-match - // // the "contractCallResult" and "functionParameters" fields - // NONDETERMINISTIC_CONTRACT_CALL_RESULTS, - // NONDETERMINISTIC_FUNCTION_PARAMETERS), newKeyNamed(adminKey), newKeyNamed(MULTI_KEY), uploadInitCode(contract), @@ -580,6 +591,7 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) lazyCreateAccount(creation, expectedCreate2Address, ftId, nftId, partyAlias), getTxnRecord(creation) .andAllChildRecords() + .logged() .exposingCreationsTo(l -> hollowCreationAddress.set(l.get(0))), sourcing(() -> getAccountInfo(hollowCreationAddress.get()).logged())) @@ -593,10 +605,11 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) .payingWith(GENESIS) .gas(4_000_000L) .sending(tcValue) - .via(CREATE_2_TXN)), + .via("TEST2")), + getTxnRecord("TEST2").andAllChildRecords().logged(), captureOneChildCreate2MetaFor( "Merged deployed contract with hollow account", - CREATE_2_TXN, + "TEST2", mergedMirrorAddr, mergedAliasAddr), sourcing(() -> contractCall(contract, DEPLOY, testContractInitcode.get(), salt) @@ -645,7 +658,7 @@ final HapiSpec canMergeCreate2MultipleCreatesWithHollowAccount() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference partyAlias = new AtomicReference<>(); - return defaultHapiSpec("CanMergeCreate2MultipleCreatesWithHollowAccount") + return defaultHapiSpec("CanMergeCreate2MultipleCreatesWithHollowAccount", FULLY_NONDETERMINISTIC) .given( newKeyNamed(adminKey), newKeyNamed(MULTI_KEY), @@ -747,7 +760,10 @@ final HapiSpec canCallFinalizedContractViaHapi() { final var vacateAddressAbi = "{\"inputs\":[],\"name\":\"vacateAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}"; - return defaultHapiSpec("CanCallFinalizedContractViaHapi") + return defaultHapiSpec( + "CanCallFinalizedContractViaHapi", + NONDETERMINISTIC_ETHEREUM_DATA, + NONDETERMINISTIC_TRANSACTION_FEES) .given( cryptoCreate(RELAYER).balance(ONE_HUNDRED_HBARS), newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), @@ -788,7 +804,11 @@ final HapiSpec eip1014AliasIsPriorityInErcOwnerPrecompile() { final byte[] salt = unhex(SALT); - return defaultHapiSpec("Eip1014AliasIsPriorityInErcOwnerPrecompile") + return defaultHapiSpec( + "Eip1014AliasIsPriorityInErcOwnerPrecompile", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS) .given( newKeyNamed(SWISS), cryptoCreate(TOKEN_TREASURY), @@ -857,7 +877,11 @@ final HapiSpec canUseAliasesInPrecompilesAndContractKeys() { final var salt = unhex(SALT); - return defaultHapiSpec("canUseAliasesInPrecompilesAndContractKeys") + return defaultHapiSpec( + "canUseAliasesInPrecompilesAndContractKeys", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_TRANSACTION_FEES, + ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE) .given( newKeyNamed(multiKey), cryptoCreate(TOKEN_TREASURY), @@ -1034,7 +1058,10 @@ final HapiSpec cannotSelfDestructToMirrorAddress() { final var salt = unhex(SALT); final var otherSalt = unhex("aabbccddee880011aabbccddee880011aabbccddee880011aabbccddee880011"); - return defaultHapiSpec("CannotSelfDestructToMirrorAddress") + return defaultHapiSpec( + "CannotSelfDestructToMirrorAddress", + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( uploadInitCode(contract), contractCreate(contract).payingWith(GENESIS), @@ -1161,7 +1188,10 @@ final HapiSpec create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIs final var salt = unhex(SALT); - return defaultHapiSpec("Create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed") + return defaultHapiSpec( + "Create2InputAddressIsStableWithTopLevelCallWhetherMirrorOrAliasIsUsed", + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_FUNCTION_PARAMETERS) .given( uploadInitCode(contract), contractCreate(contract).payingWith(GENESIS), @@ -1286,7 +1316,10 @@ final HapiSpec canInternallyCallAliasedAddressesOnlyViaCreate2Address() { final var salt = unhex(SALT); - return defaultHapiSpec("CanInternallyCallAliasedAddressesOnlyViaCreate2Address") + return defaultHapiSpec( + "CanInternallyCallAliasedAddressesOnlyViaCreate2Address", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + NONDETERMINISTIC_CONTRACT_CALL_RESULTS) .given( uploadInitCode(contract), contractCreate(contract).payingWith(GENESIS), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java index 67ddfed90aff..e3bc3dd0f2de 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/CreateOperationSuite.java @@ -36,6 +36,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.HIGHLY_NON_DETERMINISTIC_FEES; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_LOG_DATA; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -100,7 +106,10 @@ final HapiSpec factoryAndSelfDestructInConstructorContract() { final var contract = "FactorySelfDestructConstructor"; final var sender = "sender"; - return defaultHapiSpec("FactoryAndSelfDestructInConstructorContract") + return defaultHapiSpec( + "FactoryAndSelfDestructInConstructorContract", + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_LOG_DATA) .given( uploadInitCode(contract), cryptoCreate(sender).balance(ONE_HUNDRED_HBARS), @@ -120,7 +129,10 @@ final HapiSpec factoryAndSelfDestructInConstructorContract() { final HapiSpec factoryQuickSelfDestructContract() { final var contract = "FactoryQuickSelfDestruct"; final var sender = "sender"; - return defaultHapiSpec("FactoryQuickSelfDestructContract") + return defaultHapiSpec( + "FactoryQuickSelfDestructContract", + NONDETERMINISTIC_TRANSACTION_FEES, + NONDETERMINISTIC_LOG_DATA) .given( uploadInitCode(contract), contractCreate(contract), @@ -144,7 +156,7 @@ final HapiSpec factoryQuickSelfDestructContract() { @HapiTest final HapiSpec inheritanceOfNestedCreatedContracts() { final var contract = "NestedChildren"; - return defaultHapiSpec("InheritanceOfNestedCreatedContracts") + return defaultHapiSpec("InheritanceOfNestedCreatedContracts", FULLY_NONDETERMINISTIC) .given( uploadInitCode(contract), contractCreate(contract).logged().via("createRecord"), @@ -159,7 +171,7 @@ final HapiSpec inheritanceOfNestedCreatedContracts() { @HapiTest HapiSpec simpleFactoryWorks() { - return defaultHapiSpec("simpleFactoryWorks") + return defaultHapiSpec("simpleFactoryWorks", NONDETERMINISTIC_TRANSACTION_FEES) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, DEPLOYMENT_SUCCESS_FUNCTION) .gas(780_000) @@ -180,7 +192,7 @@ HapiSpec simpleFactoryWorks() { @HapiTest HapiSpec stackedFactoryWorks() { - return defaultHapiSpec("StackedFactoryWorks") + return defaultHapiSpec("StackedFactoryWorks", FULLY_NONDETERMINISTIC) .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "stackedDeploymentSuccess") .gas(1_000_000) @@ -298,7 +310,11 @@ HapiSpec resetOnStackedFactoryFailureWorks() { @HapiTest final HapiSpec contractCreateWithNewOpInConstructorAbandoningParent() { final var contract = "AbandoningParent"; - return defaultHapiSpec("contractCreateWithNewOpInConstructorAbandoningParent") + return defaultHapiSpec( + "contractCreateWithNewOpInConstructorAbandoningParent", + NONDETERMINISTIC_FUNCTION_PARAMETERS, + HIGHLY_NON_DETERMINISTIC_FEES, + ACCEPTED_MONO_GAS_CALCULATION_DIFFERENCE) .given(uploadInitCode(contract), contractCreate(contract).via("AbandoningParentTxn")) .when() .then( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java index 04309eae803b..acd4cb529426 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/ExtCodeSizeOperationSuite.java @@ -31,10 +31,8 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.contract.HapiParserUtil.asHeadlongAddress; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; -import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS; @@ -79,12 +77,8 @@ HapiSpec verifiesExistence() { final var sizeOf = "sizeOf"; final var account = "account"; - return defaultHapiSpec("VerifiesExistence") - .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, NONDETERMINISTIC_FUNCTION_PARAMETERS), - uploadInitCode(contract), - contractCreate(contract), - cryptoCreate(account)) + return defaultHapiSpec("VerifiesExistence", NONDETERMINISTIC_FUNCTION_PARAMETERS) + .given(uploadInitCode(contract), contractCreate(contract), cryptoCreate(account)) .when() .then( contractCall(contract, sizeOf, asHeadlongAddress(invalidAddress)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java index 44189818c386..64bea8bc44d4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/records/LogsSuite.java @@ -27,6 +27,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.snapshotMode; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMode.FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.parsedToByteString; @@ -71,11 +72,8 @@ public List getSpecsInSuite() { @HapiTest final HapiSpec log0Works() { - return defaultHapiSpec("log0Works") - .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + return defaultHapiSpec("log0Works", NONDETERMINISTIC_TRANSACTION_FEES) + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log0", BigInteger.valueOf(15)) .via("log0") .gas(GAS_TO_OFFER)) @@ -88,7 +86,7 @@ final HapiSpec log0Works() { @HapiTest final HapiSpec log1Works() { - return defaultHapiSpec("log1Works") + return defaultHapiSpec("log1Works", NONDETERMINISTIC_TRANSACTION_FEES) .given( snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), uploadInitCode(CONTRACT), @@ -108,11 +106,8 @@ final HapiSpec log1Works() { @HapiTest final HapiSpec log2Works() { - return defaultHapiSpec("log2Works") - .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + return defaultHapiSpec("log2Works", NONDETERMINISTIC_TRANSACTION_FEES) + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log2", BigInteger.ONE, BigInteger.TWO) .gas(GAS_TO_OFFER) .via("log2")) @@ -130,11 +125,8 @@ final HapiSpec log2Works() { @HapiTest final HapiSpec log3Works() { - return defaultHapiSpec("log3Works") - .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + return defaultHapiSpec("log3Works", NONDETERMINISTIC_TRANSACTION_FEES) + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall(CONTRACT, "log3", BigInteger.ONE, BigInteger.TWO, BigInteger.valueOf(3)) .gas(GAS_TO_OFFER) .via("log3")) @@ -153,11 +145,8 @@ final HapiSpec log3Works() { @HapiTest final HapiSpec log4Works() { - return defaultHapiSpec("log4Works") - .given( - snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS), - uploadInitCode(CONTRACT), - contractCreate(CONTRACT)) + return defaultHapiSpec("log4Works", NONDETERMINISTIC_TRANSACTION_FEES) + .given(uploadInitCode(CONTRACT), contractCreate(CONTRACT)) .when(contractCall( CONTRACT, "log4", diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java index fb62f6a26a72..703b9fe69a81 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java @@ -83,6 +83,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALIAS_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_REMAINING_AUTOMATIC_ASSOCIATIONS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.TokenSupplyType.FINITE; @@ -1335,23 +1336,38 @@ final HapiSpec multipleAutoAccountCreations() { tinyBarsFromToWithAlias(PAYER, "alias1", ONE_HUNDRED_HBARS), tinyBarsFromToWithAlias(PAYER, ALIAS_2, ONE_HUNDRED_HBARS), tinyBarsFromToWithAlias(PAYER, "alias3", ONE_HUNDRED_HBARS)) - .via("multipleAutoAccountCreates"), - getTxnRecord("multipleAutoAccountCreates") - .hasNonStakingChildRecordCount(3) - .logged(), - getAccountInfo(PAYER) - .has(accountWith().balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS))) - .then( - cryptoTransfer( - tinyBarsFromToWithAlias(PAYER, "alias4", 7 * ONE_HUNDRED_HBARS), - tinyBarsFromToWithAlias(PAYER, "alias5", 100)) - .via("failedAutoCreate") - .hasKnownStatus(INSUFFICIENT_ACCOUNT_BALANCE), - getTxnRecord("failedAutoCreate") - .hasNonStakingChildRecordCount(0) - .logged(), - getAccountInfo(PAYER) - .has(accountWith().balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS))); + .via("multipleAutoAccountCreates") + // In CI this could fail due to an end-of-staking period record already + // being added as a child to this transaction before its auto-creations + .hasKnownStatusFrom(SUCCESS, MAX_CHILD_RECORDS_EXCEEDED)) + .then(withOpContext((spec, opLog) -> { + final var lookup = getTxnRecord("multipleAutoAccountCreates"); + allRunFor(spec, lookup); + final var actualStatus = + lookup.getResponseRecord().getReceipt().getStatus(); + // Continue with more assertions given the normal case the preceding transfer succeeded + if (actualStatus == SUCCESS) { + allRunFor( + spec, + getTxnRecord("multipleAutoAccountCreates") + .hasNonStakingChildRecordCount(3) + .logged(), + getAccountInfo(PAYER) + .has(accountWith() + .balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS)), + cryptoTransfer( + tinyBarsFromToWithAlias(PAYER, "alias4", 7 * ONE_HUNDRED_HBARS), + tinyBarsFromToWithAlias(PAYER, "alias5", 100)) + .via("failedAutoCreate") + .hasKnownStatus(INSUFFICIENT_ACCOUNT_BALANCE), + getTxnRecord("failedAutoCreate") + .hasNonStakingChildRecordCount(0) + .logged(), + getAccountInfo(PAYER) + .has(accountWith() + .balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS))); + } + })); } @HapiTest @@ -1411,7 +1427,8 @@ final HapiSpec transferHbarsToEVMAddressAlias() { .has(accountWith().expectedBalanceWithChargedUsd(3 * ONE_HBAR, 0, 0))); } - private HapiSpec accountDeleteResetsTheAliasNonce() { + @HapiTest + final HapiSpec accountDeleteResetsTheAliasNonce() { final AtomicReference partyId = new AtomicReference<>(); final AtomicReference partyAlias = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java index 902ac421254a..30e9b505525f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java @@ -50,6 +50,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.FULLY_NONDETERMINISTIC; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.CONSTRUCTOR; import static com.hedera.services.bdd.suites.contract.Utils.eventSignatureOf; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; @@ -301,7 +302,8 @@ HapiSpec ethereumCallWithCalldataBiggerThanMaxSucceeds() { HapiSpec createWithSelfDestructInConstructorHasSaneRecord() { final var txn = "txn"; final var selfDestructingContract = "FactorySelfDestructConstructor"; - return defaultHapiSpec("createWithSelfDestructInConstructorHasSaneRecord") + // Does nested creates, which appear in reversed order from mono-service + return defaultHapiSpec("createWithSelfDestructInConstructorHasSaneRecord", FULLY_NONDETERMINISTIC) .given( newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), @@ -316,7 +318,11 @@ HapiSpec createWithSelfDestructInConstructorHasSaneRecord() { .maxGasAllowance(ONE_HUNDRED_HBARS) .gasLimit(5_000_000L) .via(txn)) - .then(childRecordsCheck(txn, SUCCESS, recordWith(), recordWith().hasMirrorIdInReceipt())); + .then(childRecordsCheck( + txn, + SUCCESS, + recordWith().hasMirrorIdInReceipt(), + recordWith().hasMirrorIdInReceipt())); } @HapiTest diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index b912df373475..c633ba699365 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -208,6 +208,7 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.hapi.utils.fee.FeeBuilder; import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; @@ -249,7 +250,7 @@ import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Assertions; -// @HapiTestSuite +@HapiTestSuite @SuppressWarnings("java:S1192") // "string literal should not be duplicated" - this rule makes test suites worse public class LeakyContractTestsSuite extends HapiSuite { public static final String CONTRACTS_MAX_REFUND_PERCENT_OF_GAS_LIMIT1 = "contracts.maxRefundPercentOfGasLimit"; @@ -328,6 +329,7 @@ public List getSpecsInSuite() { } @SuppressWarnings("java:S5960") + @HapiTest final HapiSpec canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor() { final var tcValue = 1_234L; final var contract = "Create2FactoryWithSelfDestructingContract"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 0a81d57c1048..63a9378e6ada 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -1002,8 +1002,6 @@ final HapiSpec lazyCreateViaEthereumCryptoTransfer() { return propertyPreservingHapiSpec("lazyCreateViaEthereumCryptoTransfer") .preserving(CHAIN_ID_PROP, LAZY_CREATE_PROPERTY_NAME, CONTRACTS_EVM_VERSION_PROP) .given( - // snapshotMode(FUZZY_MATCH_AGAINST_HAPI_TEST_STREAMS, - // ALLOW_SKIPPED_ENTITY_IDS), overridingThree( CHAIN_ID_PROP, "298", From 7534e432608d07d8c0148abce6771db93845d9c0 Mon Sep 17 00:00:00 2001 From: Lev Povolotsky <16233475+povolev15@users.noreply.github.com> Date: Sat, 30 Dec 2023 19:09:47 -0500 Subject: [PATCH 64/80] fix: recordCache to commit added entries and implemented correctly the remove elements from the queue (#10523) Signed-off-by: Lev Povolotsky Signed-off-by: Joseph Sinclair Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../node/app/spi/records/BlockRecordInfo.java | 20 +++++ .../app/spi/validation/TruePredicate.java | 28 +++++++ .../app/records/impl/BlockRecordInfoImpl.java | 7 ++ .../records/impl/BlockRecordManagerImpl.java | 5 ++ .../state/recordcache/RecordCacheImpl.java | 78 +++++++++++-------- .../handle/record/BlockRecordManagerTest.java | 2 + .../handlers/NetworkAdminHandlerTestBase.java | 2 +- .../impl/hevm/HandleContextHevmBlocks.java | 2 +- .../contract/impl/hevm/HevmBlockValues.java | 12 ++- .../impl/hevm/QueryContextHevmBlocks.java | 7 +- .../hevm/HandleContextHevmBlocksTest.java | 7 +- .../test/hevm/QueryContextHevmBlocksTest.java | 8 +- .../services/bdd/suites/crypto/RandomOps.java | 3 +- .../suites/crypto/TxnReceiptRegression.java | 1 + 14 files changed, 133 insertions(+), 49 deletions(-) create mode 100644 hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/validation/TruePredicate.java diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/BlockRecordInfo.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/BlockRecordInfo.java index 62473908ce2d..502b3317a3c3 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/BlockRecordInfo.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/records/BlockRecordInfo.java @@ -16,7 +16,9 @@ package com.hedera.node.app.spi.records; +import com.hedera.hapi.node.base.Timestamp; import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; @@ -56,6 +58,15 @@ public interface BlockRecordInfo { */ long lastBlockNo(); + /** + * Get the number of the current block. + * + * @return the number of the current block + */ + default long blockNo() { + return lastBlockNo() + 1; + } + /** * Get the consensus time of the first transaction of the last block, this is the last completed immutable block. * @@ -64,6 +75,15 @@ public interface BlockRecordInfo { @Nullable Instant firstConsTimeOfLastBlock(); + /** + * The current block timestamp. Its seconds is the value returned by {@code block.timestamp} for a contract + * executing * in this block). + * + * @return the current block timestamp + */ + @NonNull + Timestamp currentBlockTimestamp(); + /** * Gets the hash of the last block * diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/validation/TruePredicate.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/validation/TruePredicate.java new file mode 100644 index 000000000000..d4c9be3c30b6 --- /dev/null +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/validation/TruePredicate.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.spi.validation; + +import java.util.function.Predicate; + +public class TruePredicate implements Predicate { + public static final Predicate INSTANCE = new TruePredicate(); + + @Override + public boolean test(Object ignored) { + return true; + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordInfoImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordInfoImpl.java index 1fd37a8560dc..0582ecad1986 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordInfoImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordInfoImpl.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.state.blockrecords.BlockInfo; import com.hedera.hapi.node.state.blockrecords.RunningHashes; import com.hedera.node.app.spi.records.BlockRecordInfo; @@ -79,6 +80,12 @@ public Bytes lastBlockHash() { return BlockRecordInfoUtils.lastBlockHash(blockInfo); } + @Override + public @NonNull Timestamp currentBlockTimestamp() { + // There should always be a current block and a first consensus time within it + return blockInfo.firstConsTimeOfCurrentBlockOrThrow(); + } + /** {@inheritDoc} */ @Nullable @Override diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java index 93d5d29ed506..8e8a706fd322 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java @@ -293,6 +293,11 @@ public Instant firstConsTimeOfLastBlock() { return BlockRecordInfoUtils.firstConsTimeOfLastBlock(lastBlockInfo); } + @Override + public @NonNull Timestamp currentBlockTimestamp() { + return lastBlockInfo.firstConsTimeOfCurrentBlockOrThrow(); + } + /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java index 065836d83ee0..32d67f653e33 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java @@ -29,8 +29,12 @@ import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.state.recordcache.TransactionRecordEntry; import com.hedera.hapi.node.transaction.TransactionRecord; +import com.hedera.node.app.spi.state.CommittableWritableStates; import com.hedera.node.app.spi.state.ReadableQueueState; +import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.state.WritableQueueState; +import com.hedera.node.app.spi.state.WritableStates; +import com.hedera.node.app.spi.validation.TruePredicate; import com.hedera.node.app.state.DeduplicationCache; import com.hedera.node.app.state.HederaRecordCache; import com.hedera.node.app.state.SingleTransactionRecord; @@ -179,8 +183,9 @@ public void add( // To avoid having a background thread cleaning out this queue, we spend a little time when adding to the queue // to also remove from the queue any transactions that have expired. - final var queue = getQueue(); - final var firstRecord = transactionRecords.get(0); + final WritableStates states = getWritableState(); + final WritableQueueState queue = states.getQueue(TXN_RECORD_QUEUE); + final SingleTransactionRecord firstRecord = transactionRecords.get(0); removeExpiredTransactions(queue, firstRecord.transactionRecord().consensusTimestampOrElse(Timestamp.DEFAULT)); // For each transaction, in order, add to the queue and to the in-memory data structures. @@ -189,6 +194,10 @@ public void add( addToInMemoryCache(nodeId, payerAccountId, rec); queue.add(new TransactionRecordEntry(nodeId, payerAccountId, rec)); } + + if (states instanceof CommittableWritableStates committable) { + committable.commit(); + } } @NonNull @@ -246,7 +255,7 @@ private void addToInMemoryCache( // Either we add this tx to the main records list if it is a user/preceding transaction, or to the child // transactions list of its parent. Note that scheduled transactions are always child transactions, but // never produce child *records*; instead, the scheduled transaction record is treated as - // a user transaction record. + // a user transaction record. The map key remains the current user transaction ID, however. final var listToAddTo = (isChildTx && !txId.scheduled()) ? history.childRecords() : history.records(); listToAddTo.add(transactionRecord); @@ -263,33 +272,39 @@ private void removeExpiredTransactions( @NonNull final Timestamp consensusTimestamp) { // Compute the earliest valid start timestamp that is still within the max transaction duration window. final var config = configProvider.getConfiguration().getConfigData(HederaConfig.class); - final var earliestValidState = minus(consensusTimestamp, config.transactionMaxValidDuration()); - - // Loop in order and expunge every entry where the timestamp is before the current time. Also remove from the - // in memory data structures. - final var itr = queue.iterator(); - while (itr.hasNext()) { - final var entry = itr.next(); - final var rec = entry.transactionRecordOrThrow(); - final var txId = rec.transactionIDOrThrow(); - // If the timestamp is before the current time, then it has expired - if (isBefore(txId.transactionValidStartOrThrow(), earliestValidState)) { - // Remove from the histories - itr.remove(); - // Remove from the payer to transaction index - final var payerAccountId = txId.accountIDOrThrow(); // NOTE: Not accurate if the payer was the node - final var transactionIDs = - payerToTransactionIndex.computeIfAbsent(payerAccountId, ignored -> new HashSet<>()); - transactionIDs.remove(txId); - if (transactionIDs.isEmpty()) { - payerToTransactionIndex.remove(payerAccountId); + final var earliestValidStart = minus(consensusTimestamp, config.transactionMaxValidDuration()); + // Loop in order and expunge every entry where the start time is before the earliest valid start. + // Also remove from the in-memory data structures. + do { + final var entry = queue.peek(); + if (entry != null) { + final var rec = entry.transactionRecordOrThrow(); + final var txId = rec.transactionIDOrThrow(); + // If the valid start time is before the earliest valid start, then it has expired + if (isBefore(txId.transactionValidStartOrThrow(), earliestValidStart)) { + // Remove from the histories. Note that all transactions are added to this map + // keyed to the "user transaction" ID, so removing the entry here removes both + // "parent" and "child" transaction records associated with that ID. + histories.remove(txId); + // remove from queue as well. The queue only permits removing the current "HEAD", + // but that should always be correct here. + queue.removeIf(TruePredicate.INSTANCE); + // Remove from the payer to transaction index + final var payerAccountId = txId.accountIDOrThrow(); // NOTE: Not accurate if the payer was the node + final var transactionIDs = + payerToTransactionIndex.computeIfAbsent(payerAccountId, ignored -> new HashSet<>()); + transactionIDs.remove(txId); + if (transactionIDs.isEmpty()) { + payerToTransactionIndex.remove(payerAccountId); + } + } else { + break; } } else { - return; + break; } - } + } while (true); } - // --------------------------------------------------------------------------------------------------------------- // Implementation methods of RecordCache // --------------------------------------------------------------------------------------------------------------- @@ -335,22 +350,17 @@ public List getRecords(@NonNull final AccountID accountID) { } /** Utility method that get the writable queue from the working state */ - private WritableQueueState getQueue() { + private WritableStates getWritableState() { final var hederaState = workingStateAccessor.getHederaState(); if (hederaState == null) { throw new RuntimeException("HederaState is null. This can only happen very early during bootstrapping"); } - final var states = hederaState.createWritableStates(NAME); - return states.getQueue(TXN_RECORD_QUEUE); + return hederaState.createWritableStates(NAME); } /** Utility method that get the readable queue from the working state */ private ReadableQueueState getReadableQueue() { - final var hederaState = workingStateAccessor.getHederaState(); - if (hederaState == null) { - throw new RuntimeException("HederaState is null. This can only happen very early during bootstrapping"); - } - final var states = hederaState.createReadableStates(NAME); + final ReadableStates states = getWritableState(); return states.getQueue(TXN_RECORD_QUEUE); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java index 63e6e7d405ac..5863f5187387 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java @@ -169,6 +169,8 @@ void testRecordStreamProduction(final String startMode, final boolean concurrent if (!startMode.equals("GENESIS")) { blockRecordManager.switchBlocksAt(FORCED_BLOCK_SWITCH_TIME); } + assertThat(blockRecordManager.currentBlockTimestamp()).isNotNull(); + assertThat(blockRecordManager.blockNo()).isEqualTo(blockRecordManager.lastBlockNo() + 1); // write a blocks & record files int transactionCount = 0; final List endOfBlockHashes = new ArrayList<>(); diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java index 7d62c6b07611..95837a975562 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java @@ -207,7 +207,7 @@ protected void refreshRecordCache() { lenient().when(wsa.getHederaState()).thenReturn(state); lenient().when(props.getConfiguration()).thenReturn(versionedConfig); lenient().when(versionedConfig.getConfigData(HederaConfig.class)).thenReturn(hederaConfig); - lenient().when(hederaConfig.transactionMaxValidDuration()).thenReturn(180L); + lenient().when(hederaConfig.transactionMaxValidDuration()).thenReturn(123456789999L); lenient().when(versionedConfig.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); lenient().when(ledgerConfig.recordsMaxQueryableByAccount()).thenReturn(MAX_QUERYABLE_PER_ACCOUNT); givenRecordCacheState(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HandleContextHevmBlocks.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HandleContextHevmBlocks.java index 3e7540dfdc2c..d0e1102f5771 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HandleContextHevmBlocks.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HandleContextHevmBlocks.java @@ -53,6 +53,6 @@ public Hash blockHashOf(final long blockNo) { */ @Override public BlockValues blockValuesOf(final long gasLimit) { - return new HevmBlockValues(gasLimit, context.blockRecordInfo().lastBlockNo(), context.consensusNow()); + return HevmBlockValues.from(context.blockRecordInfo(), gasLimit); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmBlockValues.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmBlockValues.java index 563e4ffeffe4..8336508a20ef 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmBlockValues.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HevmBlockValues.java @@ -18,16 +18,22 @@ import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.Timestamp; +import com.hedera.node.app.spi.records.BlockRecordInfo; import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.BlockValues; -public record HevmBlockValues(long gasLimit, long blockNo, @NonNull Instant blockTime) implements BlockValues { +public record HevmBlockValues(long gasLimit, long blockNo, @NonNull Timestamp blockTime) implements BlockValues { private static final Optional ZERO_BASE_FEE = Optional.of(Wei.ZERO); + public static HevmBlockValues from(@NonNull final BlockRecordInfo blockRecordInfo, final long gasLimit) { + requireNonNull(blockRecordInfo); + return new HevmBlockValues(gasLimit, blockRecordInfo.blockNo(), blockRecordInfo.currentBlockTimestamp()); + } + public HevmBlockValues { requireNonNull(blockTime); } @@ -39,7 +45,7 @@ public long getGasLimit() { @Override public long getTimestamp() { - return blockTime.getEpochSecond(); + return blockTime.seconds(); } @Override diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/QueryContextHevmBlocks.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/QueryContextHevmBlocks.java index 4c3d07b4de2e..bf0b559b4e0a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/QueryContextHevmBlocks.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/QueryContextHevmBlocks.java @@ -22,7 +22,6 @@ import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.QueryContext; import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; import java.util.Objects; import javax.inject.Inject; import org.hyperledger.besu.datatypes.Hash; @@ -35,12 +34,10 @@ @QueryScope public class QueryContextHevmBlocks implements HederaEvmBlocks { private final QueryContext context; - private final Instant consensusTime; @Inject - public QueryContextHevmBlocks(@NonNull final QueryContext context, @NonNull final Instant consensusTime) { + public QueryContextHevmBlocks(@NonNull final QueryContext context) { this.context = Objects.requireNonNull(context); - this.consensusTime = Objects.requireNonNull(consensusTime); } /** @@ -57,6 +54,6 @@ public Hash blockHashOf(final long blockNo) { */ @Override public BlockValues blockValuesOf(final long gasLimit) { - return new HevmBlockValues(gasLimit, context.blockRecordInfo().lastBlockNo(), consensusTime); + return HevmBlockValues.from(context.blockRecordInfo(), gasLimit); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HandleContextHevmBlocksTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HandleContextHevmBlocksTest.java index 7925fcffc509..c9e4f75d69ee 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HandleContextHevmBlocksTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HandleContextHevmBlocksTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.BDDMockito.given; +import com.hedera.hapi.node.base.Timestamp; import com.hedera.node.app.service.contract.impl.hevm.HandleContextHevmBlocks; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.spi.records.BlockRecordInfo; @@ -65,8 +66,10 @@ void returnsUnavailableHashIfNecessary() { @Test void blockValuesHasExpectedValues() { - given(blockRecordInfo.lastBlockNo()).willReturn(123L); - given(context.consensusNow()).willReturn(ETERNAL_NOW); + final var now = new Timestamp(1_234_567L, 890); + given(blockRecordInfo.blockNo()).willReturn(123L); + given(blockRecordInfo.currentBlockTimestamp()).willReturn(now); + given(context.blockRecordInfo()).willReturn(blockRecordInfo); final var blockValues = subject.blockValuesOf(456L); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/QueryContextHevmBlocksTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/QueryContextHevmBlocksTest.java index caf877d9d565..6046a3f5629c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/QueryContextHevmBlocksTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/QueryContextHevmBlocksTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.BDDMockito.given; +import com.hedera.hapi.node.base.Timestamp; import com.hedera.node.app.service.contract.impl.hevm.QueryContextHevmBlocks; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; import com.hedera.node.app.spi.records.BlockRecordInfo; @@ -48,7 +49,7 @@ class QueryContextHevmBlocksTest { @BeforeEach void setUp() { - subject = new QueryContextHevmBlocks(context, ETERNAL_NOW); + subject = new QueryContextHevmBlocks(context); given(context.blockRecordInfo()).willReturn(blockRecordInfo); } @@ -65,7 +66,10 @@ void returnsUnavailableHashIfNecessary() { @Test void blockValuesHasExpectedValues() { - given(blockRecordInfo.lastBlockNo()).willReturn(123L); + final var now = new Timestamp(1_234_567L, 890); + given(blockRecordInfo.blockNo()).willReturn(123L); + given(blockRecordInfo.currentBlockTimestamp()).willReturn(now); + given(context.blockRecordInfo()).willReturn(blockRecordInfo); final var blockValues = subject.blockValuesOf(456L); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java index a1522935dfda..a42a980e4715 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/RandomOps.java @@ -41,6 +41,7 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenSupplyType; import com.hederahashgraph.api.proto.java.TokenType; @@ -150,7 +151,7 @@ final HapiSpec retryLimitDemo() { cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 7L))); } - @HapiTest + @BddMethodIsNotATest final HapiSpec freezeDemo() { return customHapiSpec("FreezeDemo") .withProperties(Map.of("nodes", "127.0.0.1:50213:0.0.3,127.0.0.1:50214:0.0.4,127.0.0.1:50215:0.0.5")) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java index d98b2847380e..2e501d2b4f56 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/TxnReceiptRegression.java @@ -86,6 +86,7 @@ final HapiSpec returnsNotSupportedForMissingOp() { .then(getReceipt("success").forgetOp().hasAnswerOnlyPrecheck(NOT_SUPPORTED)); } + @HapiTest final HapiSpec receiptUnavailableAfterCacheTtl() { return defaultHapiSpec("ReceiptUnavailableAfterCacheTtl") .given() From f26302ce24acdf83197a4f8ea7073825a6d32cea Mon Sep 17 00:00:00 2001 From: Lev Povolotsky <16233475+povolev15@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:49:58 -0500 Subject: [PATCH 65/80] fix: Fix and enable all Schedule HapiTests (#10551) Signed-off-by: Lev Povolotsky Signed-off-by: Joseph Sinclair Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker Co-authored-by: Neeharika-Sompalli --- .../node/app/records/BlockRecordManager.java | 3 +- .../records/impl/BlockRecordManagerImpl.java | 6 +- .../node/app/workflows/SolvencyPreCheck.java | 3 +- .../workflows/handle/HandleContextImpl.java | 118 ++++-- .../app/workflows/handle/HandleWorkflow.java | 20 +- .../handle/ScheduleExpirationHook.java | 48 +++ .../handle/SystemFileUpdateFacility.java | 22 + .../handle/record/RecordListBuilder.java | 3 - ...mpoundSignatureVerificationFutureTest.java | 2 +- .../workflows/handle/HandleWorkflowTest.java | 60 ++- .../ConsensusSubmitMessageHandler.java | 2 + .../txns/ScheduleCreateResourceUsage.java | 21 + .../txns/ScheduleDeleteResourceUsage.java | 17 + .../txns/ScheduleSignResourceUsage.java | 17 + .../impl/WritableScheduleStoreImpl.java | 18 + .../handlers/AbstractScheduleHandler.java | 20 +- .../impl/handlers/ScheduleCreateHandler.java | 37 +- .../impl/handlers/ScheduleDeleteHandler.java | 31 +- .../impl/handlers/ScheduleSignHandler.java | 30 ++ .../impl/WritableScheduleStoreImplTest.java | 19 + .../schedule/WritableScheduleStore.java | 13 +- .../impl/handlers/ContractCallHandler.java | 16 + .../impl/handlers/ContractDeleteHandler.java | 16 + .../impl/handlers/ContractUpdateHandler.java | 16 + .../bdd/spec/utilops/FeatureFlags.java | 16 +- .../services/bdd/spec/utilops/UtilVerbs.java | 5 +- .../suites/fees/CostOfEverythingSuite.java | 48 +-- .../bdd/suites/leaky/FeatureFlagSuite.java | 6 +- .../SteadyStateThrottlingCheck.java | 3 + .../suites/schedule/ScheduleCreateSpecs.java | 2 +- .../suites/schedule/ScheduleDeleteSpecs.java | 1 + .../ScheduleExecutionSpecStateful.java | 26 ++ .../schedule/ScheduleExecutionSpecs.java | 375 +++++++++++++----- .../suites/schedule/ScheduleRecordSpecs.java | 9 +- .../suites/schedule/ScheduleSignSpecs.java | 42 ++ 35 files changed, 847 insertions(+), 244 deletions(-) create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/ScheduleExpirationHook.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java index 447d597c298b..a6ec32f59900 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/BlockRecordManager.java @@ -69,8 +69,9 @@ public interface BlockRecordManager extends BlockRecordInfo, AutoCloseable { * adjusted consensus time, not the platform assigned consensus time. Assuming the two are * different. * @param state The state to read BlockInfo from and update when new blocks are created + * @return true if a new block was created, false otherwise */ - void startUserTransaction(@NonNull Instant consensusTime, @NonNull HederaState state); + boolean startUserTransaction(@NonNull Instant consensusTime, @NonNull HederaState state); /** * "Advances the consensus clock" by updating the latest consensus timestamp that the node has handled. This should diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java index 8e8a706fd322..7be2b332f559 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java @@ -150,7 +150,7 @@ public void close() { /** * {@inheritDoc} */ - public void startUserTransaction(@NonNull final Instant consensusTime, @NonNull final HederaState state) { + public boolean startUserTransaction(@NonNull final Instant consensusTime, @NonNull final HederaState state) { if (EPOCH.equals(lastBlockInfo.firstConsTimeOfCurrentBlock())) { // This is the first transaction of the first block, so set both the firstConsTimeOfCurrentBlock // and the current consensus time to now @@ -162,7 +162,7 @@ public void startUserTransaction(@NonNull final Instant consensusTime, @NonNull .build(); persistLastBlockInfo(state); streamFileProducer.switchBlocks(-1, 0, consensusTime); - return; + return true; } // Check to see if we are at the boundary between blocks and should create a new one. Each block is covered @@ -196,7 +196,9 @@ public void startUserTransaction(@NonNull final Instant consensusTime, @NonNull } switchBlocksAt(consensusTime); + return true; } + return false; } /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java index fa3377e79663..06c3a7cbe0a1 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/SolvencyPreCheck.java @@ -146,7 +146,8 @@ public void checkSolvency( final var availableBalance = account.tinybarBalance(); final var offeredFee = txBody.transactionFee(); final ResponseCodeEnum insufficientFeeResponseCode; - if (ingestCheck) { // throw different exception for ingest + // Use this response code for either ingest or triggered schedule transaction + if (ingestCheck || fees.totalWithoutServiceFee() == 0L) { insufficientFeeResponseCode = INSUFFICIENT_PAYER_BALANCE; } else { insufficientFeeResponseCode = INSUFFICIENT_ACCOUNT_BALANCE; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java index e5672489556b..427773a55905 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java @@ -544,23 +544,7 @@ public T doDispatchPrecedingTransaction( if (category != TransactionCategory.USER && category != TransactionCategory.CHILD) { throw new IllegalArgumentException("Only user- or child-transactions can dispatch preceding transactions"); } - // This condition fails, because for lazy-account creation we charge fees, before dispatching the transaction, - // and the state will be modified. - // if (stack.depth() > 1) { - // throw new IllegalStateException( - // "Cannot dispatch a preceding transaction when a savepoint has been created"); - // } - - // This condition fails, because for auto-account creation we charge fees, before dispatching the transaction, - // and the state will be modified. - - // if (current().isModified()) { - // throw new IllegalStateException("Cannot dispatch a preceding transaction when the state - // has been modified"); - // } - - // run the transaction final var precedingRecordBuilder = recordBuilderFactory.get(); dispatchSyntheticTxn(syntheticPayer, txBody, PRECEDING, precedingRecordBuilder, callback); @@ -652,7 +636,6 @@ private void dispatchSyntheticTxn( return; } - final var childStack = new SavepointStackImpl(current()); final HederaFunctionality function; try { function = functionOf(txBody); @@ -665,26 +648,32 @@ private void dispatchSyntheticTxn( // Any keys verified for this dispatch (including the payer key if // required) should incorporate the provided callback final var childVerifier = callback != null ? new DelegateKeyVerifier(callback) : verifier; - final Key syntheticPayerKey; + final DispatchValidationResult dispatchValidationResult; try { - syntheticPayerKey = validate( + // Note the first parameter sets up that if there is no callback, then the keys are + // not verified here at all, which is apparently required for many contract calls. + dispatchValidationResult = validate( callback == null ? null : childVerifier, + callback, function, txBody, syntheticPayer, networkInfo().selfNodeInfo().nodeId(), - dispatchNeedsHapiPayerChecks(category)); + dispatchNeedsHapiPayerChecks(childCategory)); } catch (final PreCheckException e) { + // This will happen when the payer for a triggered transaction cannot afford the service fee, + // part of normal operations childRecordBuilder.status(e.responseCode()); return; } + final var childStack = new SavepointStackImpl(current()); final var childContext = new HandleContextImpl( txBody, function, 0, syntheticPayer, - syntheticPayerKey, + dispatchValidationResult == null ? null : dispatchValidationResult.key(), networkInfo, childCategory, childRecordBuilder, @@ -704,23 +693,35 @@ private void dispatchSyntheticTxn( solvencyPreCheck, childRecordFinalizer); + if (dispatchValidationResult != null) { + childContext.feeAccumulator.chargeFees( + syntheticPayer, networkInfo().selfNodeInfo().accountId(), dispatchValidationResult.fees()); + } try { dispatcher.dispatchHandle(childContext); childRecordBuilder.status(ResponseCodeEnum.SUCCESS); - final var finalizeContext = new ChildFinalizeContextImpl( - new ReadableStoreFactory(childStack), - new WritableStoreFactory(childStack, TokenService.NAME), - childRecordBuilder); - childRecordFinalizer.finalizeChildRecord(finalizeContext); - childStack.commitFullStack(); } catch (final HandleException e) { + if (e.shouldRollbackStack()) { + childStack.rollbackFullStack(); + if (dispatchValidationResult != null) { + childContext.feeAccumulator.chargeFees( + syntheticPayer, networkInfo().selfNodeInfo().accountId(), dispatchValidationResult.fees()); + } + } childRecordBuilder.status(e.getStatus()); recordListBuilder.revertChildrenOf(recordBuilder); } + final var finalizeContext = new ChildFinalizeContextImpl( + new ReadableStoreFactory(childStack), + new WritableStoreFactory(childStack, TokenService.NAME), + childRecordBuilder); + childRecordFinalizer.finalizeChildRecord(finalizeContext); + childStack.commitFullStack(); } - private @Nullable Key validate( + private @Nullable DispatchValidationResult validate( @Nullable final KeyVerifier keyVerifier, + @Nullable final Predicate callback, @NonNull final HederaFunctionality function, @NonNull final TransactionBody transactionBody, @NonNull final AccountID syntheticPayerId, @@ -733,7 +734,7 @@ private void dispatchSyntheticTxn( readableStoreFactory(), transactionBody, syntheticPayerId, configuration(), dispatcher); dispatcher.dispatchPreHandle(preHandleContext); - Key syntheticPayerKey = null; + DispatchValidationResult dispatchValidationResult = null; if (enforceHapiPayerChecks) { // In the current system only the schedule service needs to specify its // child transaction id, and will never use a duplicate, so this check is @@ -744,21 +745,22 @@ private void dispatchSyntheticTxn( } // Check the status and solvency of the payer - final var fee = dispatchComputeFees(body(), syntheticPayerId); + final var serviceFee = dispatchComputeFees(transactionBody, syntheticPayerId) + .copyBuilder() + .networkFee(0) + .nodeFee(0) + .build(); final var payerAccount = solvencyPreCheck.getPayerAccount(readableStoreFactory(), syntheticPayerId); - solvencyPreCheck.checkSolvency(body(), syntheticPayerId, functionality, payerAccount, fee, true); - // FUTURE - charge fees here? + solvencyPreCheck.checkSolvency( + transactionBody, syntheticPayerId, function, payerAccount, serviceFee, false); // Note we do NOT want to enforce the "time box" on valid start for // transaction ids dispatched by the schedule service, since these ids derive from their // ScheduleCreate id, which could have happened long ago - - syntheticPayerKey = payerAccount.keyOrThrow(); + final var syntheticPayerKey = payerAccount.keyOrThrow(); requireNonNull(keyVerifier, "keyVerifier must not be null when enforcing HAPI-style payer checks"); - final var payerKeyVerification = keyVerifier.verificationFor(syntheticPayerKey); - if (payerKeyVerification.failed()) { - throw new PreCheckException(INVALID_SIGNATURE); - } + validateKey(keyVerifier, callback, syntheticPayerKey); + dispatchValidationResult = new DispatchValidationResult(syntheticPayerKey, serviceFee); } // Given the current HTS system contract interface and ScheduleService @@ -771,10 +773,7 @@ private void dispatchSyntheticTxn( // "verification assistant" callback if (keyVerifier != null) { for (final var key : preHandleContext.requiredNonPayerKeys()) { - final var verification = keyVerifier.verificationFor(key); - if (verification.failed()) { - throw new PreCheckException(INVALID_SIGNATURE); - } + validateKey(keyVerifier, callback, key); } for (final var hollowAccount : preHandleContext.requiredHollowAccounts()) { final var verification = keyVerifier.verificationFor(hollowAccount.alias()); @@ -783,7 +782,32 @@ private void dispatchSyntheticTxn( } } } - return syntheticPayerKey; + return dispatchValidationResult; + } + + /** + * This method works around a corner case with the `KeyVerifier` design which prevents certain + * previously verified keys from succeeding. To correct that, we give the callback predicate + * one final opportunity to accept each key if validation fails. + * @param keyVerifier theKeyVerifier to use for signature validation + * @param callback a Predicate possibly provided by the service to validate additional keys. + * @param keyToValidate The Key to be validated and determined to meet or not meet signature requirements. + * @throws PreCheckException if the Key does not meet signature requirements. + */ + private static void validateKey( + final @NonNull KeyVerifier keyVerifier, final @Nullable Predicate callback, final Key keyToValidate) + throws PreCheckException { + // We must *attempt* payer verification (in case the same key is required for other aspects of the + // transaction); however, if the callback is set, then a failed verification can become + // success if the callback accepts the payer key. This works around an issue in scheduled + // transactions where the payer for a child transaction is "deemed valid" even though that payer + // did not sign the current user transaction. + // @todo('9447') Remove this special case when fixing the "deemed valid" behavior. + final SignatureVerification verification = keyVerifier.verificationFor(keyToValidate); + final boolean callbackFailed = callback != null ? !callback.test(keyToValidate) : true; + if (verification.failed() && callbackFailed) { + throw new PreCheckException(INVALID_SIGNATURE); + } } private void assertPayerIsAuthorized( @@ -800,7 +824,7 @@ private void assertPayerIsAuthorized( } // Check if the transaction is privileged and if the payer has the required privileges - final var privileges = authorizer.hasPrivilegedAuthorization(syntheticPayerId, functionality, transactionBody); + final var privileges = authorizer.hasPrivilegedAuthorization(syntheticPayerId, function, transactionBody); if (privileges == SystemPrivilege.UNAUTHORIZED) { throw new PreCheckException(ResponseCodeEnum.AUTHORIZATION_FAILED); } @@ -879,4 +903,10 @@ public enum PrecedingTransactionCategory { private boolean dispatchNeedsHapiPayerChecks(@NonNull final TransactionCategory category) { return category == SCHEDULED; } + + private record DispatchValidationResult(@NonNull Key key, @NonNull Fees fees) { + public DispatchValidationResult { + requireNonNull(key); + } + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 98e9dbfec3ed..524d51d6ce1b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -58,6 +58,8 @@ import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.records.BlockRecordManager; import com.hedera.node.app.service.file.ReadableFileStore; +import com.hedera.node.app.service.schedule.ScheduleService; +import com.hedera.node.app.service.schedule.WritableScheduleStore; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.records.ChildRecordFinalizer; @@ -88,6 +90,7 @@ import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.dispatcher.ServiceApiFactory; import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher; +import com.hedera.node.app.workflows.dispatcher.WritableStoreFactory; import com.hedera.node.app.workflows.handle.record.GenesisRecordsConsensusHook; import com.hedera.node.app.workflows.handle.record.RecordListBuilder; import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl; @@ -134,6 +137,7 @@ public class HandleWorkflow { private final HederaRecordCache recordCache; private final GenesisRecordsConsensusHook genesisRecordsTimeHook; private final StakingPeriodTimeHook stakingPeriodTimeHook; + private final ScheduleExpirationHook scheduleExpirationHook; private final FeeManager feeManager; private final ExchangeRateManager exchangeRateManager; private final ChildRecordFinalizer childRecordFinalizer; @@ -164,7 +168,8 @@ public HandleWorkflow( @NonNull final PlatformStateUpdateFacility platformStateUpdateFacility, @NonNull final SolvencyPreCheck solvencyPreCheck, @NonNull final Authorizer authorizer, - @NonNull final NetworkUtilizationManager networkUtilizationManager) { + @NonNull final NetworkUtilizationManager networkUtilizationManager, + @NonNull final ScheduleExpirationHook scheduleExpirationHook) { this.networkInfo = requireNonNull(networkInfo, "networkInfo must not be null"); this.preHandleWorkflow = requireNonNull(preHandleWorkflow, "preHandleWorkflow must not be null"); this.dispatcher = requireNonNull(dispatcher, "dispatcher must not be null"); @@ -187,6 +192,7 @@ public HandleWorkflow( this.authorizer = requireNonNull(authorizer, "authorizer must not be null"); this.networkUtilizationManager = requireNonNull(networkUtilizationManager, "networkUtilizationManager must not be null"); + this.scheduleExpirationHook = requireNonNull(scheduleExpirationHook, "scheduleExpirationHook must not be null"); } /** @@ -269,7 +275,7 @@ private void handleUserTransaction( @NonNull final NodeInfo creator, @NonNull final ConsensusTransaction platformTxn) { // Setup record builder list - blockRecordManager.startUserTransaction(consensusNow, state); + final boolean switchedBlocks = blockRecordManager.startUserTransaction(consensusNow, state); final var recordListBuilder = new RecordListBuilder(consensusNow); final var recordBuilder = recordListBuilder.userTransactionRecordBuilder(); @@ -295,6 +301,16 @@ private void handleUserTransaction( // Consensus hooks have now had a chance to publish any records from migrations; therefore we can begin handling // the user transaction blockRecordManager.advanceConsensusClock(consensusNow, state); + // Look for any expired schedules and delete them when new block is created + if (switchedBlocks) { + final var firstSecondToExpire = + blockRecordManager.firstConsTimeOfLastBlock().getEpochSecond(); + final var lastSecondToExpire = consensusNow.getEpochSecond(); + final var scheduleStore = + new WritableStoreFactory(stack, ScheduleService.NAME).getStore(WritableScheduleStore.class); + // purge all expired schedules between the first consensus time of last block and the current consensus time + scheduleExpirationHook.processExpiredSchedules(scheduleStore, firstSecondToExpire, lastSecondToExpire); + } TransactionBody txBody; AccountID payer = null; diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/ScheduleExpirationHook.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/ScheduleExpirationHook.java new file mode 100644 index 000000000000..67eea259545f --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/ScheduleExpirationHook.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.workflows.handle; + +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.service.schedule.WritableScheduleStore; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Instant; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Handles the daily staking period updates + */ +@Singleton +public class ScheduleExpirationHook { + private static final Logger logger = LogManager.getLogger(ScheduleExpirationHook.class); + + @Inject + public ScheduleExpirationHook() {} + + public void processExpiredSchedules( + @NonNull final WritableScheduleStore store, long firstSecondToExpire, final long lastSecondToExpire) { + requireNonNull(store); + // first transaction handled has a consensus time of 0 + if (firstSecondToExpire == Instant.EPOCH.getEpochSecond()) { + firstSecondToExpire = lastSecondToExpire; + } + store.purgeExpiredSchedulesBetween(firstSecondToExpire, lastSecondToExpire); + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java index 9a2f509e273a..cebbbab75a97 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java @@ -18,9 +18,11 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.ServicesConfigurationList; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.fees.ExchangeRateManager; @@ -33,8 +35,11 @@ import com.hedera.node.config.data.FilesConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.util.Collections; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -121,6 +126,7 @@ public ResponseCodeEnum handleTxBody(@NonNull final HederaState state, @NonNull final var permissions = FileUtilities.getFileContent(state, createFileID(config.hapiPermissions(), configuration)); configProvider.update(networkProperties, permissions); + logContentsOf("Network properties", networkProperties); backendThrottle.applyGasConfig(); frontendThrottle.applyGasConfig(); @@ -132,6 +138,7 @@ public ResponseCodeEnum handleTxBody(@NonNull final HederaState state, @NonNull FileUtilities.getFileContent(state, createFileID(config.networkProperties(), configuration)); final var permissions = FileUtilities.getFileContent(state, fileID); configProvider.update(networkProperties, permissions); + logContentsOf("API permissions", permissions); } else if (fileNum == config.throttleDefinitions()) { final var result = throttleManager.update(FileUtilities.getFileContent(state, fileID)); backendThrottle.rebuildFor(throttleManager.throttleDefinitions()); @@ -144,6 +151,21 @@ public ResponseCodeEnum handleTxBody(@NonNull final HederaState state, @NonNull return SUCCESS; } + private void logContentsOf(@NonNull final String configFileName, @NonNull final Bytes contents) { + try { + final var configList = ServicesConfigurationList.PROTOBUF.parseStrict(contents.toReadableSequentialData()); + final var printableConfigList = configList.nameValueOrElse(Collections.emptyList()).stream() + .map(pair -> pair.name() + "=" + pair.value()) + .collect(joining("\n\t")); + logger.info( + "Refreshing properties with following overrides to {}:\n\t{}", + configFileName, + printableConfigList.isBlank() ? "" : printableConfigList); + } catch (IOException ignore) { + // If this isn't parseable we won't have updated anything, also don't log + } + } + private FileID createFileID(final long fileNum, @NonNull final Configuration configuration) { final var hederaConfig = configuration.getConfigData(HederaConfig.class); return FileID.newBuilder() diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java index 38d306c630a7..515d94b940d0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/RecordListBuilder.java @@ -190,10 +190,7 @@ public SingleTransactionRecordBuilderImpl doAddPreceding( // user transaction. The second item is T-2, and so on. final var parentConsensusTimestamp = userTxnRecordBuilder.consensusNow(); final var consensusNow = parentConsensusTimestamp.minusNanos(precedingCount + 1L); - // FUTURE : For some reason, we do not set the exchange rate for preceding transactions in mono-service. - // Should be corrected after differential testing. final var recordBuilder = new SingleTransactionRecordBuilderImpl(consensusNow, reversingBehavior); - // .exchangeRate(userTxnRecordBuilder.exchangeRate()); precedingTxnRecordBuilders.add(recordBuilder); return recordBuilder; } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/signature/CompoundSignatureVerificationFutureTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/signature/CompoundSignatureVerificationFutureTest.java index 71fc83db5252..338165ed00e0 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/signature/CompoundSignatureVerificationFutureTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/signature/CompoundSignatureVerificationFutureTest.java @@ -247,7 +247,7 @@ void getBlocksUntilResultsAreAvailable() throws InterruptedException { th.start(); // Then it doesn't complete yet - assertThat(aboutToBlock.await(50, TimeUnit.MILLISECONDS)).isTrue(); + assertThat(aboutToBlock.await(250, TimeUnit.MILLISECONDS)).isTrue(); assertThat(futureIsDone.getCount()).isEqualTo(1); // And when the final future completes, then the get method returns diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java index 5b56965e28f3..ebf2b86c88a7 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java @@ -174,6 +174,9 @@ private static PreHandleResult createPreHandleResult(@NonNull Status status, @No @Mock private GenesisRecordsConsensusHook genesisRecordsTimeHook; + @Mock + private ScheduleExpirationHook scheduleExpirationHook; + @Mock private StakingPeriodTimeHook stakingPeriodTimeHook; @@ -279,7 +282,8 @@ void setup() throws PreCheckException { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager); + networkUtilizationManager, + scheduleExpirationHook); } @SuppressWarnings("ConstantConditions") @@ -304,7 +308,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -325,7 +330,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -346,7 +352,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -367,7 +374,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -388,7 +396,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -409,7 +418,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -430,7 +440,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -451,7 +462,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -472,7 +484,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -493,7 +506,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -514,7 +528,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -535,7 +550,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -556,7 +572,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -577,7 +594,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -598,7 +616,8 @@ void testContructorWithInvalidArguments() { null, solvencyPreCheck, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -619,7 +638,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, null, authorizer, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -640,7 +660,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, null, - networkUtilizationManager)) + networkUtilizationManager, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> new HandleWorkflow( networkInfo, @@ -661,7 +682,8 @@ void testContructorWithInvalidArguments() { platformStateUpdateFacility, solvencyPreCheck, authorizer, - null)) + null, + scheduleExpirationHook)) .isInstanceOf(NullPointerException.class); } diff --git a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/handlers/ConsensusSubmitMessageHandler.java b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/handlers/ConsensusSubmitMessageHandler.java index 5abad6dca713..e9889be94ccd 100644 --- a/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/handlers/ConsensusSubmitMessageHandler.java +++ b/hedera-node/hedera-consensus-service-impl/src/main/java/com/hedera/node/app/service/consensus/impl/handlers/ConsensusSubmitMessageHandler.java @@ -30,6 +30,7 @@ import static com.hedera.node.app.service.mono.pbj.PbjConverter.asBytes; import static com.hedera.node.app.service.mono.state.merkle.MerkleTopic.RUNNING_HASH_VERSION; import static com.hedera.node.app.spi.validation.Validations.mustExist; +import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; @@ -83,6 +84,7 @@ public void preHandle(@NonNull final PreHandleContext context) throws PreCheckEx // The topic ID must be present on the transaction and the topic must exist. final var topic = topicStore.getTopic(op.topicID()); mustExist(topic, INVALID_TOPIC_ID); + validateFalsePreCheck(topic.deleted(), INVALID_TOPIC_ID); // If a submit key is specified on the topic, then only those transactions signed by that key can be // submitted to the topic. If there is no submit key, then it is not required on the transaction. final var submitKey = topic.submitKey(); diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleCreateResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleCreateResourceUsage.java index 2cbcc9dc4152..17e8956b6980 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleCreateResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleCreateResourceUsage.java @@ -62,4 +62,25 @@ public FeeData usageGiven(final TransactionBody txn, final SigValueObj svo, fina return scheduleOpsUsage.scheduleCreateUsage(txn, sigUsage, lifetimeSecs); } + + public FeeData usageGiven( + final TransactionBody txn, + final SigValueObj svo, + final boolean longTermEnabled, + final long scheduledTxExpiryTimeSecs) { + final var op = txn.getScheduleCreate(); + final var sigUsage = new SigUsage(svo.getTotalSigCount(), svo.getSignatureSize(), svo.getPayerAcctSigCount()); + + final long lifetimeSecs; + if (op.hasExpirationTime() && longTermEnabled) { + lifetimeSecs = Math.max( + 0L, + op.getExpirationTime().getSeconds() + - txn.getTransactionID().getTransactionValidStart().getSeconds()); + } else { + lifetimeSecs = scheduledTxExpiryTimeSecs; + } + + return scheduleOpsUsage.scheduleCreateUsage(txn, sigUsage, lifetimeSecs); + } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleDeleteResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleDeleteResourceUsage.java index 073e1f6991a0..60692fac36a2 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleDeleteResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleDeleteResourceUsage.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.mono.fees.calculation.schedule.txns; +import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.node.app.hapi.fees.usage.SigUsage; import com.hedera.node.app.hapi.fees.usage.schedule.ScheduleOpsUsage; import com.hedera.node.app.hapi.utils.fee.SigValueObj; @@ -61,4 +62,20 @@ public FeeData usageGiven(final TransactionBody txn, final SigValueObj svo, fina return scheduleOpsUsage.scheduleDeleteUsage(txn, sigUsage, latestExpiry); } } + + public FeeData usageGiven( + final TransactionBody txn, + final SigValueObj svo, + final Schedule schedule, + final long scheduledTxExpiryTimeSecs) { + final var sigUsage = new SigUsage(svo.getTotalSigCount(), svo.getSignatureSize(), svo.getPayerAcctSigCount()); + + if (schedule != null) { + return scheduleOpsUsage.scheduleDeleteUsage(txn, sigUsage, schedule.calculatedExpirationSecond()); + } else { + final long latestExpiry = + txn.getTransactionID().getTransactionValidStart().getSeconds() + scheduledTxExpiryTimeSecs; + return scheduleOpsUsage.scheduleDeleteUsage(txn, sigUsage, latestExpiry); + } + } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleSignResourceUsage.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleSignResourceUsage.java index f1838cef18db..808bce0a1489 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleSignResourceUsage.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/fees/calculation/schedule/txns/ScheduleSignResourceUsage.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.mono.fees.calculation.schedule.txns; +import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.node.app.hapi.fees.usage.SigUsage; import com.hedera.node.app.hapi.fees.usage.schedule.ScheduleOpsUsage; import com.hedera.node.app.hapi.utils.fee.SigValueObj; @@ -61,4 +62,20 @@ public FeeData usageGiven(final TransactionBody txn, final SigValueObj svo, fina return scheduleOpsUsage.scheduleSignUsage(txn, sigUsage, latestExpiry); } } + + public FeeData usageGiven( + final TransactionBody txn, + final SigValueObj svo, + final Schedule schedule, + final long scheduledTxExpiryTimeSecs) { + final var sigUsage = new SigUsage(svo.getTotalSigCount(), svo.getSignatureSize(), svo.getPayerAcctSigCount()); + + if (schedule != null) { + return scheduleOpsUsage.scheduleSignUsage(txn, sigUsage, schedule.calculatedExpirationSecond()); + } else { + final long latestExpiry = + txn.getTransactionID().getTransactionValidStart().getSeconds() + scheduledTxExpiryTimeSecs; + return scheduleOpsUsage.scheduleSignUsage(txn, sigUsage, latestExpiry); + } + } } diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java index cb5777614651..ab5c774513d4 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImpl.java @@ -146,4 +146,22 @@ private Schedule markDeleted(final Schedule schedule, final Instant consensusTim schedule.originalCreateTransaction(), schedule.signatories()); } + /** @inheritDoc */ + @Override + public void purgeExpiredSchedulesBetween(long firstSecondToExpire, long lastSecondToExpire) { + for (long i = firstSecondToExpire; i <= lastSecondToExpire; i++) { + final var second = new ProtoLong(i); + final var scheduleList = schedulesByExpirationMutable.get(second); + if (scheduleList != null) { + for (final var schedule : scheduleList.schedules()) { + schedulesByIdMutable.remove(schedule.scheduleIdOrThrow()); + + final ProtoString hash = new ProtoString(ScheduleStoreUtility.calculateStringHash(schedule)); + schedulesByEqualityMutable.remove(hash); + logger.info("Purging expired schedule {} from state.", schedule.scheduleIdOrThrow()); + } + schedulesByExpirationMutable.remove(second); + } + } + } } diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/AbstractScheduleHandler.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/AbstractScheduleHandler.java index f95678e5706e..d2be9e56ee4e 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/AbstractScheduleHandler.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/AbstractScheduleHandler.java @@ -46,12 +46,15 @@ import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListSet; import java.util.function.Predicate; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Provides some implementation support needed for both the {@link ScheduleCreateHandler} and {@link * ScheduleSignHandler}. */ abstract class AbstractScheduleHandler { + private final Logger log = LogManager.getLogger(AbstractScheduleHandler.class); protected static final String NULL_CONTEXT_MESSAGE = "Dispatcher called the schedule handler with a null context; probable internal data corruption."; @@ -228,7 +231,13 @@ protected ResponseCodeEnum validate( (expiration != Schedule.DEFAULT.calculatedExpirationSecond() ? Instant.ofEpochSecond(expiration) : Instant.MAX); - if (effectiveConsensusTime.isBefore(calculatedExpiration)) { + log.info( + "Evaluating schedule id {} for execution with calculatedExpiration {}, " + + "consensus time {}", + scheduleToValidate.scheduleId(), + calculatedExpiration, + effectiveConsensusTime); + if (calculatedExpiration.getEpochSecond() >= effectiveConsensusTime.getEpochSecond()) { result = ResponseCodeEnum.OK; } else { // We are past expiration time @@ -281,7 +290,14 @@ protected boolean tryToExecuteSchedule( @NonNull final ResponseCodeEnum validationResult, final boolean isLongTermEnabled) { if (canExecute(remainingSignatories, isLongTermEnabled, validationResult, scheduleToExecute)) { - final Predicate assistant = new DispatchPredicate(validSignatories); + final AccountID originalPayer = scheduleToExecute + .originalCreateTransaction() + .transactionID() + .accountID(); + final Set acceptedSignatories = new HashSet<>(); + acceptedSignatories.addAll(validSignatories); + acceptedSignatories.add(getKeyForAccount(context, originalPayer)); + final Predicate assistant = new DispatchPredicate(acceptedSignatories); // This sets the child transaction ID to scheduled. final TransactionBody childTransaction = HandlerUtility.childAsOrdinary(scheduleToExecute); final ScheduleRecordBuilder recordBuilder = context.dispatchChildTransaction( diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleCreateHandler.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleCreateHandler.java index 8a915abec1c7..582146897fbe 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleCreateHandler.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleCreateHandler.java @@ -16,10 +16,14 @@ package com.hedera.node.app.service.schedule.impl.handlers; +import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.scheduled.SchedulableTransactionBody; import com.hedera.hapi.node.scheduled.SchedulableTransactionBody.DataOneOfType; @@ -27,9 +31,13 @@ import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.hapi.fees.usage.schedule.ScheduleOpsUsage; +import com.hedera.node.app.service.mono.fees.calculation.schedule.txns.ScheduleCreateResourceUsage; import com.hedera.node.app.service.schedule.ScheduleRecordBuilder; import com.hedera.node.app.service.schedule.WritableScheduleStore; import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -52,7 +60,6 @@ */ @Singleton public class ScheduleCreateHandler extends AbstractScheduleHandler implements TransactionHandler { - @Inject public ScheduleCreateHandler() { super(); @@ -128,14 +135,18 @@ public void handle(@NonNull final HandleContext context) throws HandleException final Instant currentConsensusTime = context.consensusNow(); final WritableScheduleStore scheduleStore = context.writableStore(WritableScheduleStore.class); final SchedulingConfig schedulingConfig = context.configuration().getConfigData(SchedulingConfig.class); + final LedgerConfig ledgerConfig = context.configuration().getConfigData(LedgerConfig.class); final boolean isLongTermEnabled = schedulingConfig.longTermEnabled(); // Note: We must store the original ScheduleCreate transaction body in the Schedule so that we can compare // those bytes to any new ScheduleCreate transaction for detecting duplicate ScheduleCreate // transactions. SchedulesByEquality is the virtual map for that task final TransactionBody currentTransaction = context.body(); if (currentTransaction.hasScheduleCreate()) { + final var expirationSeconds = isLongTermEnabled + ? schedulingConfig.maxExpirationFutureSeconds() + : ledgerConfig.scheduleTxExpiryTimeSecs(); final Schedule provisionalSchedule = HandlerUtility.createProvisionalSchedule( - currentTransaction, currentConsensusTime, schedulingConfig.maxExpirationFutureSeconds()); + currentTransaction, currentConsensusTime, expirationSeconds); checkSchedulableWhitelistHandle(provisionalSchedule, schedulingConfig); context.attributeValidator().validateMemo(provisionalSchedule.memo()); context.attributeValidator() @@ -327,4 +338,26 @@ private SchedulableTransactionBody getSchedulableTransaction(@NonNull final Tran throw new PreCheckException(ResponseCodeEnum.INVALID_TRANSACTION_BODY); } } + + @NonNull + @Override + public Fees calculateFees(@NonNull final FeeContext feeContext) { + requireNonNull(feeContext); + final var op = feeContext.body(); + final var config = feeContext.configuration(); + final var ledgerConfig = config.getConfigData(LedgerConfig.class); + final var schedulingConfig = config.getConfigData(SchedulingConfig.class); + final var subType = (op.scheduleCreateOrThrow().hasScheduledTransactionBody() + && op.scheduleCreateOrThrow().scheduledTransactionBody().hasContractCall()) + ? SubType.SCHEDULE_CREATE_CONTRACT_CALL + : SubType.DEFAULT; + + return feeContext.feeCalculator(subType).legacyCalculate(sigValueObj -> new ScheduleCreateResourceUsage( + new ScheduleOpsUsage(), null) + .usageGiven( + fromPbj(op), + sigValueObj, + schedulingConfig.longTermEnabled(), + ledgerConfig.scheduleTxExpiryTimeSecs())); + } } diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleDeleteHandler.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleDeleteHandler.java index fbaedba499f9..0e5aed7f2d68 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleDeleteHandler.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleDeleteHandler.java @@ -16,22 +16,31 @@ package com.hedera.node.app.service.schedule.impl.handlers; +import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.scheduled.ScheduleDeleteTransactionBody; import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.hapi.fees.usage.schedule.ScheduleOpsUsage; +import com.hedera.node.app.service.mono.fees.calculation.schedule.txns.ScheduleDeleteResourceUsage; import com.hedera.node.app.service.schedule.ReadableScheduleStore; import com.hedera.node.app.service.schedule.ScheduleRecordBuilder; import com.hedera.node.app.service.schedule.WritableScheduleStore; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.signatures.SignatureVerification; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.TransactionHandler; +import com.hedera.node.config.data.LedgerConfig; import com.hedera.node.config.data.SchedulingConfig; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -44,7 +53,6 @@ */ @Singleton public class ScheduleDeleteHandler extends AbstractScheduleHandler implements TransactionHandler { - @Inject public ScheduleDeleteHandler() { super(); @@ -157,4 +165,25 @@ protected Schedule reValidate( throw new HandleException(translated.responseCode()); } } + + @NonNull + @Override + public Fees calculateFees(@NonNull final FeeContext feeContext) { + requireNonNull(feeContext); + final var op = feeContext.body(); + + final var scheduleStore = feeContext.readableStore(ReadableScheduleStore.class); + final var schedule = scheduleStore.get(op.scheduleDeleteOrThrow().scheduleIDOrThrow()); + + return feeContext.feeCalculator(SubType.DEFAULT).legacyCalculate(sigValueObj -> new ScheduleDeleteResourceUsage( + new ScheduleOpsUsage(), null) + .usageGiven( + fromPbj(op), + sigValueObj, + schedule, + feeContext + .configuration() + .getConfigData(LedgerConfig.class) + .scheduleTxExpiryTimeSecs())); + } } diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleSignHandler.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleSignHandler.java index 675f1e12712f..7c45f47affa3 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleSignHandler.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/ScheduleSignHandler.java @@ -16,25 +16,34 @@ package com.hedera.node.app.service.schedule.impl.handlers; +import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.ScheduleID; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.scheduled.SchedulableTransactionBody; import com.hedera.hapi.node.scheduled.ScheduleSignTransactionBody; import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.hapi.fees.usage.schedule.ScheduleOpsUsage; +import com.hedera.node.app.service.mono.fees.calculation.schedule.txns.ScheduleSignResourceUsage; import com.hedera.node.app.service.schedule.ReadableScheduleStore; import com.hedera.node.app.service.schedule.ScheduleRecordBuilder; import com.hedera.node.app.service.schedule.WritableScheduleStore; import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.app.spi.workflows.TransactionHandler; +import com.hedera.node.config.data.LedgerConfig; import com.hedera.node.config.data.SchedulingConfig; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -186,4 +195,25 @@ private ScheduleSignTransactionBody getValidScheduleSignBody(@Nullable final Tra throw new PreCheckException(ResponseCodeEnum.INVALID_TRANSACTION); } } + + @NonNull + @Override + public Fees calculateFees(@NonNull final FeeContext feeContext) { + requireNonNull(feeContext); + final var op = feeContext.body(); + + final var scheduleStore = feeContext.readableStore(ReadableScheduleStore.class); + final var schedule = scheduleStore.get(op.scheduleSignOrThrow().scheduleIDOrThrow()); + + return feeContext.feeCalculator(SubType.DEFAULT).legacyCalculate(sigValueObj -> new ScheduleSignResourceUsage( + new ScheduleOpsUsage(), null) + .usageGiven( + fromPbj(op), + sigValueObj, + schedule, + feeContext + .configuration() + .getConfigData(LedgerConfig.class) + .scheduleTxExpiryTimeSecs())); + } } diff --git a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java index 82523acdc78a..cdab53791f22 100644 --- a/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java +++ b/hedera-node/hedera-schedule-service-impl/src/test/java/com/hedera/node/app/service/schedule/impl/WritableScheduleStoreImplTest.java @@ -69,6 +69,25 @@ void verifyPutModifiesState() { assertThat(actual.signatories()).containsExactlyInAnyOrderElementsOf(modifiedSignatories); } + @Test + void purgesExpiredSchedules() { + final ScheduleID idToDelete = scheduleInState.scheduleId(); + final Schedule actual = writableById.get(idToDelete); + final var expirationTime = actual.calculatedExpirationSecond(); + assertThat(actual).isNotNull(); + assertThat(actual.signatories()).containsExactlyInAnyOrderElementsOf(scheduleInState.signatories()); + writableSchedules.purgeExpiredSchedulesBetween(expirationTime - 1, expirationTime + 1); + + final var purged = writableSchedules.get(idToDelete); + assertThat(purged).isNull(); + + final var byEquality = writableSchedules.getByEquality(actual); + assertThat(byEquality).isNull(); + + final var byExpiry = writableSchedules.getByExpirationSecond(expirationTime); + assertThat(byExpiry).isNull(); + } + @NonNull static Schedule replaceSignatoriesAndMarkExecuted( @NonNull final Schedule schedule, diff --git a/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/WritableScheduleStore.java b/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/WritableScheduleStore.java index 954035e595fd..6c51bfa8a86f 100644 --- a/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/WritableScheduleStore.java +++ b/hedera-node/hedera-schedule-service/src/main/java/com/hedera/node/app/service/schedule/WritableScheduleStore.java @@ -33,9 +33,16 @@ public interface WritableScheduleStore extends ReadableScheduleStore { * @throws IllegalStateException if the {@link ScheduleID} to be deleted is not present in this state, * or the ID value has a mismatched realm or shard for this node. */ - public Schedule delete(@Nullable final ScheduleID scheduleToDelete, @NonNull final Instant consensusTime); + Schedule delete(@Nullable final ScheduleID scheduleToDelete, @NonNull final Instant consensusTime); - public Schedule getForModify(final ScheduleID idToFind); + Schedule getForModify(final ScheduleID idToFind); - public void put(Schedule scheduleToAdd); + void put(Schedule scheduleToAdd); + + /** + * Purges expired schedules from the store. + * @param firstSecondToExpire The consensus second of the first schedule to expire. + * @param lastSecondToExpire The consensus second of the last schedule to expire. + */ + void purgeExpiredSchedulesBetween(long firstSecondToExpire, long lastSecondToExpire); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java index 4b20ae01bd7c..8f1f2bfb24af 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java @@ -18,11 +18,17 @@ import static com.hedera.hapi.node.base.HederaFunctionality.CONTRACT_CALL; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.throwIfUnsuccessful; +import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.SubType; +import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder; import com.hedera.node.app.service.contract.impl.exec.TransactionComponent; import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; +import com.hedera.node.app.service.mono.fees.calculation.contract.txns.ContractCallResourceUsage; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreHandleContext; @@ -64,4 +70,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException public void preHandle(@NonNull final PreHandleContext context) { // No non-payer signatures to verify } + + @NonNull + @Override + public Fees calculateFees(@NonNull final FeeContext feeContext) { + requireNonNull(feeContext); + final var op = feeContext.body(); + return feeContext.feeCalculator(SubType.DEFAULT).legacyCalculate(sigValueObj -> new ContractCallResourceUsage( + new SmartContractFeeBuilder()) + .usageGiven(fromPbj(op), sigValueObj, null)); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java index 755fb303bae8..820b5af53e6d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractDeleteHandler.java @@ -25,6 +25,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.OBTAINER_SAME_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.PERMANENT_REMOVAL_REQUIRES_SYSTEM_INITIATION; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumericContractId; +import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; import static com.hedera.node.app.spi.validation.Validations.mustExist; import static com.hedera.node.app.spi.workflows.HandleException.validateFalse; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; @@ -32,12 +33,17 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.contract.ContractDeleteTransactionBody; import com.hedera.hapi.node.state.token.Account; +import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder; import com.hedera.node.app.service.contract.impl.records.ContractDeleteRecordBuilder; +import com.hedera.node.app.service.mono.fees.calculation.contract.txns.ContractDeleteResourceUsage; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.api.TokenServiceApi.FreeAliasOnDeletion; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -115,4 +121,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException ? accountStore.getAccountById(op.transferAccountIDOrThrow()) : accountStore.getContractById(op.transferContractIDOrThrow()); } + + @NonNull + @Override + public Fees calculateFees(@NonNull final FeeContext feeContext) { + requireNonNull(feeContext); + final var op = feeContext.body(); + return feeContext.feeCalculator(SubType.DEFAULT).legacyCalculate(sigValueObj -> new ContractDeleteResourceUsage( + new SmartContractFeeBuilder()) + .usageGiven(fromPbj(op), sigValueObj, null)); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java index 001cef331fb5..bf73e613cf6b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractUpdateHandler.java @@ -25,6 +25,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.MODIFYING_IMMUTABLE_CONTRACT; import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; import static com.hedera.hapi.node.base.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; +import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj; import static com.hedera.node.app.service.token.api.AccountSummariesApi.SENTINEL_ACCOUNT_ID; import static com.hedera.node.app.spi.HapiUtils.EMPTY_KEY_LIST; import static com.hedera.node.app.spi.validation.ExpiryMeta.NA; @@ -36,11 +37,16 @@ import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.contract.ContractUpdateTransactionBody; import com.hedera.hapi.node.state.token.Account; +import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder; import com.hedera.node.app.service.contract.impl.records.ContractUpdateRecordBuilder; +import com.hedera.node.app.service.mono.fees.calculation.contract.txns.ContractUpdateResourceUsage; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.api.TokenServiceApi; +import com.hedera.node.app.spi.fees.FeeContext; +import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.key.KeyUtils; import com.hedera.node.app.spi.validation.ExpiryMeta; import com.hedera.node.app.spi.workflows.HandleContext; @@ -278,4 +284,14 @@ public Account update( } return builder.build(); } + + @NonNull + @Override + public Fees calculateFees(@NonNull final FeeContext feeContext) { + requireNonNull(feeContext); + final var op = feeContext.body(); + return feeContext.feeCalculator(SubType.DEFAULT).legacyCalculate(sigValueObj -> new ContractUpdateResourceUsage( + new SmartContractFeeBuilder()) + .usageGiven(fromPbj(op), sigValueObj, null)); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/FeatureFlags.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/FeatureFlags.java index dd6376218439..4c0a7effc6e7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/FeatureFlags.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/FeatureFlags.java @@ -17,24 +17,28 @@ package com.hedera.services.bdd.spec.utilops; import com.hedera.services.bdd.suites.HapiSuite; +import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Arrays; +import java.util.List; import java.util.Map; public enum FeatureFlags { FEATURE_FLAGS; - public Map allEnabled() { - return all(HapiSuite.TRUE_VALUE); + public Map allEnabled(@NonNull final String... exceptFeatures) { + return all(HapiSuite.TRUE_VALUE, Arrays.asList(exceptFeatures)); } public Map allDisabled() { - return all(HapiSuite.FALSE_VALUE); + return all(HapiSuite.FALSE_VALUE, List.of()); } @SuppressWarnings("unchecked") - private Map all(final String choice) { - return Map.ofEntries( - Arrays.stream(NAMES).map(name -> Map.entry(name, choice)).toArray(Map.Entry[]::new)); + private Map all(final String choice, @NonNull final List exceptFeatures) { + return Map.ofEntries(Arrays.stream(NAMES) + .filter(name -> !exceptFeatures.contains(name)) + .map(name -> Map.entry(name, choice)) + .toArray(Map.Entry[]::new)); } private static final String[] NAMES = { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java index 50859a34905d..376d662d5475 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java @@ -544,8 +544,9 @@ public static HapiSpecOperation resetToDefault(String... properties) { return overridingAllOf(defaultValues); } - public static HapiSpecOperation enableAllFeatureFlagsAndDisableContractThrottles() { - final Map allOverrides = new HashMap<>(FeatureFlags.FEATURE_FLAGS.allEnabled()); + public static HapiSpecOperation enableAllFeatureFlagsAndDisableContractThrottles( + @NonNull final String... exceptFeatures) { + final Map allOverrides = new HashMap<>(FeatureFlags.FEATURE_FLAGS.allEnabled(exceptFeatures)); allOverrides.putAll(Map.of( "contracts.throttle.throttleByGas", FALSE_VALUE, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java index 7f827eed0370..0997c4621ec4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CostOfEverythingSuite.java @@ -29,19 +29,14 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; import static java.util.stream.Collectors.toList; @@ -85,9 +80,7 @@ public List getSpecsInSuite() { // cryptoGetAccountInfoPaths(), // cryptoGetAccountRecordsPaths(), // transactionGetRecordPaths(), - miscContractCreatesAndCalls() - // canonicalScheduleOpsHaveExpectedUsdFees() - ) + miscContractCreatesAndCalls()) .map(Stream::of) .reduce(Stream.empty(), Stream::concat) .collect(toList()); @@ -99,45 +92,6 @@ HapiSpec[] transactionGetRecordPaths() { }; } - HapiSpec canonicalScheduleOpsHaveExpectedUsdFees() { - return customHapiSpec("CanonicalScheduleOps") - .withProperties(Map.of( - "nodes", "35.231.208.148", - "default.payer.pemKeyLoc", "previewtestnet-account2.pem", - "default.payer.pemKeyPassphrase", "")) - .given( - cryptoCreate(PAYING_SENDER).balance(ONE_HUNDRED_HBARS), - cryptoCreate(RECEIVER).balance(0L).receiverSigRequired(true)) - .when( - scheduleCreate( - CANONICAL, - cryptoTransfer(tinyBarsFromTo(PAYING_SENDER, RECEIVER, 1L)) - .blankMemo() - .fee(ONE_HBAR)) - .via("canonicalCreation") - .payingWith(PAYING_SENDER) - .adminKey(PAYING_SENDER), - getScheduleInfo(CANONICAL).payingWith(PAYING_SENDER), - scheduleSign(CANONICAL) - .via("canonicalSigning") - .payingWith(PAYING_SENDER) - .alsoSigningWith(RECEIVER), - scheduleCreate( - "tbd", - cryptoTransfer(tinyBarsFromTo(PAYING_SENDER, RECEIVER, 1L)) - .memo("") - .fee(ONE_HBAR) - .blankMemo() - .signedBy(PAYING_SENDER)) - .payingWith(PAYING_SENDER) - .adminKey(PAYING_SENDER), - scheduleDelete("tbd").via("canonicalDeletion").payingWith(PAYING_SENDER)) - .then( - validateChargedUsdWithin("canonicalCreation", 0.01, 3.0), - validateChargedUsdWithin("canonicalSigning", 0.001, 3.0), - validateChargedUsdWithin("canonicalDeletion", 0.001, 3.0)); - } - @HapiTest HapiSpec miscContractCreatesAndCalls() { // Note that contracts are prohibited to sending value to system diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java index b85db47465e0..27a6f0774175 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java @@ -35,6 +35,8 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.enableAllFeatureFlagsAndDisableContractThrottles; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; @@ -97,7 +99,9 @@ final HapiSpec enableAllFeatureFlagsAndDisableThrottlesForFurtherCiTesting() { return defaultHapiSpec("enableAllFeatureFlagsAndDisableThrottlesForFurtherCiTesting") .given() .when() - .then(enableAllFeatureFlagsAndDisableContractThrottles()); + .then( + ifNotHapiTest(enableAllFeatureFlagsAndDisableContractThrottles()), + ifHapiTest(enableAllFeatureFlagsAndDisableContractThrottles("scheduling.longTermEnabled"))); } private HapiSpecOperation confirmAutoCreationNotSupported() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java index 4c8403be0380..69f4d9dec8b9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/SteadyStateThrottlingCheck.java @@ -187,6 +187,7 @@ final HapiSpec restoreDevLimits() { .payingWith(ADDRESS_BOOK_CONTROL)); } + @HapiTest final HapiSpec checkTps(String txn, double expectedTps, Function provider) { return checkCustomNetworkTps(txn, expectedTps, provider, Collections.emptyMap()); } @@ -204,6 +205,7 @@ final HapiSpec checkTps(String txn, double expectedTps, Function */ + @HapiTest @SuppressWarnings("java:S5960") final HapiSpec checkCustomNetworkTps( String txn, double expectedTps, Function provider, Map custom) { @@ -229,6 +231,7 @@ final HapiSpec checkCustomNetworkTps( })); } + @HapiTest final HapiSpec checkBalanceQps(int burstSize, double expectedQps) { return defaultHapiSpec("CheckBalanceQps") .given(cryptoCreate("curious").payingWith(GENESIS)) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java index 546cc9a3b049..16d2f127df44 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java @@ -493,7 +493,7 @@ NEVER_TO_BE, cryptoCreate("nope").key(GENESIS).receiverSigRequired(true)) .hasKnownStatus(ACCOUNT_ID_DOES_NOT_EXIST)); } - // Flaky test + @HapiTest public HapiSpec doesntTriggerUntilPayerSigns() { return defaultHapiSpec("DoesntTriggerUntilPayerSigns") .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java index 866716d145e5..2df8ebf2742b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java @@ -126,6 +126,7 @@ final HapiSpec deletingNonExistingFails() { scheduleDelete("0.0.0").fee(ONE_HBAR).hasKnownStatus(INVALID_SCHEDULE_ID)); } + @HapiTest final HapiSpec deletingExecutedIsPointless() { return defaultHapiSpec("DeletingExecutedIsPointless") .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java index ee7e1ae12413..b4fc8edde973 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java @@ -37,6 +37,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs.addAllToWhitelist; import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -45,6 +46,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNRESOLVABLE_REQUIRED_SIGNERS; import com.google.protobuf.ByteString; +import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecSetup; @@ -55,8 +57,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestMethodOrder; @HapiTestSuite +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ScheduleExecutionSpecStateful extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleExecutionSpecStateful.class); @@ -95,6 +101,7 @@ public static void main(String... args) { public List getSpecsInSuite() { return withAndWithoutLongTermEnabled(() -> List.of( /* Stateful specs from ScheduleExecutionSpecs */ + suiteSetup(), scheduledUniqueMintFailsWithNftsDisabled(), scheduledUniqueBurnFailsWithNftsDisabled(), scheduledBurnWithInvalidTokenThrowsUnresolvableSigners(), @@ -103,6 +110,15 @@ public List getSpecsInSuite() { suiteCleanup())); } + @HapiTest + @Order(1) + final HapiSpec suiteSetup() { + // Managing whitelist for these is error-prone, so just whitelist everything by default. + return defaultHapiSpec("suiteSetup").given().when().then(addAllToWhitelist()); + } + + @HapiTest + @Order(4) final HapiSpec scheduledBurnWithInvalidTokenThrowsUnresolvableSigners() { return defaultHapiSpec("ScheduledBurnWithInvalidTokenThrowsUnresolvableSigners") .given(cryptoCreate(SCHEDULE_PAYER)) @@ -112,6 +128,8 @@ final HapiSpec scheduledBurnWithInvalidTokenThrowsUnresolvableSigners() { .then(); } + @HapiTest + @Order(2) final HapiSpec scheduledUniqueMintFailsWithNftsDisabled() { return defaultHapiSpec("ScheduledUniqueMintFailsWithNftsDisabled") .given( @@ -142,6 +160,8 @@ final HapiSpec scheduledUniqueMintFailsWithNftsDisabled() { .overridingProps(Map.of(TOKENS_NFTS_ARE_ENABLED, "true"))); } + @HapiTest + @Order(3) final HapiSpec scheduledUniqueBurnFailsWithNftsDisabled() { return defaultHapiSpec("ScheduledUniqueBurnFailsWithNftsDisabled") .given( @@ -172,6 +192,8 @@ final HapiSpec scheduledUniqueBurnFailsWithNftsDisabled() { .overridingProps(Map.of(TOKENS_NFTS_ARE_ENABLED, "true"))); } + @HapiTest + @Order(5) public HapiSpec executionWithTransferListWrongSizedFails() { long transferAmount = 1L; long senderBalance = 1000L; @@ -218,6 +240,8 @@ public HapiSpec executionWithTransferListWrongSizedFails() { })); } + @HapiTest + @Order(6) final HapiSpec executionWithTokenTransferListSizeExceedFails() { String xToken = "XXX"; String invalidSchedule = "withMaxTokenTransfer"; @@ -253,6 +277,8 @@ final HapiSpec executionWithTokenTransferListSizeExceedFails() { getAccountBalance(xTreasury).hasTokenBalance(xToken, 100)); } + @HapiTest + @Order(7) final HapiSpec suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java index eea4e7ebd250..3b43cdad29e7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java @@ -61,6 +61,8 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeAbort; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.prepareUpgrade; @@ -98,6 +100,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.METADATA_TOO_LONG; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_NEW_VALID_SIGNATURES; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PAYER_ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SOME_SIGNATURES_WERE_INVALID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -117,6 +120,7 @@ import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; +import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.AccountID; @@ -124,6 +128,7 @@ import com.hederahashgraph.api.proto.java.TransactionID; import com.swirlds.test.framework.config.TestConfigBuilder; import java.util.Arrays; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -134,8 +139,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestMethodOrder; @HapiTestSuite +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ScheduleExecutionSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleExecutionSpecs.class); private static final String A_TOKEN = "token"; @@ -262,32 +271,17 @@ public List getSpecsInSuite() { suiteCleanup())); } - final HapiSpec suiteCleanup() { - return defaultHapiSpec("suiteCleanup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_WHITELIST, defaultWhitelist))); - } - + @HapiTest + @Order(1) final HapiSpec suiteSetup() { // Managing whitelist for these is error-prone, so just whitelist everything by default. - final List whitelistNames = new LinkedList<>(); - for (final HederaFunctionality enumValue : HederaFunctionality.values()) { - whitelistNames.add(enumValue.protoName()); - } - final String whitelistAll = String.join(",", whitelistNames); - return defaultHapiSpec("suiteSetup") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_WHITELIST, whitelistAll))); + return defaultHapiSpec("suiteSetup").given().when().then(addAllToWhitelist()); } // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(18) final HapiSpec scheduledBurnFailsWithInvalidTxBody() { return defaultHapiSpec("ScheduledBurnFailsWithInvalidTxBody") .given( @@ -299,20 +293,26 @@ final HapiSpec scheduledBurnFailsWithInvalidTxBody() { .supplyKey(SUPPLY_KEY) .treasury(TREASURY) .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) - .initialSupply(0), + .initialSupply(0)) + .when(ifHapiTest(scheduleCreate(A_SCHEDULE, invalidBurnToken(A_TOKEN, List.of(1L, 2L), 123)) + .designatingPayer(SCHEDULE_PAYER) + .hasKnownStatus(INVALID_TRANSACTION_BODY))) + .then(ifNotHapiTest( scheduleCreate(A_SCHEDULE, invalidBurnToken(A_TOKEN, List.of(1L, 2L), 123)) .designatingPayer(SCHEDULE_PAYER) - .via(FAILING_TXN)) - .when(scheduleSign(A_SCHEDULE) - .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .hasKnownStatus(SUCCESS)) - .then(getTxnRecord(FAILING_TXN) - .scheduled() - .hasPriority(recordWith().status(INVALID_TRANSACTION_BODY))); + .via(FAILING_TXN), + scheduleSign(A_SCHEDULE) + .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) + .hasKnownStatus(SUCCESS), + getTxnRecord(FAILING_TXN) + .scheduled() + .hasPriority(recordWith().status(INVALID_TRANSACTION_BODY)))); } // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(23) final HapiSpec scheduledMintFailsWithInvalidTxBody() { return defaultHapiSpec("ScheduledMintFailsWithInvalidTxBody") .given( @@ -324,23 +324,30 @@ final HapiSpec scheduledMintFailsWithInvalidTxBody() { .supplyKey(SUPPLY_KEY) .treasury(TREASURY) .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) - .initialSupply(0), + .initialSupply(0)) + .when(ifNotHapiTest( scheduleCreate( A_SCHEDULE, invalidMintToken(A_TOKEN, List.of(ByteString.copyFromUtf8("m1")), 123)) .designatingPayer(SCHEDULE_PAYER) - .via(FAILING_TXN)) - .when(scheduleSign(A_SCHEDULE) - .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .hasKnownStatus(SUCCESS)) - .then( + .via(FAILING_TXN), + scheduleSign(A_SCHEDULE) + .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) + .hasKnownStatus(SUCCESS), getTxnRecord(FAILING_TXN) .scheduled() - .hasPriority(recordWith().status(INVALID_TRANSACTION_BODY)), + .hasPriority(recordWith().status(INVALID_TRANSACTION_BODY)))) + .then( + ifHapiTest(scheduleCreate( + A_SCHEDULE, + invalidMintToken(A_TOKEN, List.of(ByteString.copyFromUtf8("m1")), 123)) + .hasKnownStatus(INVALID_TRANSACTION_BODY) + .designatingPayer(SCHEDULE_PAYER)), getTokenInfo(A_TOKEN).hasTotalSupply(0)); } @HapiTest + @Order(24) final HapiSpec scheduledMintWithInvalidTokenThrowsUnresolvableSigners() { return defaultHapiSpec("ScheduledMintWithInvalidTokenThrowsUnresolvableSigners") .given(overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), cryptoCreate(SCHEDULE_PAYER)) @@ -354,6 +361,7 @@ final HapiSpec scheduledMintWithInvalidTokenThrowsUnresolvableSigners() { } @HapiTest + @Order(35) final HapiSpec scheduledUniqueBurnFailsWithInvalidBatchSize() { return defaultHapiSpec("ScheduledUniqueBurnFailsWithInvalidBatchSize") .given( @@ -387,6 +395,7 @@ final HapiSpec scheduledUniqueBurnFailsWithInvalidBatchSize() { } @HapiTest + @Order(34) final HapiSpec scheduledUniqueBurnExecutesProperly() { return defaultHapiSpec("ScheduledUniqueBurnExecutesProperly") .given( @@ -463,6 +472,7 @@ final HapiSpec scheduledUniqueBurnExecutesProperly() { } @HapiTest + @Order(39) final HapiSpec scheduledUniqueMintFailsWithInvalidMetadata() { return defaultHapiSpec("ScheduledUniqueMintFailsWithInvalidMetadata") .given( @@ -491,6 +501,7 @@ final HapiSpec scheduledUniqueMintFailsWithInvalidMetadata() { } @HapiTest + @Order(36) final HapiSpec scheduledUniqueBurnFailsWithInvalidNftId() { return defaultHapiSpec("ScheduledUniqueBurnFailsWithInvalidNftId") .given( @@ -515,6 +526,7 @@ final HapiSpec scheduledUniqueBurnFailsWithInvalidNftId() { } @HapiTest + @Order(20) final HapiSpec scheduledBurnForUniqueSucceedsWithExistingAmount() { return defaultHapiSpec("scheduledBurnForUniqueSucceedsWithExistingAmount") .given( @@ -542,6 +554,8 @@ final HapiSpec scheduledBurnForUniqueSucceedsWithExistingAmount() { // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(19) final HapiSpec scheduledBurnForUniqueFailsWithInvalidAmount() { return defaultHapiSpec("ScheduledBurnForUniqueFailsWithInvalidAmount") .given( @@ -553,17 +567,21 @@ final HapiSpec scheduledBurnForUniqueFailsWithInvalidAmount() { .supplyKey(SUPPLY_KEY) .treasury(TREASURY) .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) - .initialSupply(0), + .initialSupply(0)) + .when(ifNotHapiTest( scheduleCreate(A_SCHEDULE, burnToken(A_TOKEN, -123L)) .designatingPayer(SCHEDULE_PAYER) - .via(FAILING_TXN)) - .when(scheduleSign(A_SCHEDULE) - .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .hasKnownStatus(SUCCESS)) - .then( + .via(FAILING_TXN), + scheduleSign(A_SCHEDULE) + .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) + .hasKnownStatus(SUCCESS), getTxnRecord(FAILING_TXN) .scheduled() - .hasPriority(recordWith().status(INVALID_TOKEN_BURN_AMOUNT)), + .hasPriority(recordWith().status(INVALID_TOKEN_BURN_AMOUNT)))) + .then( + ifHapiTest(scheduleCreate(A_SCHEDULE, burnToken(A_TOKEN, -123L)) + .designatingPayer(SCHEDULE_PAYER) + .hasKnownStatus(INVALID_TOKEN_BURN_AMOUNT)), getTokenInfo(A_TOKEN).hasTotalSupply(0)); } @@ -578,6 +596,7 @@ private byte[] genRandomBytes(int numBytes) { } @HapiTest + @Order(38) final HapiSpec scheduledUniqueMintFailsWithInvalidBatchSize() { return defaultHapiSpec("ScheduledUniqueMintFailsWithInvalidBatchSize") .given( @@ -617,6 +636,8 @@ final HapiSpec scheduledUniqueMintFailsWithInvalidBatchSize() { // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(22) final HapiSpec scheduledMintFailsWithInvalidAmount() { final var zeroAmountTxn = "zeroAmountTxn"; return defaultHapiSpec("ScheduledMintFailsWithInvalidAmount") @@ -631,21 +652,26 @@ final HapiSpec scheduledMintFailsWithInvalidAmount() { .initialSupply(101), scheduleCreate(A_SCHEDULE, mintToken(A_TOKEN, 0)) .designatingPayer(SCHEDULE_PAYER) - .via(zeroAmountTxn), + .via(zeroAmountTxn)) + .when(ifNotHapiTest( scheduleCreate(A_SCHEDULE, mintToken(A_TOKEN, -1)) .designatingPayer(SCHEDULE_PAYER) - .via(FAILING_TXN)) - .when(scheduleSign(A_SCHEDULE) - .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .hasKnownStatus(SUCCESS)) - .then( + .via(FAILING_TXN), + scheduleSign(A_SCHEDULE) + .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) + .hasKnownStatus(SUCCESS), getTxnRecord(FAILING_TXN) .scheduled() - .hasPriority(recordWith().status(INVALID_TOKEN_MINT_AMOUNT)), + .hasPriority(recordWith().status(INVALID_TOKEN_MINT_AMOUNT)))) + .then( + ifHapiTest(scheduleCreate(A_SCHEDULE, mintToken(A_TOKEN, -1)) + .designatingPayer(SCHEDULE_PAYER) + .hasKnownStatus(INVALID_TOKEN_MINT_AMOUNT)), getTokenInfo(A_TOKEN).hasTotalSupply(101)); } @HapiTest + @Order(37) final HapiSpec scheduledUniqueMintExecutesProperly() { return defaultHapiSpec("ScheduledUniqueMintExecutesProperly") .given( @@ -725,6 +751,7 @@ final HapiSpec scheduledUniqueMintExecutesProperly() { } @HapiTest + @Order(21) final HapiSpec scheduledMintExecutesProperly() { return defaultHapiSpec("ScheduledMintExecutesProperly") .given( @@ -797,6 +824,7 @@ final HapiSpec scheduledMintExecutesProperly() { } @HapiTest + @Order(17) final HapiSpec scheduledBurnExecutesProperly() { return defaultHapiSpec("ScheduledBurnExecutesProperly") .given( @@ -870,6 +898,7 @@ final HapiSpec scheduledBurnExecutesProperly() { } @HapiTest + @Order(40) final HapiSpec scheduledXferFailingWithDeletedAccountPaysServiceFeeButNoImpact() { final String xToken = "XXX"; final String validSchedule = "withLiveAccount"; @@ -921,6 +950,7 @@ final HapiSpec scheduledXferFailingWithDeletedAccountPaysServiceFeeButNoImpact() } @HapiTest + @Order(41) final HapiSpec scheduledXferFailingWithDeletedTokenPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withLiveToken"; @@ -975,6 +1005,7 @@ final HapiSpec scheduledXferFailingWithDeletedTokenPaysServiceFeeButNoImpact() { } @HapiTest + @Order(43) final HapiSpec scheduledXferFailingWithFrozenAccountTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withUnfrozenAccount"; @@ -1031,6 +1062,7 @@ final HapiSpec scheduledXferFailingWithFrozenAccountTransferPaysServiceFeeButNoI } @HapiTest + @Order(44) final HapiSpec scheduledXferFailingWithNonKycedAccountTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withKycedToken"; @@ -1086,6 +1118,7 @@ final HapiSpec scheduledXferFailingWithNonKycedAccountTransferPaysServiceFeeButN } @HapiTest + @Order(47) final HapiSpec scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withAssociatedToken"; @@ -1137,6 +1170,8 @@ final HapiSpec scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFee // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(45) final HapiSpec scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButNoImpact() { String xToken = "XXX"; String validSchedule = "withZeroNetTokenChange"; @@ -1167,26 +1202,36 @@ final HapiSpec scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButN getAccountBalance(xTreasury).hasTokenBalance(xToken, 100), getAccountBalance(xCivilian).hasTokenBalance(xToken, 1), getTxnRecord(successTx).scheduled().logged().revealingDebitsTo(successFeesObs::set), - scheduleCreate( + ifNotHapiTest( + scheduleCreate( + invalidSchedule, + cryptoTransfer(moving(1, xToken).between(xTreasury, xCivilian)) + .breakingNetZeroInvariant()) + .via(failedTx) + .alsoSigningWith(xTreasury, schedulePayer) + .designatingPayer(schedulePayer), + getTxnRecord(failedTx) + .scheduled() + .hasPriority(recordWith().status(TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN)) + .revealingDebitsTo(failureFeesObs::set), + assertionsHold((spec, opLog) -> + assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0)))) + .then( + ifHapiTest(scheduleCreate( invalidSchedule, cryptoTransfer(moving(1, xToken).between(xTreasury, xCivilian)) .breakingNetZeroInvariant()) - .via(failedTx) .alsoSigningWith(xTreasury, schedulePayer) - .designatingPayer(schedulePayer)) - .then( - getTxnRecord(failedTx) - .scheduled() - .hasPriority(recordWith().status(TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN)) - .revealingDebitsTo(failureFeesObs::set), + .designatingPayer(schedulePayer) + .hasKnownStatus(TRANSFERS_NOT_ZERO_SUM_FOR_TOKEN)), getAccountBalance(xTreasury).hasTokenBalance(xToken, 100), - getAccountBalance(xCivilian).hasTokenBalance(xToken, 1), - assertionsHold((spec, opLog) -> - assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0))); + getAccountBalance(xCivilian).hasTokenBalance(xToken, 1)); } // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(46) final HapiSpec scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact() { String xToken = "XXX"; String yToken = "YYY"; @@ -1224,28 +1269,38 @@ final HapiSpec scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact( getAccountBalance(yTreasury).hasTokenBalance(yToken, 100), getAccountBalance(yTreasury).hasTokenBalance(xToken, 1), getTxnRecord(successTx).scheduled().logged().revealingDebitsTo(successFeesObs::set), - scheduleCreate( + ifNotHapiTest( + scheduleCreate( + invalidSchedule, + cryptoTransfer(moving(1, xToken).between(xTreasury, yTreasury)) + .appendingTokenFromTo(xToken, xTreasury, yTreasury, 1)) + .via(failedTx) + .alsoSigningWith(xTreasury, schedulePayer) + .designatingPayer(schedulePayer), + getTxnRecord(failedTx) + .scheduled() + .hasPriority(recordWith().status(TOKEN_ID_REPEATED_IN_TOKEN_LIST)) + .revealingDebitsTo(failureFeesObs::set), + assertionsHold((spec, opLog) -> + assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0)))) + .then( + ifHapiTest(scheduleCreate( invalidSchedule, cryptoTransfer(moving(1, xToken).between(xTreasury, yTreasury)) .appendingTokenFromTo(xToken, xTreasury, yTreasury, 1)) - .via(failedTx) .alsoSigningWith(xTreasury, schedulePayer) - .designatingPayer(schedulePayer)) - .then( - getTxnRecord(failedTx) - .scheduled() - .hasPriority(recordWith().status(TOKEN_ID_REPEATED_IN_TOKEN_LIST)) - .revealingDebitsTo(failureFeesObs::set), + .designatingPayer(schedulePayer) + .hasKnownStatus(TOKEN_ID_REPEATED_IN_TOKEN_LIST)), getAccountBalance(xTreasury).hasTokenBalance(xToken, 100), getAccountBalance(xTreasury).hasTokenBalance(yToken, 1), getAccountBalance(yTreasury).hasTokenBalance(yToken, 100), - getAccountBalance(yTreasury).hasTokenBalance(xToken, 1), - assertionsHold((spec, opLog) -> - assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0))); + getAccountBalance(yTreasury).hasTokenBalance(xToken, 1)); } // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(42) final HapiSpec scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServiceFeeButNoImpact() { String xToken = "XXX"; String yToken = "YYY"; @@ -1285,27 +1340,37 @@ final HapiSpec scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServi getAccountBalance(yTreasury).hasTokenBalance(yToken, 100), getAccountBalance(yTreasury).hasTokenBalance(xToken, 1), getTxnRecord(successTx).scheduled().logged().revealingDebitsTo(successFeesObs::set), - scheduleCreate( + ifNotHapiTest( + scheduleCreate( + invalidSchedule, + cryptoTransfer(moving(2, xToken) + .distributing(xTreasury, yTreasury, xyCivilian)) + .withEmptyTokenTransfers(yToken)) + .via(failedTx) + .alsoSigningWith(xTreasury, yTreasury, schedulePayer) + .designatingPayer(schedulePayer), + getTxnRecord(failedTx) + .scheduled() + .hasPriority(recordWith().status(EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS)) + .revealingDebitsTo(failureFeesObs::set), + assertionsHold((spec, opLog) -> + assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0)))) + .then( + ifHapiTest(scheduleCreate( invalidSchedule, cryptoTransfer(moving(2, xToken).distributing(xTreasury, yTreasury, xyCivilian)) .withEmptyTokenTransfers(yToken)) - .via(failedTx) .alsoSigningWith(xTreasury, yTreasury, schedulePayer) - .designatingPayer(schedulePayer)) - .then( - getTxnRecord(failedTx) - .scheduled() - .hasPriority(recordWith().status(EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS)) - .revealingDebitsTo(failureFeesObs::set), + .designatingPayer(schedulePayer) + .hasKnownStatus(EMPTY_TOKEN_TRANSFER_ACCOUNT_AMOUNTS)), getAccountBalance(xTreasury).hasTokenBalance(xToken, 100), getAccountBalance(xTreasury).hasTokenBalance(yToken, 1), getAccountBalance(yTreasury).hasTokenBalance(yToken, 100), - getAccountBalance(yTreasury).hasTokenBalance(xToken, 1), - assertionsHold((spec, opLog) -> - assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0))); + getAccountBalance(yTreasury).hasTokenBalance(xToken, 1)); } @HapiTest + @Order(29) final HapiSpec scheduledSubmitFailedWithMsgSizeTooLargeStillPaysServiceFeeButHasNoImpact() { String immutableTopic = "XXX"; String validSchedule = "withValidSize"; @@ -1348,6 +1413,7 @@ final HapiSpec scheduledSubmitFailedWithMsgSizeTooLargeStillPaysServiceFeeButHas } @HapiTest + @Order(28) final HapiSpec scheduledSubmitFailedWithInvalidChunkTxnIdStillPaysServiceFeeButHasNoImpact() { String immutableTopic = "XXX"; String validSchedule = "withValidChunkTxnId"; @@ -1401,6 +1467,7 @@ final HapiSpec scheduledSubmitFailedWithInvalidChunkTxnIdStillPaysServiceFeeButH } @HapiTest + @Order(27) final HapiSpec scheduledSubmitFailedWithInvalidChunkNumberStillPaysServiceFeeButHasNoImpact() { String immutableTopic = "XXX"; String validSchedule = "withValidChunkNumber"; @@ -1449,6 +1516,7 @@ final HapiSpec scheduledSubmitFailedWithInvalidChunkNumberStillPaysServiceFeeBut } @HapiTest + @Order(30) final HapiSpec scheduledSubmitThatWouldFailWithInvalidTopicIdCannotBeScheduled() { String civilianPayer = PAYER; AtomicReference> successFeesObs = new AtomicReference<>(); @@ -1488,6 +1556,8 @@ private void assertBasicallyIdentical( // @todo('9974') Need to work out why this succeeds instead // of failing with UNRESOLVABLE_REQUIRED_SIGNERS + @HapiTest + @Order(31) final HapiSpec scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned() { String adminKey = ADMIN; String mutableTopic = "XXX"; @@ -1514,6 +1584,7 @@ final HapiSpec scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned() { } @HapiTest + @Order(2) final HapiSpec executionTriggersOnceTopicHasSatisfiedSubmitKey() { String adminKey = ADMIN; String submitKey = "submit"; @@ -1552,6 +1623,7 @@ final HapiSpec executionTriggersOnceTopicHasSatisfiedSubmitKey() { } @HapiTest + @Order(3) final HapiSpec executionTriggersWithWeirdlyRepeatedKey() { String schedule = "dupKeyXfer"; @@ -1586,6 +1658,8 @@ final HapiSpec executionTriggersWithWeirdlyRepeatedKey() { } // @todo('9976') Need to work out why this does not produce the expected transfer list + @HapiTest + @Order(14) final HapiSpec executionWithDefaultPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithDefaultPayerWorks") @@ -1600,7 +1674,7 @@ final HapiSpec executionWithDefaultPayerWorks() { .when(scheduleSign(BASIC_XFER).alsoSigningWith(SENDER).via(SIGN_TXN)) .then(withOpContext((spec, opLog) -> { var createTx = getTxnRecord(CREATE_TXN); - var signTx = getTxnRecord(SIGN_TXN); + var signTx = getTxnRecord(SIGN_TXN).logged(); var triggeredTx = getTxnRecord(CREATE_TXN).scheduled(); allRunFor(spec, createTx, signTx, triggeredTx); @@ -1643,7 +1717,9 @@ final HapiSpec executionWithDefaultPayerWorks() { })); } - // @todo('9977') Need to figure out why the ending balance does not match expected + // @todo('9977') Need to figure out why the ending balance does not match ex + @HapiTest + @Order(13) final HapiSpec executionWithDefaultPayerButNoFundsFails() { long balance = 10_000_000L; long noBalance = 0L; @@ -1682,6 +1758,7 @@ final HapiSpec executionWithDefaultPayerButNoFundsFails() { } @HapiTest + @Order(11) final HapiSpec executionWithCustomPayerWorksWithLastSigBeingCustomPayer() { long noBalance = 0L; long transferAmount = 1; @@ -1719,7 +1796,8 @@ final HapiSpec executionWithCustomPayerWorksWithLastSigBeingCustomPayer() { getAccountBalance(RECEIVER).hasTinyBars(transferAmount)); } - // @todo('9977') Need to figure out why the ending balance does not match expected + @HapiTest + @Order(8) final HapiSpec executionWithCustomPayerButNoFundsFails() { long balance = 0L; long noBalance = 0L; @@ -1752,7 +1830,8 @@ final HapiSpec executionWithCustomPayerButNoFundsFails() { })); } - // @todo('9977') Need to figure out why the ending balance does not match expected + @HapiTest + @Order(12) final HapiSpec executionWithDefaultPayerButAccountDeletedFails() { long balance = 10_000_000L; long noBalance = 0L; @@ -1775,10 +1854,13 @@ final HapiSpec executionWithDefaultPayerButAccountDeletedFails() { getScheduleInfo(BASIC_XFER).isExecuted(), getTxnRecord(CREATE_TXN) .scheduled() - .hasPriority(recordWith().status(INSUFFICIENT_PAYER_BALANCE))); + .hasPriority( + recordWith().statusFrom(INSUFFICIENT_PAYER_BALANCE, PAYER_ACCOUNT_DELETED))); } // @todo('9977') Need to figure out why the ending balance does not match expected + @Order(7) + @HapiTest final HapiSpec executionWithCustomPayerButAccountDeletedFails() { long balance = 10_000_000L; long noBalance = 0L; @@ -1808,14 +1890,18 @@ final HapiSpec executionWithCustomPayerButAccountDeletedFails() { allRunFor(spec, triggeredTx); - Assertions.assertEquals( - INSUFFICIENT_PAYER_BALANCE, - triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); + final var failureReasons = EnumSet.of(INSUFFICIENT_PAYER_BALANCE, PAYER_ACCOUNT_DELETED); + Assertions.assertTrue( + failureReasons.contains(triggeredTx + .getResponseRecord() + .getReceipt() + .getStatus()), + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED + " for one of reasons " + failureReasons); })); } @HapiTest + @Order(4) final HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { long noBalance = 0L; long senderBalance = 100L; @@ -1850,6 +1936,7 @@ final HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { } @HapiTest + @Order(5) final HapiSpec executionWithCryptoSenderDeletedFails() { long noBalance = 0L; long senderBalance = 100L; @@ -1886,6 +1973,7 @@ final HapiSpec executionWithCryptoSenderDeletedFails() { } @HapiTest + @Order(16) final HapiSpec executionWithTokenInsufficientAccountBalanceFails() { String xToken = "XXX"; String invalidSchedule = "withInsufficientTokenTransfer"; @@ -1921,6 +2009,8 @@ final HapiSpec executionWithTokenInsufficientAccountBalanceFails() { // This should not be run for modular service due to key gathering behavior differences. // c.f. Issue #9970 for explanation + @HapiTest + @Order(15) final HapiSpec executionWithInvalidAccountAmountsFails() { long transferAmount = 100; long senderBalance = 1000L; @@ -1931,20 +2021,18 @@ final HapiSpec executionWithInvalidAccountAmountsFails() { overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), cryptoCreate(PAYING_ACCOUNT).balance(payingAccountBalance), cryptoCreate(SENDER).balance(senderBalance), - cryptoCreate(RECEIVER).balance(noBalance), + cryptoCreate(RECEIVER).balance(noBalance)) + .when(ifNotHapiTest( scheduleCreate( FAILED_XFER, cryptoTransfer( tinyBarsFromToWithInvalidAmounts(SENDER, RECEIVER, transferAmount))) .designatingPayer(PAYING_ACCOUNT) - .via(CREATE_TXN)) - .when(scheduleSign(FAILED_XFER) - .alsoSigningWith(SENDER, PAYING_ACCOUNT) - .via(SIGN_TXN) - .hasKnownStatus(SUCCESS)) - .then( - getAccountBalance(SENDER).hasTinyBars(senderBalance), - getAccountBalance(RECEIVER).hasTinyBars(noBalance), + .via(CREATE_TXN), + scheduleSign(FAILED_XFER) + .alsoSigningWith(SENDER, PAYING_ACCOUNT) + .via(SIGN_TXN) + .hasKnownStatus(SUCCESS), withOpContext((spec, opLog) -> { var triggeredTx = getTxnRecord(CREATE_TXN).scheduled(); @@ -1954,10 +2042,20 @@ final HapiSpec executionWithInvalidAccountAmountsFails() { INVALID_ACCOUNT_AMOUNTS, triggeredTx.getResponseRecord().getReceipt().getStatus(), SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); - })); + }))) + .then( + ifHapiTest(scheduleCreate( + FAILED_XFER, + cryptoTransfer( + tinyBarsFromToWithInvalidAmounts(SENDER, RECEIVER, transferAmount))) + .designatingPayer(PAYING_ACCOUNT) + .hasKnownStatus(INVALID_ACCOUNT_AMOUNTS)), + getAccountBalance(SENDER).hasTinyBars(senderBalance), + getAccountBalance(RECEIVER).hasTinyBars(noBalance)); } - // @todo('9976') Need to work out why this does not produce the expected transfer list + @HapiTest + @Order(10) final HapiSpec executionWithCustomPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerWorks") @@ -2024,6 +2122,8 @@ final HapiSpec executionWithCustomPayerWorks() { } // @todo('9976') Need to work out why this does not produce the expected transfer list + @HapiTest + @Order(6) final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerAndAdminKeyWorks") @@ -2092,6 +2192,8 @@ final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { } // @todo('9976') Need to work out why this does not produce the expected transfer list + @HapiTest + @Order(9) final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { long transferAmount = 1; return defaultHapiSpec("ExecutionWithCustomPayerWhoSignsAtCreationAsPayerWorks") @@ -2175,16 +2277,19 @@ public static boolean transferListCheck( .build(); var accountAmountList = triggered.getResponseRecord().getTransferList().getAccountAmountsList(); - + System.out.println("accountAmountList: " + accountAmountList); boolean payerHasPaid = accountAmountList.stream().anyMatch(a -> a.getAccountID().equals(payingAccountID) && a.getAmount() < 0); + System.out.println("payerHasPaid: " + payerHasPaid); boolean amountHasBeenTransferred = accountAmountList.contains(givingAmount) && accountAmountList.contains(receivingAmount); + System.out.println("amountHasBeenTransferred: " + amountHasBeenTransferred); return amountHasBeenTransferred && payerHasPaid; } // Currently this cannot be run as HapiTest because it stops the captive nodes. + @BddMethodIsNotATest final HapiSpec scheduledFreezeWorksAsExpected() { final byte[] poeticUpgradeHash = ScheduleUtils.getPoeticUpgradeHash(); @@ -2227,6 +2332,7 @@ final HapiSpec scheduledFreezeWorksAsExpected() { } // Currently this cannot be run as HapiTest because it stops the captive nodes. + @BddMethodIsNotATest final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabled) { final byte[] poeticUpgradeHash = ScheduleUtils.getPoeticUpgradeHash(); @@ -2298,6 +2404,8 @@ final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabl } // @todo('9973') Need to work out why this does not actually execute + @HapiTest + @Order(26) final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { return defaultHapiSpec("ScheduledPermissionedFileUpdateWorksAsExpected") .given( @@ -2330,6 +2438,8 @@ final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { } // @todo('9973') Work out permissioned file update issues + @HapiTest + @Order(25) final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { return defaultHapiSpec("ScheduledPermissionedFileUpdateUnauthorizedPayerFails") @@ -2363,7 +2473,8 @@ final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { })); } - // @todo('9973') Work out permissioned file update issues + @HapiTest + @Order(33) final HapiSpec scheduledSystemDeleteWorksAsExpected() { return defaultHapiSpec("ScheduledSystemDeleteWorksAsExpected") @@ -2397,8 +2508,42 @@ final HapiSpec scheduledSystemDeleteWorksAsExpected() { } @HapiTest - final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEnabled) { + @Order(32) + final HapiSpec hapiTestScheduledSystemDeleteUnauthorizedPayerFails() { + return defaultHapiSpec("ScheduledSystemDeleteUnauthorizedPayerFails") + .given( + overriding(SCHEDULING_WHITELIST, WHITELIST_MINIMUM), + cryptoCreate(PAYING_ACCOUNT), + cryptoCreate(PAYING_ACCOUNT_2), + fileCreate("misc").lifetime(THREE_MONTHS_IN_SECONDS).contents(ORIG_FILE), + overriding(SCHEDULING_WHITELIST, "SystemDelete"), + scheduleCreate(A_SCHEDULE, systemFileDelete("misc").updatingExpiry(1L)) + .withEntityMemo(randomUppercase(100)) + .designatingPayer(PAYING_ACCOUNT_2) + .payingWith(PAYING_ACCOUNT) + .via(successTxn)) + .when(scheduleSign(A_SCHEDULE) + .alsoSigningWith(PAYING_ACCOUNT_2) + .payingWith(PAYING_ACCOUNT) + .via(signTxn) + .hasKnownStatus(SUCCESS)) + .then( + overriding(SCHEDULING_WHITELIST, defaultWhitelist), + getScheduleInfo(A_SCHEDULE).isExecuted(), + getFileInfo("misc").nodePayment(1_234L), + withOpContext((spec, opLog) -> { + var triggeredTx = getTxnRecord(successTxn).scheduled(); + allRunFor(spec, triggeredTx); + Assertions.assertEquals( + NOT_SUPPORTED, + triggeredTx.getResponseRecord().getReceipt().getStatus(), + "Scheduled transaction be NOT_SUPPORTED!"); + })); + } + + @BddMethodIsNotATest + final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEnabled) { if (isLongTermEnabled) { return defaultHapiSpec("ScheduledSystemDeleteUnauthorizedPayerFails") @@ -2558,8 +2703,28 @@ final HapiSpec congestionPricingAffectsImmediateScheduleExecution() { })); } + @HapiTest + @Order(48) + final HapiSpec suiteCleanup() { + return defaultHapiSpec("suiteCleanup") + .given() + .when() + .then(fileUpdate(APP_PROPERTIES) + .payingWith(ADDRESS_BOOK_CONTROL) + .overridingProps(Map.of(SCHEDULING_WHITELIST, defaultWhitelist))); + } + private T getTestConfig(Class configClass) { final TestConfigBuilder builder = new TestConfigBuilder(configClass); return builder.getOrCreateConfig().getConfigData(configClass); } + + public static HapiSpecOperation addAllToWhitelist() { + final List whitelistNames = new LinkedList<>(); + for (final HederaFunctionality enumValue : HederaFunctionality.values()) { + whitelistNames.add(enumValue.protoName()); + } + final String fullWhitelist = String.join(",", whitelistNames); + return overriding(SCHEDULING_WHITELIST, fullWhitelist); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java index 8954c31606db..5671d5defbc8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java @@ -17,6 +17,7 @@ package com.hedera.services.bdd.suites.schedule; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.onlyDefaultHapiSpec; import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; import static com.hedera.services.bdd.spec.assertions.TransferListAsserts.exactParticipants; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; @@ -39,6 +40,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadDefaultFeeSchedules; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; @@ -102,10 +104,12 @@ public List getSpecsInSuite() { schedulingTxnIdFieldsNotAllowed())); } + @HapiTest HapiSpec canonicalScheduleOpsHaveExpectedUsdFees() { - return defaultHapiSpec("CanonicalScheduleOpsHaveExpectedUsdFees") + return onlyDefaultHapiSpec("CanonicalScheduleOpsHaveExpectedUsdFees") .given( overriding(SCHEDULING_WHITELIST, "CryptoTransfer,ContractCall"), + uploadDefaultFeeSchedules(GENESIS), uploadInitCode(SIMPLE_UPDATE), cryptoCreate(OTHER_PAYER), cryptoCreate(PAYING_SENDER), @@ -156,6 +160,7 @@ HapiSpec canonicalScheduleOpsHaveExpectedUsdFees() { validateChargedUsdWithin("canonicalContractCall", 0.1, 3.0)); } + @HapiTest public HapiSpec noFeesChargedIfTriggeredPayerIsUnwilling() { return defaultHapiSpec("NoFeesChargedIfTriggeredPayerIsUnwilling") .given(cryptoCreate(UNWILLING_PAYER)) @@ -176,6 +181,7 @@ public HapiSpec noFeesChargedIfTriggeredPayerIsUnwilling() { .status(INSUFFICIENT_TX_FEE))); } + @HapiTest public HapiSpec noFeesChargedIfTriggeredPayerIsInsolvent() { return defaultHapiSpec("NoFeesChargedIfTriggeredPayerIsInsolvent") .given(cryptoCreate(INSOLVENT_PAYER).balance(0L)) @@ -193,6 +199,7 @@ public HapiSpec noFeesChargedIfTriggeredPayerIsInsolvent() { .status(INSUFFICIENT_PAYER_BALANCE))); } + @HapiTest public HapiSpec canScheduleChunkedMessages() { String ofGeneralInterest = "Scotch"; AtomicReference initialTxnId = new AtomicReference<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java index 104bb82279fa..e7302580c0f4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java @@ -68,8 +68,12 @@ import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestMethodOrder; @HapiTestSuite +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ScheduleSignSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleSignSpecs.class); private static final int SCHEDULE_EXPIRY_TIME_SECS = 10; @@ -125,6 +129,8 @@ public List getSpecsInSuite() { suiteCleanup())); } + @HapiTest + @Order(24) private HapiSpec suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() @@ -136,6 +142,8 @@ private HapiSpec suiteCleanup() { SCHEDULING_WHITELIST, defaultWhitelist))); } + @HapiTest + @Order(1) private HapiSpec suiteSetup() { return defaultHapiSpec("suiteSetup") .given() @@ -146,6 +154,7 @@ private HapiSpec suiteSetup() { } @HapiTest + @Order(21) final HapiSpec signingDeletedSchedulesHasNoEffect() { String sender = "X"; String receiver = "Y"; @@ -169,6 +178,7 @@ final HapiSpec signingDeletedSchedulesHasNoEffect() { } @HapiTest + @Order(5) final HapiSpec changeInNestedSigningReqsRespected() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(1, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); @@ -221,6 +231,7 @@ private Key bumpThirdNestedThresholdSigningReq(Key source) { } @HapiTest + @Order(12) final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(2, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); @@ -259,6 +270,8 @@ final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { getAccountBalance(receiver).hasTinyBars(1L)); } + @HapiTest + @Order(13) final HapiSpec reductionInSigningReqsAllowsTxnToGoThroughWithRandomKey() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(2, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); @@ -311,6 +324,7 @@ private Key lowerThirdNestedThresholdSigningReq(Key source) { } @HapiTest + @Order(6) final HapiSpec nestedSigningReqsWorkAsExpected() { var senderShape = threshOf(2, threshOf(1, 3), threshOf(1, 3), threshOf(1, 3)); var sigOne = senderShape.signedWith(sigs(sigs(OFF, OFF, ON), sigs(OFF, OFF, OFF), sigs(OFF, OFF, OFF))); @@ -343,6 +357,8 @@ final HapiSpec nestedSigningReqsWorkAsExpected() { getAccountBalance(receiver).hasTinyBars(1L)); } + @HapiTest + @Order(10) final HapiSpec receiverSigRequiredNotConfusedByOrder() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); @@ -380,6 +396,7 @@ final HapiSpec receiverSigRequiredNotConfusedByOrder() { } @HapiTest + @Order(9) final HapiSpec receiverSigRequiredNotConfusedByMultiSigSender() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); @@ -417,6 +434,7 @@ final HapiSpec receiverSigRequiredNotConfusedByMultiSigSender() { } @HapiTest + @Order(11) final HapiSpec receiverSigRequiredUpdateIsRecognized() { var senderShape = threshOf(2, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); @@ -458,6 +476,7 @@ final HapiSpec receiverSigRequiredUpdateIsRecognized() { } @HapiTest + @Order(16) final HapiSpec scheduleAlreadyExecutedOnCreateDoesntRepeatTransaction() { var senderShape = threshOf(1, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); @@ -494,6 +513,7 @@ final HapiSpec scheduleAlreadyExecutedOnCreateDoesntRepeatTransaction() { } @HapiTest + @Order(15) final HapiSpec scheduleAlreadyExecutedDoesntRepeatTransaction() { var senderShape = threshOf(2, 3); var sigOne = senderShape.signedWith(sigs(ON, OFF, OFF)); @@ -528,6 +548,7 @@ final HapiSpec scheduleAlreadyExecutedDoesntRepeatTransaction() { } @HapiTest + @Order(4) final HapiSpec basicSignatureCollectionWorks() { var txnBody = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); @@ -542,6 +563,7 @@ final HapiSpec basicSignatureCollectionWorks() { } @HapiTest + @Order(19) final HapiSpec signalsIrrelevantSig() { var txnBody = cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1)); @@ -559,6 +581,7 @@ final HapiSpec signalsIrrelevantSig() { } @HapiTest + @Order(20) final HapiSpec signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { var txnBody = mintToken(TOKEN_A, 50000000L); @@ -586,6 +609,7 @@ final HapiSpec signalsIrrelevantSigEvenAfterLinkedEntityUpdate() { } @HapiTest + @Order(3) final HapiSpec addingSignaturesToNonExistingTxFails() { return defaultHapiSpec("AddingSignaturesToNonExistingTxFails") .given(cryptoCreate(SENDER), newKeyNamed(SOMEBODY)) @@ -597,6 +621,8 @@ final HapiSpec addingSignaturesToNonExistingTxFails() { .hasKnownStatus(INVALID_SCHEDULE_ID)); } + @HapiTest + @Order(2) final HapiSpec addingSignaturesToExecutedTxFails() { var txnBody = cryptoCreate(SOMEBODY); var creation = "basicCryptoCreate"; @@ -610,6 +636,8 @@ final HapiSpec addingSignaturesToExecutedTxFails() { .hasKnownStatus(SCHEDULE_ALREADY_EXECUTED)); } + @HapiTest + @Order(23) public HapiSpec triggersUponFinishingPayerSig() { return defaultHapiSpec("TriggersUponFinishingPayerSig") .given( @@ -629,6 +657,8 @@ public HapiSpec triggersUponFinishingPayerSig() { .then(getAccountBalance(RECEIVER).hasTinyBars(1L)); } + @HapiTest + @Order(22) public HapiSpec triggersUponAdditionalNeededSig() { return defaultHapiSpec("TriggersUponAdditionalNeededSig") .given( @@ -646,6 +676,8 @@ public HapiSpec triggersUponAdditionalNeededSig() { .then(getAccountBalance(RECEIVER).hasTinyBars(1L)); } + @HapiTest + @Order(17) public HapiSpec sharedKeyWorksAsExpected() { return defaultHapiSpec("RequiresSharedKeyToSignBothSchedulingAndScheduledTxns") .given( @@ -666,6 +698,7 @@ public HapiSpec sharedKeyWorksAsExpected() { } @HapiTest + @Order(7) public HapiSpec okIfAdminKeyOverlapsWithActiveScheduleKey() { var keyGen = OverlappingKeyGenerator.withAtLeastOneOverlappingByte(2); var adminKey = "adminKey"; @@ -682,6 +715,8 @@ public HapiSpec okIfAdminKeyOverlapsWithActiveScheduleKey() { .then(); } + @HapiTest + @Order(8) public HapiSpec overlappingKeysTreatedAsExpected() { var keyGen = OverlappingKeyGenerator.withAtLeastOneOverlappingByte(2); @@ -720,6 +755,8 @@ public HapiSpec overlappingKeysTreatedAsExpected() { getAccountBalance(ADDRESS_BOOK_CONTROL).hasTinyBars(changeFromSnapshot("before", +2))); } + @HapiTest + @Order(14) public HapiSpec retestsActivationOnSignWithEmptySigMap() { return defaultHapiSpec("RetestsActivationOnCreateWithEmptySigMap") .given(newKeyNamed("a"), newKeyNamed("b"), newKeyListNamed("ab", List.of("a", "b")), newKeyNamed(ADMIN)) @@ -737,6 +774,8 @@ public HapiSpec retestsActivationOnSignWithEmptySigMap() { getAccountBalance(SENDER).hasTinyBars(664L)); } + @HapiTest + @Order(18) public HapiSpec signFailsDueToDeletedExpiration() { final int FAST_EXPIRATION = 0; return defaultHapiSpec("SignFailsDueToDeletedExpiration") @@ -752,6 +791,7 @@ public HapiSpec signFailsDueToDeletedExpiration() { .alsoSigningWith(SENDER), getAccountBalance(RECEIVER).hasTinyBars(0L)) .then( + sleepFor(1000), scheduleSign(TWO_SIG_XFER) .alsoSigningWith(RECEIVER) .hasPrecheckFrom(OK, INVALID_SCHEDULE_ID) @@ -759,10 +799,12 @@ public HapiSpec signFailsDueToDeletedExpiration() { sleepFor(2000), scheduleSign(TWO_SIG_XFER) .alsoSigningWith(RECEIVER) + .fee(ONE_HUNDRED_HBARS) .hasPrecheckFrom(OK, INVALID_SCHEDULE_ID) .hasKnownStatusFrom(INVALID_SCHEDULE_ID, SCHEDULE_PENDING_EXPIRATION), scheduleSign(TWO_SIG_XFER) .alsoSigningWith(RECEIVER) + .fee(ONE_HUNDRED_HBARS) .hasPrecheckFrom(OK, INVALID_SCHEDULE_ID) .hasKnownStatusFrom(INVALID_SCHEDULE_ID), getScheduleInfo(TWO_SIG_XFER).hasCostAnswerPrecheck(INVALID_SCHEDULE_ID), From 6af2abe520d0c079df53c71202e623ada0b0ad40 Mon Sep 17 00:00:00 2001 From: JivkoKelchev Date: Tue, 2 Jan 2024 18:05:20 +0200 Subject: [PATCH 66/80] fix: implement sidecars (#9815) Signed-off-by: Zhivko Kelchev Signed-off-by: Michael Tinker Co-authored-by: Michael Tinker --- .../node/app/fees/ChildFeeContextImpl.java | 4 +- .../com/hedera/node/app/fees/FeeManager.java | 10 +- .../congestion/CongestionMultipliers.java | 13 +- .../EntityUtilizationMultiplier.java | 15 +- .../workflows/handle/HandleContextImpl.java | 6 +- .../SingleTransactionRecordBuilderImpl.java | 6 +- .../congestion/CongestionMultipliersTest.java | 4 +- .../src/xtest/java/common/AbstractXTest.java | 12 +- .../java/contract/AbstractContractXTest.java | 6 +- .../contract/impl/exec/CallOutcome.java | 34 +++- .../impl/exec/ContextQueryProcessor.java | 6 +- .../exec/ContextTransactionProcessor.java | 28 ++-- .../contract/impl/exec/EvmActionTracer.java | 10 +- .../contract/impl/exec/FrameRunner.java | 6 +- .../contract/impl/exec/QueryModule.java | 6 +- .../contract/impl/exec/TransactionModule.java | 11 +- .../exec/gas/SystemContractGasCalculator.java | 19 ++- .../AbstractCustomCreateOperation.java | 2 +- .../operations/CustomCreate2Operation.java | 8 +- .../CustomContractCreationProcessor.java | 46 ++++- .../CustomMessageCallProcessor.java | 16 +- .../exec/scope/HandleHederaOperations.java | 58 ++++--- .../contract/impl/exec/utils/ActionStack.java | 46 ++++- .../impl/exec/utils/FrameBuilder.java | 13 +- .../contract/impl/exec/utils/FrameUtils.java | 35 +++- .../exec/utils/PendingCreationMetadata.java | 23 +++ .../utils/PendingCreationMetadataRef.java | 66 ++++++++ ...nce.java => PropagatedCallFailureRef.java} | 4 +- .../impl/handlers/ContractCallHandler.java | 4 +- .../impl/handlers/ContractCreateHandler.java | 4 +- .../handlers/EthereumTransactionHandler.java | 13 +- .../impl/hevm/ActionSidecarContentTracer.java | 10 +- .../contract/impl/hevm/HederaEvmContext.java | 34 +++- .../impl/hevm/HederaEvmTransaction.java | 6 +- .../impl/hevm/HederaEvmTransactionResult.java | 54 ++++-- .../impl/hevm/HederaWorldUpdater.java | 9 +- .../records/ContractCallRecordBuilder.java | 4 +- .../records/ContractCreateRecordBuilder.java | 4 +- .../ContractOperationRecordBuilder.java | 91 ++++++++++ .../EthereumTransactionRecordBuilder.java | 4 +- .../impl/records/GasFeeRecordBuilder.java | 44 ----- .../impl/state/DispatchingEvmFrameState.java | 4 +- .../contract/impl/state/ProxyEvmAccount.java | 14 +- .../impl/state/ProxyWorldUpdater.java | 21 ++- .../contract/impl/utils/ConversionUtils.java | 11 +- .../contract/impl/test/TestHelpers.java | 39 ++++- .../impl/test/exec/CallOutcomeTest.java | 12 +- .../test/exec/ContextQueryProcessorTest.java | 6 +- .../exec/ContextTransactionProcessorTest.java | 10 +- .../impl/test/exec/EvmActionTracerTest.java | 6 +- .../impl/test/exec/FrameRunnerTest.java | 6 +- .../impl/test/exec/TransactionModuleTest.java | 17 +- .../test/exec/gas/CustomGasChargingTest.java | 5 +- .../operations/CreateOperationTestBase.java | 6 +- .../CustomCreate2OperationTest.java | 3 +- .../CustomContractCreationProcessorTest.java | 6 +- .../CustomMessageCallProcessorTest.java | 8 +- .../HandleHederaNativeOperationsTest.java | 6 +- .../scope/HandleHederaOperationsTest.java | 37 ++-- .../impl/test/exec/utils/ActionStackTest.java | 11 +- .../test/exec/utils/FrameBuilderTest.java | 6 +- .../impl/test/exec/utils/FrameUtilsTest.java | 7 +- ...ingCreationRecordBuilderReferenceTest.java | 21 +++ .../handlers/ContractCallHandlerTest.java | 17 +- .../ContractCallLocalHandlerTest.java | 6 +- .../handlers/ContractCreateHandlerTest.java | 14 +- .../EthereumTransactionHandlerTest.java | 22 ++- .../hevm/HederaEvmTransactionResultTest.java | 84 ++++++---- .../test/hevm/HederaWorldUpdaterTest.java | 6 +- .../ContractOperationRecordBuilderTest.java | 131 +++++++++++++++ .../test/records/GasFeeRecordBuilderTest.java | 45 ----- .../impl/test/utils/ConversionUtilsTest.java | 16 +- .../BroadcastingRecordStreamListener.java | 58 +++---- .../EventualRecordStreamAssertion.java | 22 ++- .../traceability/SidecarWatcher.java | 84 +++++++--- .../opcodes/Create2OperationSuite.java | 17 +- .../traceability/TraceabilitySuite.java | 158 ++++++++++-------- .../bdd/suites/leaky/FeatureFlagSuite.java | 2 +- .../src/main/resource/spec-default.properties | 2 +- .../config/impl/internal/ConfigListUtils.java | 4 +- 80 files changed, 1224 insertions(+), 520 deletions(-) create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadata.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadataRef.java rename hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/{PropagatedCallFailureReference.java => PropagatedCallFailureRef.java} (95%) create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractOperationRecordBuilder.java delete mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/GasFeeRecordBuilder.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/PendingCreationRecordBuilderReferenceTest.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java delete mode 100644 hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/GasFeeRecordBuilderTest.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java index 746e5e49dffd..efdf7583f8d0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/ChildFeeContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ public ChildFeeContextImpl( @NonNull final FeeManager feeManager, @NonNull final HandleContextImpl context, @NonNull final TransactionBody body, - AccountID payerId) { + @NonNull final AccountID payerId) { this.feeManager = Objects.requireNonNull(feeManager); this.context = Objects.requireNonNull(context); this.body = Objects.requireNonNull(body); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java index 23db39d639a4..79f30c586974 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -193,6 +193,14 @@ public FeeCalculator createFeeCalculator( storeFactory); } + public long congestionMultiplierFor( + @NonNull final TransactionBody body, + @NonNull final HederaFunctionality functionality, + @NonNull final ReadableStoreFactory storeFactory) { + + return congestionMultipliers.maxCurrentMultiplier(body, functionality, storeFactory); + } + @NonNull public FeeCalculator createFeeCalculator( @NonNull final HederaFunctionality functionality, diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/CongestionMultipliers.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/CongestionMultipliers.java index c641a92b1478..4c8a56c1501f 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/CongestionMultipliers.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/CongestionMultipliers.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import edu.umd.cs.findbugs.annotations.NonNull; @@ -59,9 +61,16 @@ public void updateMultiplier(@NonNull final Instant consensusTime) { */ public long maxCurrentMultiplier( @NonNull final TransactionInfo txnInfo, @NonNull final ReadableStoreFactory storeFactory) { + return maxCurrentMultiplier(txnInfo.txBody(), txnInfo.functionality(), storeFactory); + } + + public long maxCurrentMultiplier( + @NonNull final TransactionBody body, + @NonNull final HederaFunctionality functionality, + @NonNull final ReadableStoreFactory storeFactory) { return Math.max( throttleMultiplier.currentMultiplier(), - entityUtilizationMultiplier.currentMultiplier(txnInfo, storeFactory)); + entityUtilizationMultiplier.currentMultiplier(body, functionality, storeFactory)); } /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/EntityUtilizationMultiplier.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/EntityUtilizationMultiplier.java index c4f80ef2fe16..d553b9765ebb 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/EntityUtilizationMultiplier.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/congestion/EntityUtilizationMultiplier.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import static com.hedera.node.app.service.mono.context.properties.EntityType.TOPIC; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.consensus.ReadableTopicStore; import com.hedera.node.app.service.contract.impl.state.ContractStateStore; import com.hedera.node.app.service.file.ReadableFileStore; @@ -66,12 +68,19 @@ public EntityUtilizationMultiplier( */ public long currentMultiplier( @NonNull final TransactionInfo txnInfo, @NonNull final ReadableStoreFactory storeFactory) { + return currentMultiplier(txnInfo.txBody(), txnInfo.functionality(), storeFactory); + } + + public long currentMultiplier( + @NonNull final TransactionBody body, + @NonNull final HederaFunctionality functionality, + @NonNull final ReadableStoreFactory storeFactory) { final var throttleMultiplier = delegate.currentMultiplier(); final var configuration = configProvider.getConfiguration(); final var entityScaleFactors = configuration.getConfigData(FeesConfig.class).percentUtilizationScaleFactors(); - return switch (txnInfo.functionality()) { + return switch (functionality) { case CRYPTO_CREATE -> entityScaleFactors .scaleForNew(ACCOUNT, roundedAccountPercentUtil(storeFactory)) .scaling((int) throttleMultiplier); @@ -83,7 +92,7 @@ public long currentMultiplier( .scaling((int) throttleMultiplier); case TOKEN_MINT -> { final var mintsWithMetadata = - !txnInfo.txBody().tokenMint().metadata().isEmpty(); + !body.tokenMintOrThrow().metadata().isEmpty(); yield mintsWithMetadata ? entityScaleFactors .scaleForNew(NFT, roundedNftPercentUtil(storeFactory)) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java index 427773a55905..a59de3144778 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -272,9 +272,9 @@ public FeeCalculator feeCalculator(@NonNull final SubType subType) { @Override public FunctionalityResourcePrices resourcePricesFor( @NonNull final HederaFunctionality functionality, @NonNull final SubType subType) { - // TODO - how do we get the active congestion multiplier? return new FunctionalityResourcePrices( - requireNonNull(feeManager.getFeeData(functionality, userTransactionConsensusTime, subType)), 1L); + requireNonNull(feeManager.getFeeData(functionality, userTransactionConsensusTime, subType)), + feeManager.congestionMultiplierFor(txBody, functionality, readableStoreFactory)); } @NonNull diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index 66241b4b0067..560892dc1065 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,9 +50,9 @@ import com.hedera.node.app.service.contract.impl.records.ContractCallRecordBuilder; import com.hedera.node.app.service.contract.impl.records.ContractCreateRecordBuilder; import com.hedera.node.app.service.contract.impl.records.ContractDeleteRecordBuilder; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.records.ContractUpdateRecordBuilder; import com.hedera.node.app.service.contract.impl.records.EthereumTransactionRecordBuilder; -import com.hedera.node.app.service.contract.impl.records.GasFeeRecordBuilder; import com.hedera.node.app.service.file.impl.records.CreateFileRecordBuilder; import com.hedera.node.app.service.schedule.ScheduleRecordBuilder; import com.hedera.node.app.service.token.api.FeeRecordBuilder; @@ -126,7 +126,7 @@ public class SingleTransactionRecordBuilderImpl FeeRecordBuilder, ContractDeleteRecordBuilder, GenesisAccountRecordBuilder, - GasFeeRecordBuilder, + ContractOperationRecordBuilder, TokenAccountWipeRecordBuilder, CryptoUpdateRecordBuilder { private static final Comparator TOKEN_ASSOCIATION_COMPARATOR = diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/fees/congestion/CongestionMultipliersTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/fees/congestion/CongestionMultipliersTest.java index 515dfb3b6f30..a9d4313c1ea2 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/fees/congestion/CongestionMultipliersTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/fees/congestion/CongestionMultipliersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,7 +68,7 @@ void testUpdateMultiplier() { @Test void testMaxCurrentMultiplier() { when(throttleMultiplier.currentMultiplier()).thenReturn(2L); - when(entityUtilizationMultiplier.currentMultiplier(txnInfo, storeFactory)) + when(entityUtilizationMultiplier.currentMultiplier(txnInfo.txBody(), txnInfo.functionality(), storeFactory)) .thenReturn(3L); long maxMultiplier = congestionMultipliers.maxCurrentMultiplier(txnInfo, storeFactory); diff --git a/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java b/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java index 55f417edee69..f850a8a2a564 100644 --- a/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/common/AbstractXTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.ResponseHeader; +import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenType; import com.hedera.hapi.node.state.blockrecords.BlockInfo; @@ -402,7 +403,14 @@ private void setupInitialStates() { fakeHederaState.addService( BlockRecordService.NAME, Map.of( - BlockRecordService.BLOCK_INFO_STATE_KEY, new AtomicReference<>(BlockInfo.DEFAULT), + BlockRecordService.BLOCK_INFO_STATE_KEY, + new AtomicReference<>(new BlockInfo( + -1L, + Timestamp.DEFAULT, + Bytes.EMPTY, + Timestamp.DEFAULT, + true, + Timestamp.DEFAULT)), BlockRecordService.RUNNING_HASHES_STATE_KEY, new AtomicReference<>(initialRunningHashes()))); fakeHederaState.addService( diff --git a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java index 4605370f8bae..69cef95ca192 100644 --- a/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java +++ b/hedera-node/hedera-app/src/xtest/java/contract/AbstractContractXTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallFactory; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.SyntheticIds; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.handlers.ContractCallHandler; import com.hedera.node.app.service.contract.impl.handlers.ContractCreateHandler; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; @@ -267,7 +268,8 @@ private void runHtsCallAndExpect( tinybarValues, systemContractGasCalculator, component.config().getConfigData(HederaConfig.class), - HederaFunctionality.CONTRACT_CALL), + HederaFunctionality.CONTRACT_CALL, + new PendingCreationMetadataRef()), new HandleHederaNativeOperations(context), new HandleSystemContractOperations(context)); given(proxyUpdater.enhancement()).willReturn(enhancement); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/CallOutcome.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/CallOutcome.java index 7e22bf81eb53..85b1f4ae79be 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/CallOutcome.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/CallOutcome.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,15 @@ package com.hedera.node.app.service.contract.impl.exec; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.hapi.streams.ContractActions; +import com.hedera.hapi.streams.ContractStateChanges; +import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -31,12 +35,38 @@ * @param result the result of the call * @param status the resolved status of the call * @param tinybarGasPrice the tinybar-denominated gas price used for the call + * @param actions any contract actions that should be externalized in a sidecar + * @param stateChanges any contract state changes that should be externalized in a sidecar */ public record CallOutcome( @NonNull ContractFunctionResult result, @NonNull ResponseCodeEnum status, @Nullable ContractID recipientId, - long tinybarGasPrice) { + long tinybarGasPrice, + @Nullable ContractActions actions, + @Nullable ContractStateChanges stateChanges) { + + public boolean hasStateChanges() { + return stateChanges != null + && !stateChanges.contractStateChangesOrElse(emptyList()).isEmpty(); + } + + public static CallOutcome fromResultsWithMaybeSidecars( + @NonNull ContractFunctionResult result, @NonNull HederaEvmTransactionResult hevmResult) { + return new CallOutcome( + result, + hevmResult.finalStatus(), + hevmResult.recipientId(), + hevmResult.gasPrice(), + hevmResult.actions(), + hevmResult.stateChanges()); + } + + public static CallOutcome fromResultsWithoutSidecars( + @NonNull ContractFunctionResult result, @NonNull HederaEvmTransactionResult hevmResult) { + return new CallOutcome( + result, hevmResult.finalStatus(), hevmResult.recipientId(), hevmResult.gasPrice(), null, null); + } public CallOutcome { requireNonNull(result); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextQueryProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextQueryProcessor.java index c56cef33a7b6..9abe841fa725 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextQueryProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextQueryProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,7 +83,7 @@ public CallOutcome call() { final var result = processor.processTransaction( hevmTransaction, worldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, context.configuration()); - // Return the outcome, maybe enriched with details of the base commit and Ethereum transaction - return new CallOutcome(result.asQueryResult(), result.finalStatus(), result.recipientId(), result.gasPrice()); + // Return the outcome (which cannot include sidecars to be externalized, since this is a query) + return CallOutcome.fromResultsWithoutSidecars(result.asQueryResult(), result); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index 09f2dd0b159f..28eb95b071e2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package com.hedera.node.app.service.contract.impl.exec; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmVersion.EVM_VERSIONS; +import static java.util.Objects.requireNonNull; +import com.hedera.hapi.streams.ContractBytecode; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; @@ -98,25 +100,25 @@ public CallOutcome call() { // Get the appropriate processor for the EVM version final var processor = processors.get(EVM_VERSIONS.get(contractsConfig.evmVersion())); - // Process the transaction + // Process the transaction and return its outcome try { final var result = processor.processTransaction( hevmTransaction, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, configuration); - // Return the outcome, maybe enriched with details of the base commit and Ethereum transaction - return new CallOutcome( - result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), - result.finalStatus(), - result.recipientId(), - result.gasPrice()); + // For mono-service fidelity, externalize an initcode-only sidecar when a top-level creation fails + if (!result.isSuccess() && hevmTransaction.needsInitcodeExternalizedOnFailure()) { + final var contractBytecode = ContractBytecode.newBuilder() + .initcode(hevmTransaction.payload()) + .build(); + requireNonNull(hederaEvmContext.recordBuilder()).addContractBytecode(contractBytecode, false); + } + return CallOutcome.fromResultsWithMaybeSidecars( + result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), result); } catch (AbortException e) { // Commit any HAPI fees that were charged before aborting rootProxyWorldUpdater.commit(); final var result = HederaEvmTransactionResult.fromAborted(e.senderId(), hevmTransaction, e.getStatus()); - return new CallOutcome( - result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), - result.finalStatus(), - result.recipientId(), - result.gasPrice()); + return CallOutcome.fromResultsWithoutSidecars( + result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), result); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/EvmActionTracer.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/EvmActionTracer.java index a11dc35a4338..c3ffd46453bb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/EvmActionTracer.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/EvmActionTracer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import static org.hyperledger.besu.evm.frame.MessageFrame.State.CODE_SUSPENDED; import com.hedera.hapi.streams.ContractActionType; +import com.hedera.hapi.streams.ContractActions; import com.hedera.node.app.service.contract.impl.exec.utils.ActionStack; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import edu.umd.cs.findbugs.annotations.NonNull; @@ -93,7 +94,7 @@ public void tracePostExecution( * {@inheritDoc} */ @Override - public void tracePrecompileResult(@NonNull MessageFrame frame, @NonNull ContractActionType type) { + public void tracePrecompileResult(@NonNull final MessageFrame frame, @NonNull final ContractActionType type) { requireNonNull(type); requireNonNull(frame); if (hasActionSidecarsEnabled(frame)) { @@ -101,6 +102,11 @@ public void tracePrecompileResult(@NonNull MessageFrame frame, @NonNull Contract } } + @Override + public @NonNull ContractActions contractActions() { + return actionStack.asContractActions(); + } + /** * {@inheritDoc} */ diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java index 1ba2f7d8ece8..cda9154896e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/FrameRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,9 +103,9 @@ public HederaEvmTransactionResult runToCompletion( final var gasUsed = effectiveGasUsed(gasLimit, frame); if (frame.getState() == COMPLETED_SUCCESS) { return successFrom( - gasUsed, senderId, recipientMetadata.hederaId(), asEvmContractId(recipientAddress), frame); + gasUsed, senderId, recipientMetadata.hederaId(), asEvmContractId(recipientAddress), frame, tracer); } else { - return failureFrom(gasUsed, senderId, frame, recipientMetadata.postFailureHederaId()); + return failureFrom(gasUsed, senderId, frame, recipientMetadata.postFailureHederaId(), tracer); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java index da0000d56b0c..3e766fcc5488 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,13 +113,15 @@ static HederaEvmContext provideHederaEvmContext( @NonNull final HederaEvmBlocks hederaEvmBlocks, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator) { - // Use null for the DeleteCapableTransactionRecordBuilder, as selfdestruct is illegal in a static context + // Use null for the top-level record builder and reference to pending creation record builder, + // as neither is usable by any operation permitted in a static context return new HederaEvmContext( hederaOperations.gasPriceInTinybars(), true, hederaEvmBlocks, tinybarValues, systemContractGasCalculator, + null, null); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java index b365350ac8db..742bc59ab0ba 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.scope.HederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; import com.hedera.node.app.service.contract.impl.exec.utils.ActionStack; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HandleContextHevmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; @@ -42,6 +43,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.EvmFrameStateFactory; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.state.ScopedEvmFrameStateFactory; @@ -51,7 +53,6 @@ import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.FunctionalityResourcePrices; import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.data.HederaConfig; import dagger.Binds; import dagger.Module; @@ -137,14 +138,16 @@ static HederaEvmContext provideHederaEvmContext( @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator, @NonNull final HederaOperations hederaOperations, - @NonNull final HederaEvmBlocks hederaEvmBlocks) { + @NonNull final HederaEvmBlocks hederaEvmBlocks, + @NonNull final PendingCreationMetadataRef pendingCreationMetadataRef) { return new HederaEvmContext( hederaOperations.gasPriceInTinybars(), false, hederaEvmBlocks, tinybarValues, systemContractGasCalculator, - context.recordBuilder(DeleteCapableTransactionRecordBuilder.class)); + context.recordBuilder(ContractOperationRecordBuilder.class), + pendingCreationMetadataRef); } @Provides diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java index 074784a41cb2..b387e631724e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,6 +76,18 @@ public long gasRequirement( return asGasRequirement(priceInTinybars); } + /** + * Given a transaction body whose, returns the gas requirement for the transaction to be + * dispatched using the gas price of the top-level HAPI operation and no markup. + * + * @param body the transaction body to be dispatched + * @param payer the payer of the transaction + * @return the gas requirement for the transaction to be dispatched + */ + public long topLevelGasRequirement(@NonNull final TransactionBody body, @NonNull final AccountID payer) { + return feeCalculator.applyAsLong(body, payer) / tinybarValues.topLevelTinybarGasPrice(); + } + /** * Given a dispatch type, returns the canonical gas requirement for that dispatch type. * Useful when providing a ballpark gas requirement in the absence of a valid @@ -139,7 +151,10 @@ public long gasCostInTinybars(final long gas) { * @return the equivalent gas requirement at the current gas price */ private long asGasRequirement(final long tinybarPrice) { - final var gasPrice = tinybarValues.childTransactionTinybarGasPrice(); + return asGasRequirement(tinybarPrice, tinybarValues.childTransactionTinybarGasPrice()); + } + + private long asGasRequirement(final long tinybarPrice, final long gasPrice) { // We round up to the nearest gas unit, and then add 20% to account for the premium // of doing a HAPI operation from inside the EVM final var gasRequirement = (tinybarPrice + gasPrice - 1) / gasPrice; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/AbstractCustomCreateOperation.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/AbstractCustomCreateOperation.java index 110077c236df..a476c51adeb8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/AbstractCustomCreateOperation.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/AbstractCustomCreateOperation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java index eda60faadfcd..1b2c1330ccb0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/operations/CustomCreate2Operation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,11 +67,7 @@ protected long cost(@NonNull final MessageFrame frame) { @Override protected void onSuccess(@NonNull final MessageFrame frame, @NonNull final Address creation) { - final var updater = (ProxyWorldUpdater) frame.getWorldUpdater(); - if (updater.isHollowAccount(creation)) { - // Pass along the address of the "parent" finalizing the hollow account as well - updater.finalizeHollowAccount(creation, frame.getRecipientAddress()); - } + // No-op, CustomContractCreationProcessor will finalize the creation address if a hollow account } private Address eip1014AddressFor(@NonNull final MessageFrame frame) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java index 8190a05e935c..2525ae169f48 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomContractCreationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,16 @@ package com.hedera.node.app.service.contract.impl.exec.processors; import static com.hedera.node.app.service.contract.impl.exec.processors.ProcessorModule.INITIAL_CONTRACT_NONCE; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.getAndClearPendingCreationMetadata; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.hasBytecodeSidecarsEnabled; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.streams.ContractBytecode; import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; @@ -79,6 +84,9 @@ public void start(@NonNull final MessageFrame frame, @NonNull final OperationTra halt(frame, tracer, COLLISION_HALT_REASON); } else { final var updater = proxyUpdaterFor(frame); + if (isHollow(contract)) { + updater.finalizeHollowAccount(addressToCreate, frame.getSenderAddress()); + } // A contract creation is never a delegate call, hence the false argument below final var maybeReasonToHalt = updater.tryTransfer( frame.getSenderAddress(), addressToCreate, frame.getValue().toLong(), false); @@ -94,6 +102,31 @@ public void start(@NonNull final MessageFrame frame, @NonNull final OperationTra } } + @Override + public void codeSuccess(@NonNull final MessageFrame frame, @NonNull final OperationTracer tracer) { + super.codeSuccess(requireNonNull(frame), requireNonNull(tracer)); + // TODO - check if a code rule failed before proceeding + if (hasBytecodeSidecarsEnabled(frame)) { + final var recipient = proxyUpdaterFor(frame).getHederaAccount(frame.getRecipientAddress()); + final var recipientId = requireNonNull(recipient).hederaContractId(); + final var pendingCreationMetadata = getAndClearPendingCreationMetadata(frame, recipientId); + final var contractBytecode = ContractBytecode.newBuilder() + .contractId(recipientId) + .runtimeBytecode(tuweniToPbjBytes(recipient.getCode())); + if (pendingCreationMetadata.externalizeInitcodeOnSuccess()) { + contractBytecode.initcode(tuweniToPbjBytes(frame.getCode().getBytes())); + } + pendingCreationMetadata.recordBuilder().addContractBytecode(contractBytecode.build(), false); + } + } + + @Override + protected void revert(final MessageFrame frame) { + super.revert(frame); + // Clear the childRecords from the record builder checkpoint in ProxyWorldUpdater, when revert() is called + ((HederaWorldUpdater) frame.getWorldUpdater()).revertChildRecords(); + } + private void halt( @NonNull final MessageFrame frame, @NonNull final OperationTracer tracer, @@ -101,16 +134,17 @@ private void halt( frame.setState(MessageFrame.State.EXCEPTIONAL_HALT); frame.setExceptionalHaltReason(reason); tracer.traceAccountCreationResult(frame, reason); + // TODO - should we revert child records here? } private boolean alreadyCreated(final MutableAccount account) { return account.getNonce() > 0 || account.getCode().size() > 0; } - @Override - protected void revert(final MessageFrame frame) { - super.revert(frame); - // Clear the childRecords from the record builder checkpoint in ProxyWorldUpdater, when revert() is called - ((HederaWorldUpdater) frame.getWorldUpdater()).revertChildRecords(); + private boolean isHollow(@NonNull final MutableAccount account) { + if (account instanceof ProxyEvmAccount proxyEvmAccount) { + return proxyEvmAccount.isHollow(); + } + throw new IllegalArgumentException("Creation target not a ProxyEvmAccount - " + account); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java index 18242a281b00..8105e14aaf87 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/processors/CustomMessageCallProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package com.hedera.node.app.service.contract.impl.exec.processors; +import static com.hedera.hapi.streams.ContractActionType.PRECOMPILE; +import static com.hedera.hapi.streams.ContractActionType.SYSTEM; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INSUFFICIENT_CHILD_RECORDS; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_FEE_SUBMITTED; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_SIGNATURE; @@ -29,9 +31,11 @@ import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.PRECOMPILE_ERROR; import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT; +import com.hedera.hapi.streams.ContractActionType; import com.hedera.node.app.service.contract.impl.exec.AddressChecks; import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.HederaSystemContract; +import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.swirlds.config.api.Configuration; @@ -161,7 +165,7 @@ private void doExecutePrecompile( if (result.isRefundGas()) { frame.incrementRemainingGas(gasRequirement); } - finishPrecompileExecution(frame, result); + finishPrecompileExecution(frame, result, PRECOMPILE, (ActionSidecarContentTracer) tracer); } } @@ -188,12 +192,15 @@ private void doExecuteSystemContract( if (!fullResult.isRefundGas()) { frame.decrementRemainingGas(gasRequirement); } - finishPrecompileExecution(frame, fullResult.result()); + finishPrecompileExecution(frame, fullResult.result(), SYSTEM, (ActionSidecarContentTracer) tracer); } } private void finishPrecompileExecution( - @NonNull final MessageFrame frame, @NonNull final PrecompileContractResult result) { + @NonNull final MessageFrame frame, + @NonNull final PrecompileContractResult result, + @NonNull final ContractActionType type, + @NonNull final ActionSidecarContentTracer tracer) { if (frame.getState() == MessageFrame.State.REVERT) { frame.setRevertReason(result.getOutput()); } else { @@ -201,6 +208,7 @@ private void finishPrecompileExecution( } frame.setState(result.getState()); frame.setExceptionalHaltReason(result.getHaltReason()); + tracer.tracePrecompileResult(frame, type); } private void doTransferValueOrHalt( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java index 7087c2f8a14c..2e4133ba6671 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package com.hedera.node.app.service.contract.impl.exec.scope; -import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.*; @@ -35,10 +34,12 @@ import com.hedera.hapi.node.transaction.SignedTransaction; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; -import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadata; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.records.ContractCreateRecordBuilder; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.ContractStateStore; import com.hedera.node.app.service.contract.impl.state.WritableContractStateStore; import com.hedera.node.app.service.token.ReadableAccountStore; @@ -88,6 +89,7 @@ public class HandleHederaOperations implements HederaOperations { private final SystemContractGasCalculator gasCalculator; private final HandleContext context; private final HederaFunctionality functionality; + private final PendingCreationMetadataRef pendingCreationMetadataRef; @Inject public HandleHederaOperations( @@ -97,7 +99,8 @@ public HandleHederaOperations( @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator gasCalculator, @NonNull final HederaConfig hederaConfig, - @NonNull final HederaFunctionality functionality) { + @NonNull final HederaFunctionality functionality, + @NonNull final PendingCreationMetadataRef pendingCreationMetadataRef) { this.ledgerConfig = requireNonNull(ledgerConfig); this.contractsConfig = requireNonNull(contractsConfig); this.context = requireNonNull(context); @@ -105,6 +108,7 @@ public HandleHederaOperations( this.hederaConfig = requireNonNull(hederaConfig); this.gasCalculator = requireNonNull(gasCalculator); this.functionality = requireNonNull(functionality); + this.pendingCreationMetadataRef = requireNonNull(pendingCreationMetadataRef); } /** @@ -185,19 +189,17 @@ public long contractCreationLimit() { public long lazyCreationCostInGas(@NonNull final Address recipient) { final var payerId = context.payer(); // Calculate gas for a CryptoCreateTransactionBody with an alias address - final var createFee = gasCalculator.gasRequirement( + final var createFee = gasCalculator.topLevelGasRequirement( TransactionBody.newBuilder() .cryptoCreateAccount(CREATE_TXN_BODY_BUILDER.alias(tuweniToPbjBytes(recipient))) .build(), - DispatchType.CRYPTO_CREATE, payerId); // Calculate gas for an update TransactionBody - final var updateFee = gasCalculator.gasRequirement( + final var updateFee = gasCalculator.topLevelGasRequirement( TransactionBody.newBuilder() .cryptoUpdateAccount(UPDATE_TXN_BODY_BUILDER) .build(), - DispatchType.CRYPTO_UPDATE, payerId); return createFee + updateFee; @@ -208,8 +210,7 @@ public long lazyCreationCostInGas(@NonNull final Address recipient) { */ @Override public long gasPriceInTinybars() { - var multiplier = context.feeCalculator(SubType.DEFAULT).getCongestionMultiplier(); - return tinybarValues.topLevelTinybarGasPrice() * multiplier; + return tinybarValues.topLevelTinybarGasPrice(); } /** @@ -280,7 +281,8 @@ public void createContract(final long number, final long parentNumber, @Nullable ContractID.newBuilder().contractNum(number).build(), evmAddress, impliedContractCreation), impliedContractCreation, parent.autoRenewAccountId(), - evmAddress); + evmAddress, + ExternalizeInitcodeOnSuccess.YES); } /** @@ -298,7 +300,8 @@ public void createContract( ContractID.newBuilder().contractNum(number).build(), evmAddress, body), functionality == HederaFunctionality.ETHEREUM_TRANSACTION ? body : null, body.autoRenewAccountId(), - evmAddress); + evmAddress, + body.hasInitcode() ? ExternalizeInitcodeOnSuccess.NO : ExternalizeInitcodeOnSuccess.YES); } /** @@ -348,7 +351,7 @@ public void externalizeHollowAccountMerge( @NonNull ContractID contractId, @NonNull ContractID parentId, @Nullable Bytes evmAddress) { final var accountStore = context.readableStore(ReadableAccountStore.class); final var parent = requireNonNull(accountStore.getContractById(parentId)); - context.addRemovableChildRecordBuilder(ContractCreateRecordBuilder.class) + final var recordBuilder = context.addRemovableChildRecordBuilder(ContractCreateRecordBuilder.class) .contractID(contractId) .status(SUCCESS) .transaction(transactionWith(TransactionBody.newBuilder() @@ -358,6 +361,8 @@ public void externalizeHollowAccountMerge( .contractID(contractId) .evmAddress(evmAddress) .build()); + final var pendingCreationMetadata = new PendingCreationMetadata(recordBuilder, true); + pendingCreationMetadataRef.set(contractId, pendingCreationMetadata); } @Override @@ -370,29 +375,44 @@ public RecordListCheckPoint createRecordListCheckPoint() { return context.createRecordListCheckPoint(); } + private enum ExternalizeInitcodeOnSuccess { + YES, + NO + } + private void dispatchAndMarkCreation( final long number, @NonNull final CryptoCreateTransactionBody bodyToDispatch, @Nullable final ContractCreateTransactionBody bodyToExternalize, @Nullable final AccountID autoRenewAccountId, - @Nullable final Bytes evmAddress) { + @Nullable final Bytes evmAddress, + @NonNull final ExternalizeInitcodeOnSuccess externalizeInitcodeOnSuccess) { // Create should have conditional child record, but we only externalize this child if it's not already // externalized by the top-level HAPI transaction; and we "finish" the synthetic transaction by swapping // in the contract creation body for the dispatched crypto create body + final var isTopLevelCreation = bodyToExternalize == null; final var recordBuilder = context.dispatchRemovableChildTransaction( TransactionBody.newBuilder().cryptoCreateAccount(bodyToDispatch).build(), ContractCreateRecordBuilder.class, null, context.payer(), - (bodyToExternalize == null) + isTopLevelCreation ? SUPPRESSING_EXTERNALIZED_RECORD_CUSTOMIZER : contractBodyCustomizerFor(number, bodyToExternalize)); - // TODO - deal with MAX_ENTITIES_IN_PRICE_REGIME_CREATED - if (recordBuilder.status() != OK && recordBuilder.status() != SUCCESS) { - throw new AssertionError("Not implemented"); + if (recordBuilder.status() != SUCCESS) { + // The only plausible failure mode (MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED) should + // have been pre-validated in ProxyWorldUpdater.createAccount() so this is an invariant failure + throw new IllegalStateException("Unexpected failure creating new contract - " + recordBuilder.status()); } - // On success we add extra information on the created contract + // If this creation runs to a successful completion, its ContractBytecode sidecar + // goes in the top-level record or the just-created child record depending on whether + // we are doing this on behalf of a HAPI ContractCreate call; we only include the + // initcode in the bytecode sidecar if it's not already externalized via a body + final var pendingCreationMetadata = new PendingCreationMetadata( + isTopLevelCreation ? context.recordBuilder(ContractOperationRecordBuilder.class) : recordBuilder, + externalizeInitcodeOnSuccess == ExternalizeInitcodeOnSuccess.YES); final var contractId = ContractID.newBuilder().contractNum(number).build(); + pendingCreationMetadataRef.set(contractId, pendingCreationMetadata); recordBuilder .contractID(contractId) .contractCreateResult(ContractFunctionResult.newBuilder() diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java index 52f004bff8b2..d7215c3874f5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/ActionStack.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import static com.hedera.hapi.streams.ContractActionType.CALL; import static com.hedera.hapi.streams.ContractActionType.CREATE; import static com.hedera.hapi.streams.ContractActionType.PRECOMPILE; +import static com.hedera.hapi.streams.codec.ContractActionProtoCodec.RECIPIENT_UNSET; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_SOLIDITY_ADDRESS; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asNumberedContractId; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.hederaIdNumOfContractIn; @@ -39,6 +40,7 @@ import com.hedera.hapi.streams.CallOperationType; import com.hedera.hapi.streams.ContractAction; import com.hedera.hapi.streams.ContractActionType; +import com.hedera.hapi.streams.ContractActions; import com.hedera.node.app.service.contract.impl.utils.OpcodeUtils; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; @@ -98,6 +100,16 @@ public ActionStack( this.actionsStack = actionsStack; } + /** + * Returns a view of this stack appropriate for externalizing in a + * {@link com.hedera.hapi.streams.SidecarType#CONTRACT_ACTION} sidecar. + * + * @return a view of this stack ready to be put in a sidecar + */ + public @NonNull ContractActions asContractActions() { + return new ContractActions(allActions.stream().map(ActionWrapper::get).toList()); + } + /** * Finalizes the last action created in this stack in the context of the given frame; and * also pops the action from the stack if it is topmost. The nature of finalization work @@ -196,10 +208,7 @@ private ContractAction finalFormOf(@NonNull final ContractAction action, @NonNul .ifPresentOrElse( reason -> builder.revertReason(tuweniToPbjBytes(reason)), () -> builder.revertReason(Bytes.EMPTY)); - if (frame.getType() == CONTRACT_CREATION) { - builder.recipientContract((ContractID) null); - } - yield builder.build(); + yield withUnsetRecipientIfNeeded(frame.getType(), builder); } case EXCEPTIONAL_HALT, COMPLETED_FAILED -> { final var builder = action.copyBuilder(); @@ -214,10 +223,7 @@ private ContractAction finalFormOf(@NonNull final ContractAction action, @NonNul } else { builder.error(Bytes.EMPTY); } - if (frame.getType() == CONTRACT_CREATION) { - builder.recipientContract((ContractID) null); - } - yield builder.build(); + yield withUnsetRecipientIfNeeded(frame.getType(), builder); } }; } @@ -331,6 +337,28 @@ private ContractID contractIdWith(final long num) { return ContractID.newBuilder().contractNum(num).build(); } + private ContractAction withUnsetRecipientIfNeeded( + @NonNull MessageFrame.Type type, @NonNull final ContractAction.Builder builder) { + final var action = builder.build(); + return (type == CONTRACT_CREATION) ? withUnsetRecipient(action) : action; + } + + // (FUTURE) Use builder for simplicity when PBJ lets us set the oneof recipient to UNSET; + // c.f., https://github.com/hashgraph/pbj/issues/160 + private ContractAction withUnsetRecipient(@NonNull final ContractAction action) { + return new ContractAction( + action.callType(), + action.caller(), + action.gas(), + action.input(), + RECIPIENT_UNSET, + action.value(), + action.gasUsed(), + action.resultData(), + action.callDepth(), + action.callOperationType()); + } + private boolean targetsMissingAddress(@NonNull final MessageFrame frame) { return frame.getType() == MESSAGE_CALL && isMissing(frame, frame.getContractAddress()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java index 6b67dff5c387..387a41fac87d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,9 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hedera.hapi.streams.SidecarType.CONTRACT_STATE_CHANGE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.HAPI_RECORD_BUILDER_CONTEXT_VARIABLE; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.PENDING_CREATION_BUILDER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SELF_DESTRUCT_BENEFICIARIES; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TINYBAR_VALUES_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TRACKER_CONTEXT_VARIABLE; @@ -119,12 +120,14 @@ private Map contextVariablesFrom( contextEntries.put(CONFIG_CONTEXT_VARIABLE, config); contextEntries.put(TINYBAR_VALUES_CONTEXT_VARIABLE, context.tinybarValues()); contextEntries.put(SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE, context.systemContractGasCalculator()); - contextEntries.put(PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE, new PropagatedCallFailureReference()); + contextEntries.put(PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE, new PropagatedCallFailureRef()); if (config.getConfigData(ContractsConfig.class).sidecars().contains(CONTRACT_STATE_CHANGE)) { contextEntries.put(TRACKER_CONTEXT_VARIABLE, new StorageAccessTracker()); } - if (context.isDeleteCapable()) { - contextEntries.put(SELF_DESTRUCT_BENEFICIARIES, context.deleteCapableTransactionRecordBuilder()); + if (context.isTransaction()) { + contextEntries.put(HAPI_RECORD_BUILDER_CONTEXT_VARIABLE, context.recordBuilder()); + contextEntries.put( + PENDING_CREATION_BUILDER_CONTEXT_VARIABLE, context.pendingCreationRecordBuilderReference()); } return contextEntries; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java index d455cc87f547..de38479dd205 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,9 +17,11 @@ package com.hedera.node.app.service.contract.impl.exec.utils; import static com.hedera.hapi.streams.SidecarType.CONTRACT_ACTION; +import static com.hedera.hapi.streams.SidecarType.CONTRACT_BYTECODE; import static com.hedera.node.app.service.evm.store.contracts.HederaEvmWorldStateTokenAccount.TOKEN_PROXY_ACCOUNT_NONCE; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.ContractID; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; @@ -40,9 +42,10 @@ public class FrameUtils { public static final String CONFIG_CONTEXT_VARIABLE = "contractsConfig"; public static final String TRACKER_CONTEXT_VARIABLE = "storageAccessTracker"; public static final String TINYBAR_VALUES_CONTEXT_VARIABLE = "tinybarValues"; - public static final String SELF_DESTRUCT_BENEFICIARIES = "selfDestructBeneficiaries"; + public static final String HAPI_RECORD_BUILDER_CONTEXT_VARIABLE = "hapiRecordBuilder"; public static final String PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE = "propagatedCallFailure"; public static final String SYSTEM_CONTRACT_GAS_CALCULATOR_CONTEXT_VARIABLE = "systemContractGasCalculator"; + public static final String PENDING_CREATION_BUILDER_CONTEXT_VARIABLE = "pendingCreationBuilder"; private FrameUtils() { throw new UnsupportedOperationException("Utility Class"); @@ -56,6 +59,10 @@ private FrameUtils() { return configOf(frame).getConfigData(ContractsConfig.class); } + public static boolean hasBytecodeSidecarsEnabled(@NonNull final MessageFrame frame) { + return contractsConfigOf(frame).sidecars().contains(CONTRACT_BYTECODE); + } + public static boolean hasActionSidecarsEnabled(@NonNull final MessageFrame frame) { return contractsConfigOf(frame).sidecars().contains(CONTRACT_ACTION); } @@ -99,8 +106,18 @@ public static void setPropagatedCallFailure( return propagatedCallFailureReference(frame).getAndClear(); } - private static PropagatedCallFailureReference propagatedCallFailureReference(@NonNull final MessageFrame frame) { - return initialFrameOf(frame).getContextVariable(PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE); + /** + * Gets and clears any metadata for a pending creation in the context of the given frame. + * + * @param frame a frame in the transaction of interest + * @param contractID the contract id of the pending creation + * @return the metadata for the pending creation + */ + public static @NonNull PendingCreationMetadata getAndClearPendingCreationMetadata( + @NonNull final MessageFrame frame, @NonNull final ContractID contractID) { + requireNonNull(frame); + requireNonNull(contractID); + return pendingCreationMetadataRef(frame).getAndClearOrThrowFor(contractID); } public static @NonNull ProxyWorldUpdater proxyUpdaterFor(@NonNull final MessageFrame frame) { @@ -124,7 +141,7 @@ private static PropagatedCallFailureReference propagatedCallFailureReference(@No */ public static @NonNull DeleteCapableTransactionRecordBuilder selfDestructBeneficiariesFor( @NonNull final MessageFrame frame) { - return requireNonNull(initialFrameOf(frame).getContextVariable(SELF_DESTRUCT_BENEFICIARIES)); + return requireNonNull(initialFrameOf(frame).getContextVariable(HAPI_RECORD_BUILDER_CONTEXT_VARIABLE)); } public static @NonNull SystemContractGasCalculator systemContractGasCalculatorOf( @@ -252,4 +269,12 @@ private static boolean isToken(final MessageFrame frame, final Address address) final var stack = frame.getMessageFrameStack(); return stack.isEmpty() ? frame : stack.getLast(); } + + private static PropagatedCallFailureRef propagatedCallFailureReference(@NonNull final MessageFrame frame) { + return initialFrameOf(frame).getContextVariable(PROPAGATED_CALL_FAILURE_CONTEXT_VARIABLE); + } + + private static PendingCreationMetadataRef pendingCreationMetadataRef(@NonNull final MessageFrame frame) { + return initialFrameOf(frame).getContextVariable(PENDING_CREATION_BUILDER_CONTEXT_VARIABLE); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadata.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadata.java new file mode 100644 index 000000000000..32d7405397b4 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadata.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.utils; + +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; + +public record PendingCreationMetadata( + @NonNull ContractOperationRecordBuilder recordBuilder, boolean externalizeInitcodeOnSuccess) {} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadataRef.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadataRef.java new file mode 100644 index 000000000000..987b51ca59ac --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PendingCreationMetadataRef.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.exec.utils; + +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.ContractID; +import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; +import com.hedera.node.app.service.contract.impl.exec.processors.CustomContractCreationProcessor; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.inject.Inject; + +/** + * Wrapper that holds a reference to the {@link ContractOperationRecordBuilder} that should receive + * the bytecode sidecar in the next {@code codeSuccess()} call of {@link CustomContractCreationProcessor}. + */ +@TransactionScope +public class PendingCreationMetadataRef { + private final Map pendingMetadata = new LinkedHashMap<>(); + + @Inject + public PendingCreationMetadataRef() { + // Dagger2 + } + + /** + * Sets the given contract id's pending creation metadata to the given value. + * + * @param contractID the contract id to set pending creation metadata for + * @param metadata the metadata to set + */ + public void set(@NonNull final ContractID contractID, @NonNull final PendingCreationMetadata metadata) { + requireNonNull(metadata); + requireNonNull(contractID); + pendingMetadata.put(contractID, metadata); + } + + /** + * Returns and forgets the pending creation metadata for the given contract id. + * Throws if no metadata has been set for the pending creation. + * + * @param contractID the contract id to get metadata for + * @return the metadata for the pending creation + * @throws IllegalStateException if there is no pending creation + */ + public @NonNull PendingCreationMetadata getAndClearOrThrowFor(@NonNull final ContractID contractID) { + return requireNonNull(pendingMetadata.remove(contractID)); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PropagatedCallFailureReference.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PropagatedCallFailureRef.java similarity index 95% rename from hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PropagatedCallFailureReference.java rename to hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PropagatedCallFailureRef.java index 5a0055bc462d..993bb750d9fe 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PropagatedCallFailureReference.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/PropagatedCallFailureRef.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ * {@link org.hyperledger.besu.evm.frame.MessageFrame} context so that such failures can be * propagated up the call stack. */ -public class PropagatedCallFailureReference { +public class PropagatedCallFailureRef { private HevmPropagatedCallFailure failure = NONE; /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java index 8f1f2bfb24af..7909c0b54031 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCallHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException context.recordBuilder(ContractCallRecordBuilder.class) .contractCallResult(outcome.result()) .contractID(outcome.recipientId()) - .withTinybarGasFee(outcome.tinybarGasCost()); + .withCommonFieldsSetFrom(outcome); throwIfUnsuccessful(outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCreateHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCreateHandler.java index 3dd26ae07fdb..8f8db8a00ce3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCreateHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractCreateHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException context.recordBuilder(ContractCreateRecordBuilder.class) .contractCreateResult(outcome.result()) .contractID(outcome.recipientIdIfCreated()) - .withTinybarGasFee(outcome.tinybarGasCost()); + .withCommonFieldsSetFrom(outcome); throwIfUnsuccessful(outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java index f6f826b411a6..f2f5e207a227 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/EthereumTransactionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,6 +81,15 @@ public void preHandle(@NonNull final PreHandleContext context) throws PreCheckEx context.configuration()); } + /** + * If the given transaction, when hydrated from the given file store with the given config, implies a valid + * {@link EthTxSigs}, returns it. Otherwise, returns null. + * + * @param op the transaction + * @param fileStore the file store + * @param config the configuration + * @return the implied Ethereum signature metadata + */ public @Nullable EthTxSigs maybeEthTxSigsFor( @NonNull final EthereumTransactionBody op, @NonNull final ReadableFileStore fileStore, @@ -113,7 +122,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException // The Ethereum transaction was a top-level CONTRACT_CREATION recordBuilder.contractID(outcome.recipientIdIfCreated()).contractCreateResult(outcome.result()); } - recordBuilder.withTinybarGasFee(outcome.tinybarGasCost()); + recordBuilder.withCommonFieldsSetFrom(outcome); throwIfUnsuccessful(outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/ActionSidecarContentTracer.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/ActionSidecarContentTracer.java index abde88ba75b0..105e3318766a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/ActionSidecarContentTracer.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/ActionSidecarContentTracer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.hedera.node.app.service.contract.impl.hevm; import com.hedera.hapi.streams.ContractActionType; +import com.hedera.hapi.streams.ContractActions; import edu.umd.cs.findbugs.annotations.NonNull; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -51,4 +52,11 @@ public interface ActionSidecarContentTracer extends OperationTracer { * @param type the type of precompile called; expected values are {@code PRECOMPILE} and {@code SYSTEM} */ void tracePrecompileResult(@NonNull MessageFrame frame, @NonNull ContractActionType type); + + /** + * The final list of actions traced by this tracer. + * + * @return the actions traced by this tracer + */ + ContractActions contractActions(); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java index f12208090f12..66dddfda9560 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,37 @@ package com.hedera.node.app.service.contract.impl.hevm; +import static java.util.Objects.requireNonNull; + import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; -import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import org.hyperledger.besu.evm.frame.BlockValues; public record HederaEvmContext( long gasPrice, boolean staticCall, - HederaEvmBlocks blocks, - TinybarValues tinybarValues, - SystemContractGasCalculator systemContractGasCalculator, - @Nullable DeleteCapableTransactionRecordBuilder deleteCapableTransactionRecordBuilder) { + @NonNull HederaEvmBlocks blocks, + @NonNull TinybarValues tinybarValues, + @NonNull SystemContractGasCalculator systemContractGasCalculator, + @Nullable ContractOperationRecordBuilder recordBuilder, + @Nullable PendingCreationMetadataRef pendingCreationRecordBuilderReference) { + + public HederaEvmContext { + requireNonNull(blocks); + requireNonNull(tinybarValues); + requireNonNull(systemContractGasCalculator); + if (recordBuilder != null) { + requireNonNull(pendingCreationRecordBuilderReference); + } + if (pendingCreationRecordBuilderReference != null) { + requireNonNull(recordBuilder); + } + } + public BlockValues blockValuesOf(final long gasLimit) { return blocks.blockValuesOf(gasLimit); } @@ -37,7 +55,7 @@ public boolean isNoopGasContext() { return staticCall || gasPrice == 0; } - public boolean isDeleteCapable() { - return deleteCapableTransactionRecordBuilder != null; + public boolean isTransaction() { + return recordBuilder != null && pendingCreationRecordBuilderReference != null; } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java index d5bed5e3da93..0525704e7df5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,10 @@ public boolean isCreate() { return contractId == null; } + public boolean needsInitcodeExternalizedOnFailure() { + return hapiCreation != null && !hapiCreation.hasInitcode(); + } + public boolean isEthereumTransaction() { return relayerId != null; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java index 9ef769810941..66b2337822c4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransactionResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.errorMessageFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.hasActionSidecarsEnabled; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.proxyUpdaterFor; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asPbjStateChanges; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.bloomForAll; @@ -39,6 +40,7 @@ import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.hapi.streams.ContractActions; import com.hedera.hapi.streams.ContractStateChanges; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; @@ -66,7 +68,8 @@ public record HederaEvmTransactionResult( @Nullable Bytes revertReason, @NonNull List logs, @Nullable ContractStateChanges stateChanges, - @Nullable ResponseCodeEnum finalStatus) { + @Nullable ResponseCodeEnum finalStatus, + @Nullable ContractActions actions) { public HederaEvmTransactionResult { requireNonNull(senderId); requireNonNull(output); @@ -168,6 +171,12 @@ public ResponseCodeEnum finalStatus() { * Create a result for a transaction that succeeded. * * @param gasUsed the gas used by the transaction + * @param senderId the Hedera id of the sender + * @param recipientId the Hedera numbered id of the receiving or created contract + * @param recipientEvmAddress the Hedera aliased id of the receiving or created contract + * @param frame the root frame for the transaction + * @param tracer the Hedera-specific tracer for the EVM transaction's actions + * * @return the result */ public static HederaEvmTransactionResult successFrom( @@ -175,8 +184,10 @@ public static HederaEvmTransactionResult successFrom( @NonNull final AccountID senderId, @NonNull final ContractID recipientId, @NonNull final ContractID recipientEvmAddress, - @NonNull final MessageFrame frame) { + @NonNull final MessageFrame frame, + @NonNull final ActionSidecarContentTracer tracer) { requireNonNull(frame); + requireNonNull(tracer); return successFrom( gasUsed, frame.getGasPrice(), @@ -185,7 +196,8 @@ public static HederaEvmTransactionResult successFrom( recipientEvmAddress, frame.getOutputData(), frame.getLogs(), - allStateAccessesFrom(frame)); + maybeAllStateChangesFrom(frame), + maybeActionsFrom(frame, tracer)); } public static HederaEvmTransactionResult successFrom( @@ -196,7 +208,8 @@ public static HederaEvmTransactionResult successFrom( @NonNull final ContractID recipientEvmAddress, @NonNull final org.apache.tuweni.bytes.Bytes output, @NonNull final List logs, - @Nullable final ContractStateChanges stateChanges) { + @Nullable final ContractStateChanges stateChanges, + @Nullable ContractActions actions) { return new HederaEvmTransactionResult( gasUsed, requireNonNull(gasPrice).toLong(), @@ -208,22 +221,29 @@ public static HederaEvmTransactionResult successFrom( null, requireNonNull(logs), stateChanges, - null); + null, + actions); } /** * Create a result for a transaction that failed. * * @param gasUsed the gas used by the transaction - * @param recipientId if known, the Hedera contract ID of the recipient of the transaction + * @param senderId the Hedera id of the transaction sender + * @param frame the initial frame of the transaction + * @param recipientId if known, the Hedera id of the receiving contract + * @param tracer the Hedera-specific tracer for the EVM transaction's actions + * * @return the result */ public static HederaEvmTransactionResult failureFrom( final long gasUsed, @NonNull final AccountID senderId, @NonNull final MessageFrame frame, - @Nullable final ContractID recipientId) { + @Nullable final ContractID recipientId, + @NonNull final ActionSidecarContentTracer tracer) { requireNonNull(frame); + requireNonNull(tracer); return new HederaEvmTransactionResult( gasUsed, frame.getGasPrice().toLong(), @@ -234,8 +254,9 @@ public static HederaEvmTransactionResult failureFrom( frame.getExceptionalHaltReason().orElse(null), frame.getRevertReason().map(ConversionUtils::tuweniToPbjBytes).orElse(null), Collections.emptyList(), - stateReadsFrom(frame), - null); + maybeReadOnlyStateChangesFrom(frame), + null, + maybeActionsFrom(frame, tracer)); } /** @@ -263,6 +284,7 @@ public static HederaEvmTransactionResult resourceExhaustionFrom( Bytes.wrap(reason.name()), Collections.emptyList(), null, + null, null); } @@ -292,7 +314,8 @@ public static HederaEvmTransactionResult fromAborted( Bytes.wrap(reason.name().getBytes()), List.of(), null, - reason); + reason, + null); } private ContractFunctionResult withMaybeEthFields( @@ -346,11 +369,16 @@ public boolean isSuccess() { return revertReason == null && haltReason == null; } - private static @Nullable ContractStateChanges allStateAccessesFrom(@NonNull final MessageFrame frame) { + private static @Nullable ContractStateChanges maybeAllStateChangesFrom(@NonNull final MessageFrame frame) { return stateChangesFrom(frame, true); } - private static @Nullable ContractStateChanges stateReadsFrom(@NonNull final MessageFrame frame) { + private static @Nullable ContractActions maybeActionsFrom( + @NonNull final MessageFrame frame, @NonNull final ActionSidecarContentTracer tracer) { + return hasActionSidecarsEnabled(frame) ? tracer.contractActions() : null; + } + + private static @Nullable ContractStateChanges maybeReadOnlyStateChangesFrom(@NonNull final MessageFrame frame) { return stateChangesFrom(frame, false); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java index dd34dca11ce4..a5972db56352 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaWorldUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -102,7 +102,12 @@ record Enhancement( */ @Nullable default HederaEvmAccount getHederaAccount(@NonNull Address address) { - return getHederaAccount(getHederaContractId(address)); + requireNonNull(address); + final var maybeAccount = get(address); + if (maybeAccount instanceof HederaEvmAccount account) { + return account; + } + return null; } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java index 782f78f5a294..cf03f2d03547 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCallRecordBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ /** * Exposes the record customizations needed for a HAPI contract call transaction. */ -public interface ContractCallRecordBuilder extends GasFeeRecordBuilder { +public interface ContractCallRecordBuilder extends ContractOperationRecordBuilder { /** * Tracks the final status of a top-level contract call. diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCreateRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCreateRecordBuilder.java index 10324495560e..5ef24c5280a2 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCreateRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractCreateRecordBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ /** * Exposes the record customizations needed for a HAPI contract create transaction. */ -public interface ContractCreateRecordBuilder extends SingleTransactionRecordBuilder, GasFeeRecordBuilder { +public interface ContractCreateRecordBuilder extends SingleTransactionRecordBuilder, ContractOperationRecordBuilder { /** * Tracks the final status of a top-level contract creation. diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractOperationRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractOperationRecordBuilder.java new file mode 100644 index 000000000000..449902be9345 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/ContractOperationRecordBuilder.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.records; + +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.streams.ContractActions; +import com.hedera.hapi.streams.ContractBytecode; +import com.hedera.hapi.streams.ContractStateChanges; +import com.hedera.node.app.service.contract.impl.exec.CallOutcome; +import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; + +public interface ContractOperationRecordBuilder extends DeleteCapableTransactionRecordBuilder { + /** + * Returns the current transaction fee. + * + * @return the current transaction fee + */ + long transactionFee(); + + /** + * Sets the transaction fee. + * + * @param transactionFee the new transaction fee + * @return the updated {@link ContractOperationRecordBuilder} + */ + ContractOperationRecordBuilder transactionFee(long transactionFee); + + /** + * Updates this record builder to include the standard contract fields from the given outcome. + * + * @param outcome the EVM transaction outcome + * @return this updated builder + */ + default ContractOperationRecordBuilder withCommonFieldsSetFrom(@NonNull final CallOutcome outcome) { + transactionFee(transactionFee() + outcome.tinybarGasCost()); + if (outcome.actions() != null) { + addContractActions(outcome.actions(), false); + } + if (outcome.hasStateChanges()) { + addContractStateChanges(requireNonNull(outcome.stateChanges()), false); + } + return this; + } + + /** + * Updates this record builder to include contract actions. + * + * @param contractActions the contract actions + * @param isMigration whether these actions are exported as part of a system-initiated migration of some kind + * @return this builder + */ + @NonNull + ContractOperationRecordBuilder addContractActions(@NonNull ContractActions contractActions, boolean isMigration); + + /** + * Updates this record builder to include contract bytecode. + * + * @param contractBytecode the contract bytecode + * @param isMigration whether this bytecode is exported as part of a system-initiated migration of some kind + * @return this builder + */ + @NonNull + ContractOperationRecordBuilder addContractBytecode(@NonNull ContractBytecode contractBytecode, boolean isMigration); + + /** + * Updates this record builder to include contract state changes. + * + * @param contractStateChanges the contract state changes + * @param isMigration whether these state changes are exported as part of a system-initiated migration of some kind + * @return this builder + */ + @NonNull + ContractOperationRecordBuilder addContractStateChanges( + @NonNull ContractStateChanges contractStateChanges, boolean isMigration); +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java index 598ce361b206..8ed029dcf117 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/EthereumTransactionRecordBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -public interface EthereumTransactionRecordBuilder extends GasFeeRecordBuilder { +public interface EthereumTransactionRecordBuilder extends ContractOperationRecordBuilder { /** * Tracks the final status of a HAPI Ethereum transaction. * diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/GasFeeRecordBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/GasFeeRecordBuilder.java deleted file mode 100644 index 1fd36456f2fa..000000000000 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/records/GasFeeRecordBuilder.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.app.service.contract.impl.records; - -public interface GasFeeRecordBuilder { - /** - * Returns the current transaction fee. - * - * @return the current transaction fee - */ - long transactionFee(); - - /** - * Sets the transaction fee. - * - * @param transactionFee the new transaction fee - * @return the updated {@link GasFeeRecordBuilder} - */ - GasFeeRecordBuilder transactionFee(long transactionFee); - - /** - * Updates this record builder to include an additional tinybar-denominated gas fee. - * - * @param gasFee the tinybar-denominated gas fee to add - * @return the updated {@link GasFeeRecordBuilder} - */ - default GasFeeRecordBuilder withTinybarGasFee(final long gasFee) { - return transactionFee(transactionFee() + gasFee); - } -} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java index 539dcbf23405..2ee8debabefd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/DispatchingEvmFrameState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ * TODO - get a little further to clarify DI strategy, then bring back a code cache. */ public class DispatchingEvmFrameState implements EvmFrameState { - private static final Key HOLLOW_ACCOUNT_KEY = + public static final Key HOLLOW_ACCOUNT_KEY = Key.newBuilder().keyList(KeyList.DEFAULT).build(); private static final String TOKEN_BYTECODE_PATTERN = "fefefefefefefefefefefefefefefefefefefefe"; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java index 0bf290a0e721..0d32ba68bb27 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyEvmAccount.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package com.hedera.node.app.service.contract.impl.state; +import static com.hedera.node.app.service.contract.impl.state.DispatchingEvmFrameState.HOLLOW_ACCOUNT_KEY; + import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; import edu.umd.cs.findbugs.annotations.NonNull; @@ -162,4 +164,14 @@ public int numPositiveTokenBalances() { public boolean isContract() { return state.isContract(number); } + + /** + * Returns whether this account is a "hollow" account; i.e. an account created by sending + * value to an EVM address that did not already have a corresponding Hedera account. + * + * @return whether this account is hollow + */ + public boolean isHollow() { + return HOLLOW_ACCOUNT_KEY.equals(toNativeAccount().key()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java index 574aa91b53cb..8f9d46a809d0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/ProxyWorldUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -169,13 +169,19 @@ public ProxyWorldUpdater( */ @Override public ContractID getHederaContractId(@NonNull final Address address) { - // As an important special case, return the pending creation's contract ID if its address matches - if (pendingCreation != null && pendingCreation.address().equals(requireNonNull(address))) { - return ContractID.newBuilder().contractNum(pendingCreation.number()).build(); - } - final HederaEvmAccount account = (HederaEvmAccount) get(address); + requireNonNull(address); + final var account = (HederaEvmAccount) get(address); + // As an important special case, return the pending creation's contract ID if + // its address matches and there is no extant account; but still prioritize + // existing accounts of course if (account == null) { - throw new IllegalArgumentException("No contract pending or extant at " + address); + if (pendingCreation != null && pendingCreation.address().equals(address)) { + return ContractID.newBuilder() + .contractNum(pendingCreation.number()) + .build(); + } else { + throw new IllegalArgumentException("No contract pending or extant at " + address); + } } return account.hederaContractId(); } @@ -348,6 +354,7 @@ public MutableAccount createAccount(@NonNull final Address address, final long n if (pendingCreation == null) { throw new IllegalStateException(CANNOT_CREATE + address + " without a pending creation"); } + // TODO - also enforce the account creation limit here, since contracts are accounts if (evmFrameState.numBytecodesInState() + 1 > enhancement.operations().contractCreationLimit()) { throw new ResourceExhaustedException(MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java index 5cbdbb9c9773..9984cf3b9354 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -277,9 +277,12 @@ public static ContractStateChanges asPbjStateChanges(@NonNull final List changes = new ArrayList<>(); for (final var access : storageAccess.accesses()) { changes.add(new StorageChange( - tuweniToPbjBytes(access.key()), - tuweniToPbjBytes(access.value()), - access.isReadOnly() ? null : tuweniToPbjBytes(requireNonNull(access.writtenValue())))); + tuweniToPbjBytes(access.key().trimLeadingZeros()), + tuweniToPbjBytes(access.value().trimLeadingZeros()), + access.isReadOnly() + ? null + : tuweniToPbjBytes( + requireNonNull(access.writtenValue()).trimLeadingZeros()))); } allStateChanges.add(new ContractStateChange( ContractID.newBuilder() diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java index bbe07c0d5b60..1fd973d2b087 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.INVALID_SIGNATURE; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.typedKeyTupleFor; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asEvmAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.asLongZeroAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.headlongAddressOf; @@ -30,6 +31,8 @@ import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doReturn; import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.hapi.node.base.AccountID; @@ -73,16 +76,17 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.TokenKeyType; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransaction; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.StorageAccess; import com.hedera.node.app.service.contract.impl.state.StorageAccesses; import com.hedera.node.app.spi.key.KeyUtils; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; -import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.data.ContractsConfig; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; @@ -96,8 +100,10 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -107,6 +113,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.log.LogTopic; import org.hyperledger.besu.evm.operation.Operation; @@ -490,6 +497,7 @@ public class TestHelpers { CALLED_CONTRACT_EVM_ADDRESS, pbjToTuweniBytes(CALL_DATA), List.of(BESU_LOG), + null, null); public static final HederaEvmTransactionResult HALT_RESULT = new HederaEvmTransactionResult( @@ -503,6 +511,7 @@ public class TestHelpers { null, Collections.emptyList(), null, + null, null); public static final StorageAccesses ONE_STORAGE_ACCESSES = @@ -699,7 +708,8 @@ public static HederaEvmContext wellKnownContextWith( @NonNull final HederaEvmBlocks blocks, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator) { - return new HederaEvmContext(NETWORK_GAS_PRICE, false, blocks, tinybarValues, systemContractGasCalculator, null); + return new HederaEvmContext( + NETWORK_GAS_PRICE, false, blocks, tinybarValues, systemContractGasCalculator, null, null); } public static HederaEvmContext wellKnownContextWith( @@ -708,16 +718,22 @@ public static HederaEvmContext wellKnownContextWith( @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator) { return new HederaEvmContext( - NETWORK_GAS_PRICE, staticCall, blocks, tinybarValues, systemContractGasCalculator, null); + NETWORK_GAS_PRICE, staticCall, blocks, tinybarValues, systemContractGasCalculator, null, null); } public static HederaEvmContext wellKnownContextWith( @NonNull final HederaEvmBlocks blocks, @NonNull final TinybarValues tinybarValues, @NonNull final SystemContractGasCalculator systemContractGasCalculator, - @NonNull DeleteCapableTransactionRecordBuilder recordBuilder) { + @NonNull ContractOperationRecordBuilder recordBuilder) { return new HederaEvmContext( - NETWORK_GAS_PRICE, false, blocks, tinybarValues, systemContractGasCalculator, recordBuilder); + NETWORK_GAS_PRICE, + false, + blocks, + tinybarValues, + systemContractGasCalculator, + recordBuilder, + new PendingCreationMetadataRef()); } public static void assertFailsWith(@NonNull final ResponseCodeEnum status, @NonNull final Runnable something) { @@ -774,4 +790,15 @@ public static ContractID asNumericContractId(@NonNull final AccountID accountId) .contractNum(accountId.accountNumOrThrow()) .build(); } + + public static void givenDefaultConfigInFrame(@NonNull final MessageFrame frame) { + givenConfigInFrame(frame, DEFAULT_CONFIG); + } + + public static void givenConfigInFrame(@NonNull final MessageFrame frame, @NonNull final Configuration config) { + final Deque stack = new ArrayDeque<>(); + given(frame.getMessageFrameStack()).willReturn(stack); + doReturn(config).when(frame).getContextVariable(CONFIG_CONTEXT_VARIABLE); + given(frame.getMessageFrameStack()).willReturn(stack); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/CallOutcomeTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/CallOutcomeTest.java index f703841ba381..00ccad9fd338 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/CallOutcomeTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/CallOutcomeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,13 +41,15 @@ class CallOutcomeTest { @Test void recognizesCreatedIdWhenEvmAddressIsSet() { given(updater.getCreatedContractIds()).willReturn(List.of(CALLED_CONTRACT_ID)); - final var outcome = new CallOutcome(SUCCESS_RESULT.asProtoResultOf(updater), SUCCESS, null, NETWORK_GAS_PRICE); + final var outcome = + new CallOutcome(SUCCESS_RESULT.asProtoResultOf(updater), SUCCESS, null, NETWORK_GAS_PRICE, null, null); assertEquals(CALLED_CONTRACT_ID, outcome.recipientIdIfCreated()); } @Test void recognizesNoCreatedIdWhenEvmAddressNotSet() { - final var outcome = new CallOutcome(SUCCESS_RESULT.asProtoResultOf(updater), SUCCESS, null, NETWORK_GAS_PRICE); + final var outcome = + new CallOutcome(SUCCESS_RESULT.asProtoResultOf(updater), SUCCESS, null, NETWORK_GAS_PRICE, null, null); assertNull(outcome.recipientIdIfCreated()); } @@ -57,7 +59,9 @@ void calledIdIsFromResult() { SUCCESS_RESULT.asProtoResultOf(updater), INVALID_CONTRACT_ID, CALLED_CONTRACT_ID, - SUCCESS_RESULT.gasPrice()); + SUCCESS_RESULT.gasPrice(), + null, + null); assertEquals(CALLED_CONTRACT_ID, outcome.recipientId()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextQueryProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextQueryProcessorTest.java index bfc018ec41fc..1f7c8356465d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextQueryProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextQueryProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -91,8 +91,8 @@ void callsComponentInfraAsExpectedForValidQuery() { HEVM_CREATION, proxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) .willReturn(SUCCESS_RESULT); final var protoResult = SUCCESS_RESULT.asQueryResult(); - final var expectedResult = - new CallOutcome(protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice()); + final var expectedResult = new CallOutcome( + protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice(), null, null); assertEquals(expectedResult, subject.call()); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java index d9dce7b1ddf5..40f0fd953656 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,8 +100,8 @@ void callsComponentInfraAsExpectedForValidEthTx() { .willReturn(SUCCESS_RESULT); final var protoResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, rootProxyWorldUpdater); - final var expectedResult = - new CallOutcome(protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice()); + final var expectedResult = new CallOutcome( + protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice(), null, null); assertEquals(expectedResult, subject.call()); } @@ -129,8 +129,8 @@ void callsComponentInfraAsExpectedForNonEthTx() { .willReturn(SUCCESS_RESULT); final var protoResult = SUCCESS_RESULT.asProtoResultOf(null, rootProxyWorldUpdater); - final var expectedResult = - new CallOutcome(protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice()); + final var expectedResult = new CallOutcome( + protoResult, SUCCESS, HEVM_CREATION.contractId(), SUCCESS_RESULT.gasPrice(), null, null); assertEquals(expectedResult, subject.call()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/EvmActionTracerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/EvmActionTracerTest.java index 289c324ddb6d..b2b872574068 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/EvmActionTracerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/EvmActionTracerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package com.hedera.node.app.service.contract.impl.test.exec; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; @@ -24,6 +25,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import com.hedera.hapi.streams.ContractActionType; +import com.hedera.hapi.streams.ContractActions; import com.hedera.node.app.service.contract.impl.exec.EvmActionTracer; import com.hedera.node.app.service.contract.impl.exec.utils.ActionStack; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; @@ -63,10 +65,12 @@ void setUp() { @Test void customInitIsNoopWithoutActionSidecars() { givenNoActionSidecars(); + given(actionStack.asContractActions()).willReturn(ContractActions.DEFAULT); subject.traceOriginAction(frame); verifyNoInteractions(actionStack); + assertSame(ContractActions.DEFAULT, subject.contractActions()); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java index 662f3d9de05b..bbc10bb921b7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/FrameRunnerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCalculator; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; -import com.hedera.node.app.service.contract.impl.exec.utils.PropagatedCallFailureReference; +import com.hedera.node.app.service.contract.impl.exec.utils.PropagatedCallFailureRef; import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import com.hedera.node.app.service.contract.impl.hevm.HevmPropagatedCallFailure; @@ -94,7 +94,7 @@ class FrameRunnerTest { @Mock private CustomGasCalculator gasCalculator; - private final PropagatedCallFailureReference propagatedCallFailure = new PropagatedCallFailureReference(); + private final PropagatedCallFailureRef propagatedCallFailure = new PropagatedCallFailureRef(); private FrameRunner subject; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java index 945f1daa47d0..9019ed4fb2d7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,10 +42,12 @@ import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.SystemContractOperations; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.EthereumCallDataHydration; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.EvmFrameStateFactory; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.test.TestHelpers; @@ -55,7 +57,6 @@ import com.hedera.node.app.spi.validation.AttributeValidator; import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; -import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import java.time.Instant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -117,16 +118,18 @@ void feesOnlyUpdaterIsProxyUpdater() { @Test void providesExpectedEvmContext() { - final var recordBuilder = mock(DeleteCapableTransactionRecordBuilder.class); + final var recordBuilder = mock(ContractOperationRecordBuilder.class); final var gasCalculator = mock(SystemContractGasCalculator.class); final var blocks = mock(HederaEvmBlocks.class); given(hederaOperations.gasPriceInTinybars()).willReturn(123L); - given(context.recordBuilder(DeleteCapableTransactionRecordBuilder.class)) - .willReturn(recordBuilder); - final var result = provideHederaEvmContext(context, tinybarValues, gasCalculator, hederaOperations, blocks); + given(context.recordBuilder(ContractOperationRecordBuilder.class)).willReturn(recordBuilder); + final var pendingCreationBuilder = new PendingCreationMetadataRef(); + final var result = provideHederaEvmContext( + context, tinybarValues, gasCalculator, hederaOperations, blocks, pendingCreationBuilder); assertSame(blocks, result.blocks()); assertSame(123L, result.gasPrice()); - assertSame(recordBuilder, result.deleteCapableTransactionRecordBuilder()); + assertSame(recordBuilder, result.recordBuilder()); + assertSame(pendingCreationBuilder, result.pendingCreationRecordBuilderReference()); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java index e60265cdeddb..cef86ffc9986 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/CustomGasChargingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,8 @@ void staticCallsDoNotChargeGas() { @Test void zeroPriceGasDoesNoChargingWorkButDoesReturnIntrinsicGas() { - final var context = new HederaEvmContext(0L, false, blocks, tinybarValues, systemContractGasCalculator, null); + final var context = + new HederaEvmContext(0L, false, blocks, tinybarValues, systemContractGasCalculator, null, null); givenWellKnownIntrinsicGasCost(); final var chargingResult = subject.chargeForGas(sender, relayer, context, worldUpdater, wellKnownHapiCall()); assertEquals(0, chargingResult.relayerAllowanceUsed()); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CreateOperationTestBase.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CreateOperationTestBase.java index cde63b11cab2..80000fc25246 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CreateOperationTestBase.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CreateOperationTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import java.util.Deque; import org.apache.tuweni.bytes.Bytes; @@ -27,7 +28,6 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -64,7 +64,7 @@ public class CreateOperationTestBase { protected ProxyWorldUpdater worldUpdater; @Mock - protected MutableAccount receiver; + protected ProxyEvmAccount receiver; @Mock protected Deque stack; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java index 40f2dfc5e727..cca766540abb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/operations/CustomCreate2OperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,6 +135,5 @@ void finalizesHollowAccountWhenPendingContractIsHollowAccountAndLazyCreationEnab childFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); childFrame.notifyCompletion(); verify(frame).pushStackItem(Words.fromAddress(EIP_1014_ADDRESS)); - verify(worldUpdater).finalizeHollowAccount(EIP_1014_ADDRESS, RECIEVER_ADDRESS); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomContractCreationProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomContractCreationProcessorTest.java index 6a5a344e8808..75a0ac901c37 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomContractCreationProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomContractCreationProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,13 @@ import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; import com.hedera.node.app.service.contract.impl.exec.processors.CustomContractCreationProcessor; +import com.hedera.node.app.service.contract.impl.state.ProxyEvmAccount; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.contractvalidation.ContractValidationRule; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; @@ -57,7 +57,7 @@ class CustomContractCreationProcessorTest { private GasCalculator gasCalculator; @Mock - private MutableAccount contract; + private ProxyEvmAccount contract; @Mock private MessageFrame frame; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java index ee86c8fdba25..be4982094dd7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/processors/CustomMessageCallProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.test.exec.processors; +import static com.hedera.hapi.streams.ContractActionType.SYSTEM; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.REMAINING_GAS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.isSameResult; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; @@ -34,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.FullResult; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.PrngSystemContract; +import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.test.TestHelpers; import com.swirlds.config.api.Configuration; @@ -53,7 +55,6 @@ import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; import org.hyperledger.besu.evm.precompile.PrecompiledContract; import org.hyperledger.besu.evm.precompile.PrecompiledContract.PrecompileContractResult; -import org.hyperledger.besu.evm.tracing.OperationTracer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -91,7 +92,7 @@ class CustomMessageCallProcessorTest { private PrecompiledContract nativePrecompile; @Mock - private OperationTracer operationTracer; + private ActionSidecarContentTracer operationTracer; @Mock private PrecompileContractRegistry registry; @@ -137,6 +138,7 @@ void callPrngSystemContractHappyPath() { verify(frame).setOutputData(OUTPUT_DATA); verify(frame).setState(MessageFrame.State.CODE_SUCCESS); verify(frame).setExceptionalHaltReason(Optional.empty()); + verify(operationTracer).tracePrecompileResult(frame, SYSTEM); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index d80f89028276..75ca590f54c4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED; import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations.MISSING_ENTITY_NUMBER; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SELF_DESTRUCT_BENEFICIARIES; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.HAPI_RECORD_BUILDER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_FUNGIBLE_RELATION; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.A_NEW_ACCOUNT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CANONICAL_ALIAS; @@ -296,7 +296,7 @@ void trackDeletionUpdatesMap() { final DeleteCapableTransactionRecordBuilder beneficiaries = mock(DeleteCapableTransactionRecordBuilder.class); given(frame.getMessageFrameStack()).willReturn(stack); stack.push(frame); - given(frame.getContextVariable(SELF_DESTRUCT_BENEFICIARIES)).willReturn(beneficiaries); + given(frame.getContextVariable(HAPI_RECORD_BUILDER_CONTEXT_VARIABLE)).willReturn(beneficiaries); subject.trackSelfDestructBeneficiary(1L, 2L, frame); verify(beneficiaries) .addBeneficiaryForDeletedAccount( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java index a5b34cbf76d2..d2bf3ab9127c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,9 @@ package com.hedera.node.app.service.contract.impl.test.exec.scope; +import static com.hedera.hapi.node.base.HederaFunctionality.ETHEREUM_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED; -import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.*; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthAccountCreationFromHapi; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.synthContractCreationFromParent; @@ -41,13 +42,12 @@ import com.hedera.hapi.node.token.TokenCreateTransactionBody; import com.hedera.hapi.node.transaction.SignedTransaction; import com.hedera.hapi.node.transaction.TransactionBody; -import com.hedera.node.app.service.contract.impl.exec.gas.DispatchType; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.service.contract.impl.exec.scope.HandleHederaOperations; import com.hedera.node.app.service.contract.impl.exec.scope.HederaOperations; +import com.hedera.node.app.service.contract.impl.exec.utils.PendingCreationMetadataRef; import com.hedera.node.app.service.contract.impl.records.ContractCreateRecordBuilder; -import com.hedera.node.app.service.contract.impl.records.ContractDeleteRecordBuilder; import com.hedera.node.app.service.contract.impl.state.WritableContractStateStore; import com.hedera.node.app.service.contract.impl.test.TestHelpers; import com.hedera.node.app.service.token.ReadableAccountStore; @@ -94,9 +94,6 @@ class HandleHederaOperationsTest { @Mock private ContractCreateRecordBuilder contractCreateRecordBuilder; - @Mock - private ContractDeleteRecordBuilder contractDeleteRecordBuilder; - @Mock private TinybarValues tinybarValues; @@ -109,6 +106,9 @@ class HandleHederaOperationsTest { @Mock private ReadableAccountStore readableAccountStore; + @Mock + private PendingCreationMetadataRef pendingCreationMetadataRef; + private HandleHederaOperations subject; @BeforeEach @@ -120,7 +120,8 @@ void setUp() { tinybarValues, gasCalculator, DEFAULT_HEDERA_CONFIG, - HederaFunctionality.CONTRACT_CALL); + HederaFunctionality.CONTRACT_CALL, + pendingCreationMetadataRef); } @Test @@ -232,9 +233,8 @@ void commitIsNoopUntilSavepointExposesIt() { @Test void lazyCreationCostInGasTest() { given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); - given(gasCalculator.gasRequirement(any(), eq(DispatchType.CRYPTO_CREATE), eq(A_NEW_ACCOUNT_ID))) - .willReturn(6L); - given(gasCalculator.gasRequirement(any(), eq(DispatchType.CRYPTO_UPDATE), eq(A_NEW_ACCOUNT_ID))) + given(gasCalculator.topLevelGasRequirement(any(), eq(A_NEW_ACCOUNT_ID))) + .willReturn(6L) .willReturn(5L); assertEquals(11L, subject.lazyCreationCostInGas(NON_SYSTEM_LONG_ZERO_ADDRESS)); } @@ -242,8 +242,6 @@ void lazyCreationCostInGasTest() { @Test void gasPriceInTinybarsDelegates() { given(tinybarValues.topLevelTinybarGasPrice()).willReturn(1234L); - given(context.feeCalculator(SubType.DEFAULT)).willReturn(feeCalculator); - given(feeCalculator.getCongestionMultiplier()).willReturn(1L); assertEquals(1234L, subject.gasPriceInTinybars()); } @@ -330,7 +328,7 @@ void createContractWithNonSelfAdminParentDispatchesAsExpectedThenMarksCreated() eq(A_NEW_ACCOUNT_ID), captor.capture())) .willReturn(contractCreateRecordBuilder); - given(contractCreateRecordBuilder.status()).willReturn(OK); + given(contractCreateRecordBuilder.status()).willReturn(SUCCESS); given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); given(accountStore.getAccountById(NON_SYSTEM_ACCOUNT_ID)).willReturn(parent); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); @@ -378,7 +376,7 @@ void createContractWithSelfAdminParentDispatchesAsExpectedThenMarksCreated() thr eq(A_NEW_ACCOUNT_ID), captor.capture())) .willReturn(contractCreateRecordBuilder); - given(contractCreateRecordBuilder.status()).willReturn(OK); + given(contractCreateRecordBuilder.status()).willReturn(SUCCESS); given(context.readableStore(ReadableAccountStore.class)).willReturn(accountStore); given(accountStore.getAccountById(NON_SYSTEM_ACCOUNT_ID)).willReturn(parent); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); @@ -453,7 +451,7 @@ void createContractWithBodyDispatchesThenMarksAsContract() { eq(A_NEW_ACCOUNT_ID), any(ExternalizedRecordCustomizer.class))) .willReturn(contractCreateRecordBuilder); - given(contractCreateRecordBuilder.status()).willReturn(OK); + given(contractCreateRecordBuilder.status()).willReturn(SUCCESS); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); subject.createContract(666L, someBody, CANONICAL_ALIAS); @@ -478,7 +476,8 @@ void createContractInsideEthereumTransactionWithBodyDispatchesThenMarksAsContrac tinybarValues, gasCalculator, DEFAULT_HEDERA_CONFIG, - HederaFunctionality.ETHEREUM_TRANSACTION); + ETHEREUM_TRANSACTION, + pendingCreationMetadataRef); final var someBody = ContractCreateTransactionBody.newBuilder() .adminKey(AN_ED25519_KEY) .autoRenewAccountId(NON_SYSTEM_ACCOUNT_ID) @@ -500,7 +499,7 @@ void createContractInsideEthereumTransactionWithBodyDispatchesThenMarksAsContrac eq(A_NEW_ACCOUNT_ID), any(ExternalizedRecordCustomizer.class))) .willReturn(contractCreateRecordBuilder); - given(contractCreateRecordBuilder.status()).willReturn(OK); + given(contractCreateRecordBuilder.status()).willReturn(SUCCESS); given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); subject.createContract(666L, someBody, CANONICAL_ALIAS); @@ -539,7 +538,7 @@ void createContractWithFailedDispatchNotImplemented() { .willReturn(contractCreateRecordBuilder); given(contractCreateRecordBuilder.status()).willReturn(MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); - assertThrows(AssertionError.class, () -> subject.createContract(666L, someBody, CANONICAL_ALIAS)); + assertThrows(IllegalStateException.class, () -> subject.createContract(666L, someBody, CANONICAL_ALIAS)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java index f6f81f134890..cd1a14e407d8 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/ActionStackTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,6 +59,7 @@ import static org.mockito.Mockito.verifyNoInteractions; import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.streams.ContractAction; import com.hedera.hapi.streams.ContractActionType; import com.hedera.node.app.service.contract.impl.exec.utils.ActionStack; import com.hedera.node.app.service.contract.impl.exec.utils.ActionWrapper; @@ -133,6 +134,14 @@ void loggingAnomaliesIsNoopWithEmptyStackAndNoInvalid() { verifyNoInteractions(log); } + @Test + void getsActionsFromWrappers() { + allActions.add(new ActionWrapper(ContractAction.DEFAULT)); + allActions.add(new ActionWrapper(ContractAction.DEFAULT)); + final var actions = subject.asContractActions(); + assertEquals(actions.contractActionsOrThrow(), List.of(ContractAction.DEFAULT, ContractAction.DEFAULT)); + } + @Test void logsAndSanitizesInvalidAsExpected() { given(log.atLevel(Level.ERROR)).willReturn(logBuilder); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java index a0a47c9ef625..4f686cc883cd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,8 @@ import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmBlocks; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; -import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionRecordBuilder; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import org.apache.tuweni.bytes.Bytes; import org.hyperledger.besu.datatypes.Hash; @@ -74,7 +74,7 @@ class FrameBuilderTest { @Test void constructsExpectedFrameForCallToExtantContractIncludingOptionalContextVaraiables() { final var transaction = wellKnownHapiCall(); - final var recordBuilder = mock(DeleteCapableTransactionRecordBuilder.class); + final var recordBuilder = mock(ContractOperationRecordBuilder.class); given(worldUpdater.getHederaAccount(NON_SYSTEM_LONG_ZERO_ADDRESS)).willReturn(account); given(account.getEvmCode()).willReturn(CONTRACT_CODE); given(worldUpdater.updater()).willReturn(stackedUpdater); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java index 4ab17cd14982..ea5405e05112 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package com.hedera.node.app.service.contract.impl.test.exec.utils; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; -import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.SELF_DESTRUCT_BENEFICIARIES; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.HAPI_RECORD_BUILDER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TRACKER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.accessTrackerFor; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.configOf; @@ -251,7 +251,8 @@ void checksForBeneficiaryMapAsExpected() { givenNonInitialFrame(); given(frame.getMessageFrameStack()).willReturn(stack); final DeleteCapableTransactionRecordBuilder beneficiaries = mock(DeleteCapableTransactionRecordBuilder.class); - given(initialFrame.getContextVariable(SELF_DESTRUCT_BENEFICIARIES)).willReturn(beneficiaries); + given(initialFrame.getContextVariable(HAPI_RECORD_BUILDER_CONTEXT_VARIABLE)) + .willReturn(beneficiaries); assertSame(beneficiaries, selfDestructBeneficiariesFor(frame)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/PendingCreationRecordBuilderReferenceTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/PendingCreationRecordBuilderReferenceTest.java new file mode 100644 index 000000000000..10a3916f9bd1 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/PendingCreationRecordBuilderReferenceTest.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.exec.utils; + +import static org.junit.jupiter.api.Assertions.*; + +class PendingCreationRecordBuilderReferenceTest {} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallHandlerTest.java index 3e6e064ebcf3..0b2470ba2ceb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,13 +73,17 @@ void delegatesToCreatedComponentAndExposesSuccess() { given(handleContext.recordBuilder(ContractCallRecordBuilder.class)).willReturn(recordBuilder); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome( - expectedResult, SUCCESS_RESULT.finalStatus(), CALLED_CONTRACT_ID, SUCCESS_RESULT.gasPrice()); + expectedResult, + SUCCESS_RESULT.finalStatus(), + CALLED_CONTRACT_ID, + SUCCESS_RESULT.gasPrice(), + null, + null); given(processor.call()).willReturn(expectedOutcome); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); - given(recordBuilder.withTinybarGasFee(SUCCESS_RESULT.gasPrice() * expectedResult.gasUsed())) - .willReturn(recordBuilder); + given(recordBuilder.withCommonFieldsSetFrom(expectedOutcome)).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @@ -91,13 +95,12 @@ void delegatesToCreatedComponentAndThrowsOnFailure() { given(handleContext.recordBuilder(ContractCallRecordBuilder.class)).willReturn(recordBuilder); final var expectedResult = HALT_RESULT.asProtoResultOf(baseProxyWorldUpdater); final var expectedOutcome = - new CallOutcome(expectedResult, HALT_RESULT.finalStatus(), null, HALT_RESULT.gasPrice()); + new CallOutcome(expectedResult, HALT_RESULT.finalStatus(), null, HALT_RESULT.gasPrice(), null, null); given(processor.call()).willReturn(expectedOutcome); given(recordBuilder.contractID(null)).willReturn(recordBuilder); given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); - given(recordBuilder.withTinybarGasFee(HALT_RESULT.gasPrice() * expectedResult.gasUsed())) - .willReturn(recordBuilder); + given(recordBuilder.withCommonFieldsSetFrom(expectedOutcome)).willReturn(recordBuilder); assertFailsWith(INVALID_SIGNATURE, () -> subject.handle(handleContext)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallLocalHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallLocalHandlerTest.java index 62e96a7a919c..11fe0b044788 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallLocalHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCallLocalHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -194,8 +194,8 @@ void findResponsePositiveTest() { .willReturn(component); given(component.contextQueryProcessor()).willReturn(processor); final var expectedResult = SUCCESS_RESULT.asQueryResult(); - final var expectedOutcome = - new CallOutcome(expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice()); + final var expectedOutcome = new CallOutcome( + expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice(), null, null); given(processor.call()).willReturn(expectedOutcome); // given(processor.call()).willReturn(responseHeader); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCreateHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCreateHandlerTest.java index 8e6617d24615..6d7420469a22 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCreateHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractCreateHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,14 +83,13 @@ void delegatesToCreatedComponentAndExposesSuccess() { given(baseProxyWorldUpdater.getCreatedContractIds()).willReturn(List.of(CALLED_CONTRACT_ID)); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(baseProxyWorldUpdater); System.out.println(expectedResult); - final var expectedOutcome = - new CallOutcome(expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice()); + final var expectedOutcome = new CallOutcome( + expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice(), null, null); given(processor.call()).willReturn(expectedOutcome); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); - given(recordBuilder.withTinybarGasFee(SUCCESS_RESULT.gasPrice() * expectedResult.gasUsed())) - .willReturn(recordBuilder); + given(recordBuilder.withCommonFieldsSetFrom(expectedOutcome)).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @@ -103,13 +102,12 @@ void delegatesToCreatedComponentAndThrowsFailure() { given(handleContext.recordBuilder(ContractCreateRecordBuilder.class)).willReturn(recordBuilder); final var expectedResult = HALT_RESULT.asProtoResultOf(baseProxyWorldUpdater); final var expectedOutcome = - new CallOutcome(expectedResult, HALT_RESULT.finalStatus(), null, HALT_RESULT.gasPrice()); + new CallOutcome(expectedResult, HALT_RESULT.finalStatus(), null, HALT_RESULT.gasPrice(), null, null); given(processor.call()).willReturn(expectedOutcome); given(recordBuilder.contractID(null)).willReturn(recordBuilder); given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); - given(recordBuilder.withTinybarGasFee(HALT_RESULT.gasPrice() * expectedResult.gasUsed())) - .willReturn(recordBuilder); + given(recordBuilder.withCommonFieldsSetFrom(expectedOutcome)).willReturn(recordBuilder); assertFailsWith(INVALID_SIGNATURE, () -> subject.handle(handleContext)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java index f5837be31d9d..f135d7f4a74c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/EthereumTransactionHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -156,12 +156,17 @@ void delegatesToCreatedComponentAndExposesEthTxDataCallWithToAddress() { .willReturn(recordBuilder); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, baseProxyWorldUpdater); final var expectedOutcome = new CallOutcome( - expectedResult, SUCCESS_RESULT.finalStatus(), CALLED_CONTRACT_ID, SUCCESS_RESULT.gasPrice()); + expectedResult, + SUCCESS_RESULT.finalStatus(), + CALLED_CONTRACT_ID, + SUCCESS_RESULT.gasPrice(), + null, + null); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCallResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITH_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); - given(recordBuilder.withTinybarGasFee(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); + given(recordBuilder.withCommonFieldsSetFrom(expectedOutcome)).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } @@ -175,14 +180,19 @@ void delegatesToCreatedComponentAndExposesEthTxDataCreateWithoutToAddress() { .willReturn(recordBuilder); given(baseProxyWorldUpdater.getCreatedContractIds()).willReturn(List.of(CALLED_CONTRACT_ID)); final var expectedResult = SUCCESS_RESULT.asProtoResultOf(ETH_DATA_WITHOUT_TO_ADDRESS, baseProxyWorldUpdater); - final var expectedOutcome = - new CallOutcome(expectedResult, SUCCESS_RESULT.finalStatus(), null, SUCCESS_RESULT.gasPrice()); + final var expectedOutcome = new CallOutcome( + expectedResult, + SUCCESS_RESULT.finalStatus(), + CALLED_CONTRACT_ID, + SUCCESS_RESULT.gasPrice(), + null, + null); given(recordBuilder.contractID(CALLED_CONTRACT_ID)).willReturn(recordBuilder); given(recordBuilder.contractCreateResult(expectedResult)).willReturn(recordBuilder); given(recordBuilder.ethereumHash(Bytes.wrap(ETH_DATA_WITHOUT_TO_ADDRESS.getEthereumHash()))) .willReturn(recordBuilder); - given(recordBuilder.withTinybarGasFee(expectedOutcome.tinybarGasCost())).willReturn(recordBuilder); + given(recordBuilder.withCommonFieldsSetFrom(expectedOutcome)).willReturn(recordBuilder); assertDoesNotThrow(() -> subject.handle(handleContext)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java index b6594bfa4bbc..a91e263a1443 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaEvmTransactionResultTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.SELF_DESTRUCT_TO_SELF; +import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.TRACKER_CONTEXT_VARIABLE; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.BESU_LOGS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_EVM_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; @@ -37,6 +38,8 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SOME_STORAGE_ACCESSES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.TWO_STORAGE_ACCESSES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.WEI_NETWORK_GAS_PRICE; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.givenConfigInFrame; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.givenDefaultConfigInFrame; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.wellKnownHapiCall; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.bloomForAll; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.pbjLogsFrom; @@ -45,15 +48,19 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verifyNoInteractions; import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.streams.ContractActions; import com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason; -import com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils; +import com.hedera.node.app.service.contract.impl.hevm.ActionSidecarContentTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import com.hedera.node.app.service.contract.impl.infra.StorageAccessTracker; import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.state.RootProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import java.util.Deque; import java.util.List; import java.util.Optional; @@ -81,12 +88,15 @@ class HederaEvmTransactionResultTest { @Mock private StorageAccessTracker accessTracker; + @Mock + private ActionSidecarContentTracer tracer; + @Test void finalStatusFromHaltUsesCorrespondingStatusIfFromCustom() { - withFrameSetup(); + givenFrameWithoutSidecars(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(SELF_DESTRUCT_TO_SELF)); - final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); assertEquals(OBTAINER_SAME_CONTRACT_ID, subject.finalStatus()); final var protoResult = subject.asProtoResultOf(rootProxyWorldUpdater); assertEquals(SELF_DESTRUCT_TO_SELF.toString(), protoResult.errorMessage()); @@ -94,10 +104,10 @@ void finalStatusFromHaltUsesCorrespondingStatusIfFromCustom() { @Test void finalStatusFromHaltUsesCorrespondingStatusIfFromStandard() { - withFrameSetup(); + givenFrameWithAllSidecarsEnabled(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); - final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); assertEquals(INSUFFICIENT_GAS, subject.finalStatus()); final var protoResult = subject.asProtoResultOf(rootProxyWorldUpdater); assertEquals(ExceptionalHaltReason.INSUFFICIENT_GAS.toString(), protoResult.errorMessage()); @@ -105,20 +115,21 @@ void finalStatusFromHaltUsesCorrespondingStatusIfFromStandard() { @Test void finalStatusFromInsufficientGasHaltImplemented() { - withFrameSetup(); + givenFrameWithoutSidecars(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); - final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); assertEquals(ResponseCodeEnum.INSUFFICIENT_GAS, subject.finalStatus()); + verifyNoInteractions(tracer); } @Test void finalStatusFromMissingAddressHaltImplemented() { - withFrameSetup(); + givenFrameWithAllSidecarsEnabled(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()) .willReturn(Optional.of(CustomExceptionalHaltReason.INVALID_SOLIDITY_ADDRESS)); - final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var subject = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); assertEquals(ResponseCodeEnum.INVALID_SOLIDITY_ADDRESS, subject.finalStatus()); } @@ -150,8 +161,7 @@ void finalStatusFromIpbAbortTranslated() { @Test void givenAccessTrackerIncludesFullContractStorageChangesAndNonNullNoncesOnSuccess() { - withFrameSetup(); - given(frame.getContextVariable(FrameUtils.TRACKER_CONTEXT_VARIABLE)).willReturn(accessTracker); + givenFrameWithAllSidecarsEnabled(); given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); final var pendingWrites = List.of(TWO_STORAGE_ACCESSES); given(proxyWorldUpdater.pendingStorageUpdates()).willReturn(pendingWrites); @@ -164,7 +174,7 @@ void givenAccessTrackerIncludesFullContractStorageChangesAndNonNullNoncesOnSucce given(rootProxyWorldUpdater.getUpdatedContractNonces()).willReturn(NONCES); final var result = HederaEvmTransactionResult.successFrom( - GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame); + GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame, tracer); final var protoResult = result.asProtoResultOf(rootProxyWorldUpdater); assertEquals(GAS_LIMIT / 2, protoResult.gasUsed()); assertEquals(bloomForAll(BESU_LOGS), protoResult.bloom()); @@ -184,8 +194,7 @@ void givenAccessTrackerIncludesFullContractStorageChangesAndNonNullNoncesOnSucce @Test void givenEthTxDataIncludesSpecialFields() { - withFrameSetup(); - given(frame.getContextVariable(FrameUtils.TRACKER_CONTEXT_VARIABLE)).willReturn(accessTracker); + givenFrameWithAllSidecarsEnabled(); given(frame.getWorldUpdater()).willReturn(proxyWorldUpdater); final var pendingWrites = List.of(TWO_STORAGE_ACCESSES); given(proxyWorldUpdater.pendingStorageUpdates()).willReturn(pendingWrites); @@ -198,7 +207,7 @@ void givenEthTxDataIncludesSpecialFields() { given(rootProxyWorldUpdater.getUpdatedContractNonces()).willReturn(NONCES); final var result = HederaEvmTransactionResult.successFrom( - GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame); + GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame, tracer); final var protoResult = result.asProtoResultOf(ETH_DATA_WITH_TO_ADDRESS, rootProxyWorldUpdater); assertEquals(ETH_DATA_WITH_TO_ADDRESS.gasLimit(), protoResult.gas()); assertEquals(ETH_DATA_WITH_TO_ADDRESS.getAmount(), protoResult.amount()); @@ -223,12 +232,11 @@ void givenEthTxDataIncludesSpecialFields() { @Test void givenAccessTrackerIncludesReadStorageAccessesOnlyOnFailure() { - withFrameSetup(); - given(frame.getContextVariable(FrameUtils.TRACKER_CONTEXT_VARIABLE)).willReturn(accessTracker); + givenFrameWithAllSidecarsEnabled(); given(accessTracker.getJustReads()).willReturn(SOME_STORAGE_ACCESSES); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); - final var result = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var result = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); final var expectedChanges = ConversionUtils.asPbjStateChanges(SOME_STORAGE_ACCESSES); assertEquals(expectedChanges, result.stateChanges()); @@ -236,25 +244,26 @@ void givenAccessTrackerIncludesReadStorageAccessesOnlyOnFailure() { @Test void withoutAccessTrackerReturnsNullStateChanges() { - withFrameSetup(); + givenFrameWithoutSidecars(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getOutputData()).willReturn(pbjToTuweniBytes(OUTPUT_DATA)); final var result = HederaEvmTransactionResult.successFrom( - GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame); + GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame, tracer); assertNull(result.stateChanges()); } @Test void QueryResultOnSuccess() { - withFrameSetup(); + givenFrameWithDefaultConfigNoAccessTracker(); + given(tracer.contractActions()).willReturn(new ContractActions(List.of())); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getLogs()).willReturn(BESU_LOGS); given(frame.getOutputData()).willReturn(pbjToTuweniBytes(OUTPUT_DATA)); final var result = HederaEvmTransactionResult.successFrom( - GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame); + GAS_LIMIT / 2, SENDER_ID, CALLED_CONTRACT_ID, CALLED_CONTRACT_EVM_ADDRESS, frame, tracer); final var queryResult = result.asQueryResult(); assertEquals(GAS_LIMIT / 2, queryResult.gasUsed()); assertEquals(bloomForAll(BESU_LOGS), queryResult.bloom()); @@ -267,28 +276,47 @@ void QueryResultOnSuccess() { @Test void QueryResultOnHalt() { - withFrameSetup(); + givenFrameWithoutSidecars(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getExceptionalHaltReason()).willReturn(Optional.of(ExceptionalHaltReason.INVALID_OPERATION)); - final var result = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var result = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); final var protoResult = result.asQueryResult(); assertEquals(ExceptionalHaltReason.INVALID_OPERATION.toString(), protoResult.errorMessage()); } @Test void QueryResultOnRevert() { - withFrameSetup(); + givenFrameWithoutSidecars(); given(frame.getGasPrice()).willReturn(WEI_NETWORK_GAS_PRICE); given(frame.getRevertReason()).willReturn(Optional.of(SOME_REVERT_REASON)); - final var result = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null); + final var result = HederaEvmTransactionResult.failureFrom(GAS_LIMIT / 2, SENDER_ID, frame, null, tracer); final var protoResult = result.asQueryResult(); assertEquals(SOME_REVERT_REASON.toString(), protoResult.errorMessage()); } - private void withFrameSetup() { + private void givenFrameWithDegenerateStack() { given(frame.getMessageFrameStack()).willReturn(stack); given(stack.isEmpty()).willReturn(true); } + + private void givenFrameWithDefaultConfigNoAccessTracker() { + givenDefaultConfigInFrame(frame); + doReturn(null).when(frame).getContextVariable(TRACKER_CONTEXT_VARIABLE); + } + + private void givenFrameWithAllSidecarsEnabled() { + givenDefaultConfigInFrame(frame); + doReturn(accessTracker).when(frame).getContextVariable(TRACKER_CONTEXT_VARIABLE); + } + + private void givenFrameWithoutSidecars() { + givenConfigInFrame( + frame, + HederaTestConfigBuilder.create() + .withValue("contracts.sidecars", "CONTRACT_STATE_CHANGE,CONTRACT_BYTECODE") + .getOrCreateConfig()); + doReturn(null).when(frame).getContextVariable(TRACKER_CONTEXT_VARIABLE); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaWorldUpdaterTest.java index 19d9afbe287b..8a3a40178252 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/hevm/HederaWorldUpdaterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package com.hedera.node.app.service.contract.impl.test.hevm; -import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CALLED_CONTRACT_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EIP_1014_ADDRESS; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; @@ -39,8 +38,7 @@ class HederaWorldUpdaterTest { void delegatesGettingHederaAccount() { final var subject = mock(HederaWorldUpdater.class); doCallRealMethod().when(subject).getHederaAccount(EIP_1014_ADDRESS); - given(subject.getHederaContractId(EIP_1014_ADDRESS)).willReturn(CALLED_CONTRACT_ID); - given(subject.getHederaAccount(CALLED_CONTRACT_ID)).willReturn(account); + given(subject.get(EIP_1014_ADDRESS)).willReturn(account); assertSame(account, subject.getHederaAccount(EIP_1014_ADDRESS)); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java new file mode 100644 index 000000000000..eef75abcc616 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hedera.node.app.service.contract.impl.test.records; + +import static org.junit.jupiter.api.Assertions.*; + +import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.ResponseCodeEnum; +import com.hedera.hapi.node.contract.ContractFunctionResult; +import com.hedera.hapi.streams.ContractAction; +import com.hedera.hapi.streams.ContractActions; +import com.hedera.hapi.streams.ContractBytecode; +import com.hedera.hapi.streams.ContractStateChange; +import com.hedera.hapi.streams.ContractStateChanges; +import com.hedera.node.app.service.contract.impl.exec.CallOutcome; +import com.hedera.node.app.service.contract.impl.records.ContractOperationRecordBuilder; +import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ContractOperationRecordBuilderTest { + @Test + void withGasFeeWorksAsExpected() { + final var subject = new ContractOperationRecordBuilder() { + private long totalFee = 456L; + private ContractActions actions = null; + private ContractStateChanges stateChanges = null; + + @Override + public long transactionFee() { + return totalFee; + } + + @Override + public ContractOperationRecordBuilder transactionFee(final long transactionFee) { + totalFee = transactionFee; + return this; + } + + @NonNull + @Override + public ContractOperationRecordBuilder addContractActions( + @NonNull ContractActions contractActions, boolean isMigration) { + this.actions = contractActions; + return this; + } + + @NonNull + @Override + public ContractOperationRecordBuilder addContractBytecode( + @NonNull ContractBytecode contractBytecode, boolean isMigration) { + return this; + } + + @NonNull + @Override + public ContractOperationRecordBuilder addContractStateChanges( + @NonNull ContractStateChanges contractStateChanges, boolean isMigration) { + stateChanges = contractStateChanges; + return this; + } + + @Override + public int getNumberOfDeletedAccounts() { + return 0; + } + + @Nullable + @Override + public AccountID getDeletedAccountBeneficiaryFor(@NonNull AccountID deletedAccountID) { + return null; + } + + @Override + public void addBeneficiaryForDeletedAccount( + @NonNull AccountID deletedAccountID, @NonNull AccountID beneficiaryForDeletedAccount) { + // No-op + } + + @NonNull + @Override + public ResponseCodeEnum status() { + return ResponseCodeEnum.SUCCESS; + } + + @Override + public SingleTransactionRecordBuilder status(@NonNull ResponseCodeEnum status) { + return this; + } + }; + + final var outcomeWithoutSidecars = new CallOutcome( + ContractFunctionResult.newBuilder().gasUsed(1L).build(), + ResponseCodeEnum.SUCCESS, + ContractID.DEFAULT, + 123L, + null, + null); + final var actions = new ContractActions(List.of(ContractAction.DEFAULT)); + final var stateChanges = new ContractStateChanges(List.of(ContractStateChange.DEFAULT)); + final var outcomeWithSidecars = new CallOutcome( + ContractFunctionResult.newBuilder().gasUsed(1L).build(), + ResponseCodeEnum.SUCCESS, + ContractID.DEFAULT, + 123L, + actions, + stateChanges); + assertSame(subject, subject.withCommonFieldsSetFrom(outcomeWithoutSidecars)); + assertSame(subject, subject.withCommonFieldsSetFrom(outcomeWithSidecars)); + assertEquals(456L + 2 * 123L, subject.transactionFee()); + assertSame(actions, subject.actions); + assertSame(stateChanges, subject.stateChanges); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/GasFeeRecordBuilderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/GasFeeRecordBuilderTest.java deleted file mode 100644 index 8722e628bd97..000000000000 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/GasFeeRecordBuilderTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.hedera.node.app.service.contract.impl.test.records; - -import static org.junit.jupiter.api.Assertions.*; - -import com.hedera.node.app.service.contract.impl.records.GasFeeRecordBuilder; -import org.junit.jupiter.api.Test; - -class GasFeeRecordBuilderTest { - @Test - void withGasFeeWorksAsExpected() { - final var subject = new GasFeeRecordBuilder() { - private long totalFee = 456L; - - @Override - public long transactionFee() { - return totalFee; - } - - @Override - public GasFeeRecordBuilder transactionFee(final long transactionFee) { - totalFee = transactionFee; - return this; - } - }; - - assertSame(subject, subject.withTinybarGasFee(123L)); - assertEquals(123L + 456L, subject.transactionFee()); - } -} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java index b4153de26705..617746dbcc86 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/utils/ConversionUtilsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -204,19 +204,21 @@ void convertsFromStorageAccessesAsExpected() { ContractStateChange.newBuilder() .contractId(ContractID.newBuilder().contractNum(123L)) .storageChanges(new StorageChange( - tuweniToPbjBytes(UInt256.MIN_VALUE), tuweniToPbjBytes(UInt256.MAX_VALUE), null)) + tuweniToPbjBytes(UInt256.MIN_VALUE.trimLeadingZeros()), + tuweniToPbjBytes(UInt256.MAX_VALUE.trimLeadingZeros()), + null)) .build(), ContractStateChange.newBuilder() .contractId(ContractID.newBuilder().contractNum(456L)) .storageChanges( new StorageChange( - tuweniToPbjBytes(UInt256.MAX_VALUE), - tuweniToPbjBytes(UInt256.MIN_VALUE), + tuweniToPbjBytes(UInt256.MAX_VALUE.trimLeadingZeros()), + tuweniToPbjBytes(UInt256.MIN_VALUE.trimLeadingZeros()), null), new StorageChange( - tuweniToPbjBytes(UInt256.ONE), - tuweniToPbjBytes(UInt256.MIN_VALUE), - tuweniToPbjBytes(UInt256.MAX_VALUE))) + tuweniToPbjBytes(UInt256.ONE.trimLeadingZeros()), + tuweniToPbjBytes(UInt256.MIN_VALUE.trimLeadingZeros()), + tuweniToPbjBytes(UInt256.MAX_VALUE.trimLeadingZeros()))) .build()) .build(); final var actualPbj = ConversionUtils.asPbjStateChanges(SOME_STORAGE_ACCESSES); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java index c8edd99e35ad..cbe9610fe04d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BroadcastingRecordStreamListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,12 @@ import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.isRecordFile; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.isSidecarFile; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.File; import java.io.UncheckedIOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -54,42 +56,36 @@ enum FileType { @Override public void onFileCreate(final File file) { - final var newFilePath = file.getPath(); + switch (typeOf(file)) { + case RECORD_STREAM_FILE -> retryExposingVia(this::exposeItems, "record stream", file); + case SIDE_CAR_FILE -> retryExposingVia(this::exposeSidecars, "sidecar", file); + case OTHER -> { + // Nothing to expose + } + } + } - final var fileType = typeOf(file); - switch (fileType) { - case RECORD_STREAM_FILE -> { - var retryCount = 0; - while (true) { - retryCount++; + private void retryExposingVia( + @NonNull final Consumer exposure, @NonNull final String fileType, @NonNull final File f) { + var retryCount = 0; + while (true) { + retryCount++; + try { + exposure.accept(f); + return; + } catch (UncheckedIOException e) { + if (retryCount < 8) { try { - exposeItems(file); + Thread.sleep(500); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); return; - } catch (UncheckedIOException e) { - log.warn( - "Attempt #{} - an error occurred trying to parse" + " recordStream file {} - {}.", - retryCount, - newFilePath, - e); - - if (retryCount < 8) { - try { - Thread.sleep(500); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - return; - } - } else { - log.fatal("Could not read recordStream file {} - {}, exiting now.", newFilePath, e); - throw new IllegalStateException(); - } } + } else { + log.error("Could not expose contents of {} file {}", fileType, f.getAbsolutePath(), e); + throw new IllegalStateException(); } } - case SIDE_CAR_FILE -> exposeSidecars(file); - case OTHER -> { - // Nothing to expose - } } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java index c32b4772e2b3..4fe52c1dee75 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.stream.proto.RecordStreamItem; import com.hedera.services.stream.proto.TransactionSidecarRecord; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.time.Duration; import java.util.Objects; import java.util.function.Function; import org.junit.jupiter.api.Assertions; @@ -73,20 +73,18 @@ public static EventualRecordStreamAssertion eventuallyAssertingNoFailures( return new EventualRecordStreamAssertion(assertionFactory, true); } - public EventualRecordStreamAssertion( - final Function assertionFactory, final Duration timeout) { - super(timeout); - this.assertionFactory = assertionFactory; + public static String recordStreamLocFor(@NonNull final HapiSpec spec) { + Objects.requireNonNull(spec); + return switch (spec.targetNetworkType()) { + case HAPI_TEST_NETWORK -> HAPI_TEST_STREAMS_LOC_TEST_NETWORK; + case CI_DOCKER_NETWORK -> TEST_CONTAINER_NODE0_STREAMS; + case STANDALONE_MONO_NETWORK -> spec.setup().defaultRecordLoc(); + }; } @Override protected boolean submitOp(final HapiSpec spec) throws Throwable { - final var locToUse = - switch (spec.targetNetworkType()) { - case HAPI_TEST_NETWORK -> HAPI_TEST_STREAMS_LOC_TEST_NETWORK; - case CI_DOCKER_NETWORK -> TEST_CONTAINER_NODE0_STREAMS; - case STANDALONE_MONO_NETWORK -> spec.setup().defaultRecordLoc(); - }; + final var locToUse = recordStreamLocFor(spec); final var validatingListener = RECORD_STREAM_ACCESS.getValidatingListener(locToUse); assertion = Objects.requireNonNull(assertionFactory.apply(spec)); unsubscribe = validatingListener.subscribe(new StreamDataListener() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java index d795447183fa..aaa84f2cce5b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,13 @@ package com.hedera.services.bdd.spec.verification.traceability; +import static java.util.Objects.requireNonNull; + import com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils; +import com.hedera.services.stream.proto.ContractActions; import com.hedera.services.stream.proto.SidecarFile; import com.hedera.services.stream.proto.TransactionSidecarRecord; +import edu.umd.cs.findbugs.annotations.NonNull; import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -43,7 +47,7 @@ public SidecarWatcher(final Path recordStreamFolderPath) { private static final Logger log = LogManager.getLogger(SidecarWatcher.class); private static final Pattern SIDECAR_FILE_REGEX = Pattern.compile("\\d{4}-\\d{2}-\\d{2}T\\d{2}_\\d{2}_\\d{2}\\.\\d{9}Z_\\d{2}.rcd"); - private static final int POLLING_INTERVAL_MS = 250; + private static final int POLLING_INTERVAL_MS = 500; private final Queue expectedSidecars = new LinkedBlockingDeque<>(); @@ -64,7 +68,7 @@ public void watch() throws Exception { public void onFileCreate(File file) { final var newFilePath = file.getPath(); if (SIDECAR_FILE_REGEX.matcher(newFilePath).find()) { - log.info("New sidecar file: {}", newFilePath); + log.info("New sidecar file: {}", file.getAbsolutePath()); var retryCount = 0; while (true) { retryCount++; @@ -73,25 +77,15 @@ public void onFileCreate(File file) { onNewSidecarFile(sidecarFile); return; } catch (IOException e) { - // given that there is a slight chance we poll the - // file system at the exact time the file is being created, - // *but not yet finished*, and we try reading it in this - // unfinished state, which will throw an exception, - // we wait 250ms and try reading the file again - // we wait for a maximum of 1s for the file to be finished - log.warn( - "Attempt #{} - an error occurred trying to parse" + " sidecar file {} - {}.", - retryCount, - newFilePath, - e); - if (retryCount < 4) { + // Some number of retries are expected to be necessary due to incomplete files on disk + if (retryCount < 8) { try { Thread.sleep(POLLING_INTERVAL_MS); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } else { - log.fatal("Could not read sidecar file {} - {}, exiting now.", newFilePath, e); + log.error("Could not read sidecar file {}, exiting now.", newFilePath, e); throw new IllegalStateException(); } } @@ -138,20 +132,72 @@ private void onNewSidecarFile(final SidecarFile sidecarFile) { } } - private void assertIncomingSidecar(final TransactionSidecarRecord actualSidecar) { + private void assertIncomingSidecar(final TransactionSidecarRecord actualSidecarRecord) { // there should always be an expected sidecar at this point; // if a NPE is thrown here, the specs have missed a sidecar // and must be updated to account for it final var expectedSidecar = expectedSidecars.poll(); final var expectedSidecarRecord = expectedSidecar.expectedSidecarRecord(); - if (!actualSidecar.equals(expectedSidecarRecord)) { + if (!areEqualUpToIntrinsicGasVariation(expectedSidecarRecord, actualSidecarRecord)) { final var spec = expectedSidecar.spec(); failedSidecars.computeIfAbsent(spec, k -> new ArrayList<>()); - failedSidecars.get(spec).add(new MismatchedSidecar(expectedSidecarRecord, actualSidecar)); + failedSidecars.get(spec).add(new MismatchedSidecar(expectedSidecarRecord, actualSidecarRecord)); + } + } + + private boolean areEqualUpToIntrinsicGasVariation( + @NonNull final TransactionSidecarRecord expected, @NonNull final TransactionSidecarRecord actual) { + requireNonNull(expected, "Expected sidecar"); + requireNonNull(actual, "Actual sidecar"); + if (actual.equals(expected)) { + return true; + } else { + // Depending on the addresses used in TraceabilitySuite, the hard-coded gas values may vary + // slightly from observed results. For example, the actual sidecar may have an intrinsic gas + // cost differing from that of the expected sidecar by a value of 12 * X, where X is the + // difference in the number of zero bytes in the transaction payload used between the actual + // and hard-coded transactions (because the payload includes addresses with different numbers + // of zeros in their hex encoding). So we allow for a variation of up to 32L gas between + // expected and actual. + if (actual.hasActions() && expected.hasActions()) { + final var variedActual = actual.toBuilder() + .setActions(withZeroedGasValues(actual.getActions())) + .build(); + final var variedExpected = expected.toBuilder() + .setActions(withZeroedGasValues(expected.getActions())) + .build(); + if (variedExpected.equals(variedActual)) { + return maxGasDeltaBetween(actual.getActions(), expected.getActions()) <= 32L; + } + } + return false; } } + private long maxGasDeltaBetween(@NonNull final ContractActions a, @NonNull final ContractActions b) { + final var aActions = a.getContractActionsList(); + final var bActions = b.getContractActionsList(); + if (aActions.size() != bActions.size()) { + throw new IllegalArgumentException("Arguments should be equal up to gas usage"); + } + var maxGasDelta = 0L; + for (int i = 0, n = aActions.size(); i < n; i++) { + final var aAction = aActions.get(i); + final var bAction = bActions.get(i); + maxGasDelta = Math.max(maxGasDelta, Math.abs(aAction.getGas() - bAction.getGas())); + } + return maxGasDelta; + } + + private ContractActions withZeroedGasValues(@NonNull final ContractActions actions) { + final var perturbedAction = ContractActions.newBuilder(); + actions.getContractActionsList() + .forEach(action -> perturbedAction.addContractActions( + action.toBuilder().setGas(0L).build())); + return perturbedAction.build(); + } + public void waitUntilFinished() { if (!expectedSidecars.isEmpty()) { log.info("Waiting a maximum of 10 seconds for expected sidecars"); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java index bf93324c98b4..e7f915e3ce40 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/opcodes/Create2OperationSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,6 +61,8 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifHapiTest; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.ifNotHapiTest; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.inParallel; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; @@ -715,13 +717,22 @@ contract, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) .gas(10_000_000L) .sending(tcValue) .via(CREATE_2_TXN)), - captureChildCreate2MetaFor( + // mod-service externalizes internal creations in order of their initiation, + // while mono-service externalizes them in order of their completion + ifHapiTest(captureChildCreate2MetaFor( + 3, + 0, + "Merged deployed contract with hollow account", + CREATE_2_TXN, + mergedMirrorAddr, + mergedAliasAddr)), + ifNotHapiTest(captureChildCreate2MetaFor( 3, 2, "Merged deployed contract with hollow account", CREATE_2_TXN, mergedMirrorAddr, - mergedAliasAddr), + mergedAliasAddr)), withOpContext((spec, opLog) -> { final var opExpectedMergedNonce = getTxnRecord(CREATE_2_TXN) .andAllChildRecords() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java index a628adefcd22..8c5d2f8501f6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/traceability/TraceabilitySuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,8 +47,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingThree; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.streams.assertions.EventualRecordStreamAssertion.recordStreamLocFor; import static com.hedera.services.bdd.suites.contract.Utils.FunctionType; import static com.hedera.services.bdd.suites.contract.Utils.aaWith; import static com.hedera.services.bdd.suites.contract.Utils.asAddress; @@ -80,7 +82,6 @@ import static org.hyperledger.besu.crypto.Hash.keccak256; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.DefaultExceptionalHaltReason.PRECOMPILE_ERROR; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import com.esaulpaugh.headlong.abi.Address; import com.esaulpaugh.headlong.abi.Function; @@ -94,7 +95,6 @@ import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.assertions.StateChange; import com.hedera.services.bdd.spec.assertions.StorageChange; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; @@ -118,27 +118,30 @@ import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TransferList; import com.swirlds.common.utility.CommonUtils; +import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestMethodOrder; @HapiTestSuite +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @Tag(SMART_CONTRACT) public class TraceabilitySuite extends HapiSuite { private static final Logger log = LogManager.getLogger(TraceabilitySuite.class); - private static final String RECORD_STREAM_FOLDER_PATH_PROPERTY_KEY = "recordStream.path"; private static SidecarWatcher sidecarWatcher; private static final ByteString EMPTY = ByteStringUtils.wrapUnsafely(new byte[0]); @@ -173,62 +176,52 @@ public static void main(final String... args) { @SuppressWarnings("java:S5960") @Override public List getSpecsInSuite() { - try { - initialize(); - } catch (final Exception e) { - log.warn("An exception occurred initializing watch service", e); - return List.of(defaultHapiSpec("initialize") - .given() - .when() - .then(assertionsHold((spec, opLog) -> fail("Watch service couldn't be" + " initialized.")))); - } - return Stream.of( - traceabilityE2EScenario1(), - traceabilityE2EScenario2(), - traceabilityE2EScenario3(), - traceabilityE2EScenario4(), - traceabilityE2EScenario5(), - traceabilityE2EScenario6(), - traceabilityE2EScenario7(), - traceabilityE2EScenario8(), - traceabilityE2EScenario9(), - traceabilityE2EScenario10(), - traceabilityE2EScenario11(), - traceabilityE2EScenario12(), - traceabilityE2EScenario13(), - traceabilityE2EScenario14(), - traceabilityE2EScenario15(), - traceabilityE2EScenario16(), - traceabilityE2EScenario17(), - traceabilityE2EScenario18(), - traceabilityE2EScenario19(), - traceabilityE2EScenario20(), - traceabilityE2EScenario21(), - vanillaBytecodeSidecar(), - vanillaBytecodeSidecar2(), - actionsShowPropagatedRevert(), - ethereumLazyCreateExportsExpectedSidecars(), - hollowAccountCreate2MergeExportsExpectedSidecars(), - assertSidecars()) - .toList(); + return List.of( + suiteSetup(), + traceabilityE2EScenario1(), + traceabilityE2EScenario2(), + traceabilityE2EScenario3(), + traceabilityE2EScenario4(), + traceabilityE2EScenario5(), + traceabilityE2EScenario6(), + traceabilityE2EScenario7(), + traceabilityE2EScenario8(), + traceabilityE2EScenario9(), + traceabilityE2EScenario10(), + traceabilityE2EScenario11(), + traceabilityE2EScenario12(), + traceabilityE2EScenario13(), + traceabilityE2EScenario14(), + traceabilityE2EScenario15(), + traceabilityE2EScenario16(), + traceabilityE2EScenario17(), + traceabilityE2EScenario18(), + traceabilityE2EScenario19(), + traceabilityE2EScenario20(), + traceabilityE2EScenario21(), + vanillaBytecodeSidecar(), + vanillaBytecodeSidecar2(), + actionsShowPropagatedRevert(), + ethereumLazyCreateExportsExpectedSidecars(), + hollowAccountCreate2MergeExportsExpectedSidecars(), + assertSidecars()); } @HapiTest - final HapiSpec beforeAll() { - try { - initialize(); - } catch (final Exception e) { - log.warn("An exception occurred initializing watch service", e); - return defaultHapiSpec("initialize") - .given() - .when() - .then(assertionsHold((spec, opLog) -> fail("Watch service couldn't be" + " initialized."))); - } - - return defaultHapiSpec("initialize").given().when().then(); + @Order(1) + final HapiSpec suiteSetup() { + return defaultHapiSpec("suiteSetup") + .given() + .when() + .then( + overridingTwo( + "contracts.throttle.throttleByGas", "false", + "contracts.enforceCreationThrottle", "false"), + withOpContext((spec, opLog) -> initializeWatcherOf(recordStreamLocFor(spec)))); } @HapiTest + @Order(2) final HapiSpec traceabilityE2EScenario1() { return defaultHapiSpec("traceabilityE2EScenario1") .given( @@ -599,6 +592,7 @@ final HapiSpec traceabilityE2EScenario1() { } @HapiTest + @Order(3) final HapiSpec traceabilityE2EScenario2() { return defaultHapiSpec("traceabilityE2EScenario2") .given( @@ -1003,6 +997,7 @@ final HapiSpec traceabilityE2EScenario2() { } @HapiTest + @Order(4) final HapiSpec traceabilityE2EScenario3() { return defaultHapiSpec("traceabilityE2EScenario3") .given( @@ -1411,6 +1406,7 @@ final HapiSpec traceabilityE2EScenario3() { } @HapiTest + @Order(5) final HapiSpec traceabilityE2EScenario4() { return defaultHapiSpec("traceabilityE2EScenario4") .given( @@ -1702,6 +1698,7 @@ final HapiSpec traceabilityE2EScenario4() { } @HapiTest + @Order(6) final HapiSpec traceabilityE2EScenario5() { return defaultHapiSpec("traceabilityE2EScenario5") .given( @@ -2007,6 +2004,7 @@ final HapiSpec traceabilityE2EScenario5() { } @HapiTest + @Order(7) final HapiSpec traceabilityE2EScenario6() { return defaultHapiSpec("traceabilityE2EScenario6") .given( @@ -2342,6 +2340,7 @@ final HapiSpec traceabilityE2EScenario6() { } @HapiTest + @Order(8) final HapiSpec traceabilityE2EScenario7() { return defaultHapiSpec("traceabilityE2EScenario7") .given( @@ -2732,6 +2731,7 @@ final HapiSpec traceabilityE2EScenario7() { } @HapiTest + @Order(9) final HapiSpec traceabilityE2EScenario8() { return defaultHapiSpec("traceabilityE2EScenario8") .given( @@ -3083,6 +3083,7 @@ final HapiSpec traceabilityE2EScenario8() { } @HapiTest + @Order(10) final HapiSpec traceabilityE2EScenario9() { return defaultHapiSpec("traceabilityE2EScenario9") .given( @@ -3389,6 +3390,7 @@ final HapiSpec traceabilityE2EScenario9() { } @HapiTest + @Order(11) final HapiSpec traceabilityE2EScenario10() { return defaultHapiSpec("traceabilityE2EScenario10") .given( @@ -3731,6 +3733,7 @@ final HapiSpec traceabilityE2EScenario10() { } @HapiTest + @Order(12) final HapiSpec traceabilityE2EScenario11() { return defaultHapiSpec("traceabilityE2EScenario11") .given( @@ -4008,6 +4011,7 @@ final HapiSpec traceabilityE2EScenario11() { } @HapiTest + @Order(13) final HapiSpec traceabilityE2EScenario12() { final var contract = "CreateTrivial"; final var scenario12 = "traceabilityE2EScenario12"; @@ -4039,6 +4043,8 @@ final HapiSpec traceabilityE2EScenario12() { expectContractBytecode(TRACEABILITY_TXN, contract)); } + @HapiTest + @Order(14) HapiSpec traceabilityE2EScenario13() { final AtomicReference accountIDAtomicReference = new AtomicReference<>(); return defaultHapiSpec("traceabilityE2EScenario13") @@ -4081,6 +4087,8 @@ HapiSpec traceabilityE2EScenario13() { expectContractBytecodeWithMinimalFieldsSidecarFor(FIRST_CREATE_TXN, PAY_RECEIVABLE_CONTRACT)); } + @HapiTest + @Order(15) final HapiSpec traceabilityE2EScenario14() { return defaultHapiSpec("traceabilityE2EScenario14") .given( @@ -4123,6 +4131,8 @@ final HapiSpec traceabilityE2EScenario14() { })); } + @HapiTest + @Order(16) HapiSpec traceabilityE2EScenario15() { final String GET_BYTECODE = "getBytecode"; final String DEPLOY = "deploy"; @@ -4261,6 +4271,8 @@ HapiSpec traceabilityE2EScenario15() { .then(); } + @HapiTest + @Order(17) HapiSpec traceabilityE2EScenario16() { final AtomicReference vanillaTokenID = new AtomicReference<>(); final String PRECOMPILE_CALLER = "PrecompileCaller"; @@ -4370,6 +4382,7 @@ HapiSpec traceabilityE2EScenario16() { } @HapiTest + @Order(18) final HapiSpec traceabilityE2EScenario17() { return defaultHapiSpec("traceabilityE2EScenario17") .given( @@ -4428,6 +4441,7 @@ final HapiSpec traceabilityE2EScenario17() { } @HapiTest + @Order(19) final HapiSpec traceabilityE2EScenario18() { return defaultHapiSpec("traceabilityE2EScenario18") .given(uploadInitCode(REVERTING_CONTRACT)) @@ -4451,6 +4465,8 @@ final HapiSpec traceabilityE2EScenario18() { FIRST_CREATE_TXN, REVERTING_CONTRACT, BigInteger.valueOf(4))); } + @HapiTest + @Order(20) HapiSpec traceabilityE2EScenario19() { final var RECEIVER = "RECEIVER"; final var hbarsToSend = 1; @@ -4493,6 +4509,8 @@ HapiSpec traceabilityE2EScenario19() { })); } + @HapiTest + @Order(21) final HapiSpec traceabilityE2EScenario20() { return defaultHapiSpec("traceabilityE2EScenario20") .given(uploadInitCode(REVERTING_CONTRACT)) @@ -4518,6 +4536,7 @@ final HapiSpec traceabilityE2EScenario20() { } @HapiTest + @Order(22) final HapiSpec traceabilityE2EScenario21() { return defaultHapiSpec("traceabilityE2EScenario21") .given( @@ -4592,6 +4611,7 @@ final HapiSpec traceabilityE2EScenario21() { } @HapiTest + @Order(23) final HapiSpec vanillaBytecodeSidecar() { final var EMPTY_CONSTRUCTOR_CONTRACT = "EmptyConstructor"; final var vanillaBytecodeSidecar = "vanillaBytecodeSidecar"; @@ -4626,6 +4646,7 @@ final HapiSpec vanillaBytecodeSidecar() { } @HapiTest + @Order(24) final HapiSpec vanillaBytecodeSidecar2() { final var contract = "CreateTrivial"; final String trivialCreate = "vanillaBytecodeSidecar2"; @@ -4657,6 +4678,7 @@ final HapiSpec vanillaBytecodeSidecar2() { } @HapiTest + @Order(25) final HapiSpec actionsShowPropagatedRevert() { final var APPROVE_BY_DELEGATE = "ApproveByDelegateCall"; final var badApproval = "BadApproval"; @@ -4835,6 +4857,8 @@ final HapiSpec actionsShowPropagatedRevert() { })); } + @HapiTest + @Order(26) final HapiSpec ethereumLazyCreateExportsExpectedSidecars() { final var RECIPIENT_KEY = "lazyAccountRecipient"; final var RECIPIENT_KEY2 = "lazyAccountRecipient2"; @@ -4926,6 +4950,8 @@ final HapiSpec ethereumLazyCreateExportsExpectedSidecars() { } @SuppressWarnings("java:S5960") + @HapiTest + @Order(27) final HapiSpec hollowAccountCreate2MergeExportsExpectedSidecars() { final var tcValue = 1_234L; final var create2Factory = "Create2Factory"; @@ -5092,6 +5118,8 @@ create2Factory, GET_BYTECODE, asHeadlongAddress(factoryEvmAddress.get()), salt) } @SuppressWarnings("java:S5960") + @HapiTest + @Order(28) final HapiSpec assertSidecars() { return defaultHapiSpec("assertSidecars") .given( @@ -5201,7 +5229,8 @@ private CustomSpecAssert expectContractBytecodeWithMinimalFieldsSidecarFor( final var txnRecord = getTxnRecord(contractCreateTxn).andAllChildRecords(); final var contractBytecode = getContractBytecode(contractName).saveResultTo(RUNTIME_CODE); allRunFor(spec, txnRecord, contractBytecode); - final var consensusTimestamp = txnRecord.getChildRecord(0).getConsensusTimestamp(); + final var consensusTimestamp = + txnRecord.getFirstNonStakingChildRecord().getConsensusTimestamp(); sidecarWatcher.addExpectedSidecar(new ExpectedSidecar( spec.getName(), TransactionSidecarRecord.newBuilder() @@ -5270,19 +5299,10 @@ private ByteString getInitcode(final String binFileName, final Object... constru return initCode.concat(ByteStringUtils.wrapUnsafely(params.length > 4 ? stripSelector(params) : params)); } - private static void initialize() throws Exception { - final var recordStreamFolderPath = HapiSpec.isRunningInCi() - ? HapiSpec.ciPropOverrides().get(RECORD_STREAM_FOLDER_PATH_PROPERTY_KEY) - : HapiSpecSetup.getDefaultPropertySource().get(RECORD_STREAM_FOLDER_PATH_PROPERTY_KEY); - - { - final var absolutePath = - Paths.get(recordStreamFolderPath).toAbsolutePath().toString(); - log.info("recordStreamFolderPath from config %s: %s (absolute %s)" - .formatted(HapiSpec.isRunningInCi() ? "CI" : "not CI", recordStreamFolderPath, absolutePath)); - } - - sidecarWatcher = new SidecarWatcher(Paths.get(recordStreamFolderPath)); + private static void initializeWatcherOf(@NonNull final String recordStreamLoc) throws Exception { + final var absolutePath = Paths.get(recordStreamLoc).toAbsolutePath(); + log.info("Watching for sidecars at absolute path {}", absolutePath); + sidecarWatcher = new SidecarWatcher(Paths.get(recordStreamLoc)); sidecarWatcher.watch(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java index 27a6f0774175..3ef1da8cf4ce 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/FeatureFlagSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/hedera-node/test-clients/src/main/resource/spec-default.properties b/hedera-node/test-clients/src/main/resource/spec-default.properties index 35b75f2e88dc..19469637b562 100644 --- a/hedera-node/test-clients/src/main/resource/spec-default.properties +++ b/hedera-node/test-clients/src/main/resource/spec-default.properties @@ -36,7 +36,7 @@ default.node=0.0.3 default.node.name=DEFAULT_NODE default.nodePayment.tinyBars=5000 default.payer=0.0.2 -recordStream.path=hedera-node/data/recordstreams/record0.0.3 +recordStream.path=hedera-node/hedera-app/build/node/data/recordStreams/record0.0.3 recordStream.autoSnapshotManagement=false #recordStream.autoSnapshotTarget=MONO_SERVICE recordStream.autoMatchTarget=HAPI_TEST diff --git a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigListUtils.java b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigListUtils.java index 42eaecee323e..7bc058f79688 100644 --- a/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigListUtils.java +++ b/platform-sdk/swirlds-config-impl/src/main/java/com/swirlds/config/impl/internal/ConfigListUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2023 Hedera Hashgraph, LLC + * Copyright (C) 2016-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ static List createList(@Nullable final String rawValue) { if (rawValue == null) { return null; } - if (Objects.equals(Configuration.EMPTY_LIST, rawValue)) { + if (Objects.equals(Configuration.EMPTY_LIST, rawValue) || rawValue.isBlank()) { return List.of(); } return Arrays.stream(rawValue.split(",")).toList(); From 0200208cbe71e0e86535bc288b4097d6f86f5897 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:37:01 -0600 Subject: [PATCH 67/80] feat: add setting for birth round ancient threshold (#10660) Signed-off-by: Cody Littley Co-authored-by: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> --- .../com/swirlds/platform/eventhandling/EventConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java index 5130379b8be1..cf4568ab5a9c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/EventConfig.java @@ -59,6 +59,9 @@ * @param prehandlePoolSize the size of the thread pool used for prehandling transactions * @param useLegacyIntake if true then use the legacy intake monolith, if false then use the new * intake pipeline + * @param useBirthRoundAncientThreshold if true, use birth rounds instead of generations for deciding if an event is + * ancient or not. Once this setting has been enabled on a network, it can + * never be disabled again (migration pathway is one-way). */ @ConfigData("event") public record EventConfig( @@ -73,4 +76,5 @@ public record EventConfig( @ConfigProperty(defaultValue = "/opt/hgcapp/eventsStreams") String eventsLogDir, @ConfigProperty(defaultValue = "true") boolean enableEventStreaming, @ConfigProperty(defaultValue = "8") int prehandlePoolSize, - @ConfigProperty(defaultValue = "false") boolean useLegacyIntake) {} + @ConfigProperty(defaultValue = "false") boolean useLegacyIntake, + @ConfigProperty(defaultValue = "false") boolean useBirthRoundAncientThreshold) {} From 8b105bcfc6671d1b65c53e8814e88e4abb4eeffb Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:42:02 -0600 Subject: [PATCH 68/80] chore: drop chatter (#10670) Signed-off-by: Cody Littley --- .../com/swirlds/platform/SwirldsPlatform.java | 2 - .../platform/gossip/AbstractGossip.java | 28 - .../com/swirlds/platform/gossip/Gossip.java | 6 - .../platform/gossip/GossipFactory.java | 85 +-- .../gossip/chatter/ChatterGossip.java | 641 ++++++++---------- .../gossip/sync/SingleNodeSyncGossip.java | 4 - .../platform/gossip/sync/SyncGossip.java | 1 - 7 files changed, 315 insertions(+), 452 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index 31534c6facb3..f02c07ad7c6a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -874,8 +874,6 @@ public class SwirldsPlatform implements Platform { intakeQueue, swirldStateManager, latestCompleteState, - eventValidator, - eventObserverDispatcher, syncMetrics, eventLinker, platformStatusManager, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java index e33a2d124e6d..2b89aec7cced 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/AbstractGossip.java @@ -16,7 +16,6 @@ package com.swirlds.platform.gossip; -import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.platform.SwirldsPlatform.PLATFORM_THREAD_POOL_NAME; import com.swirlds.base.state.LifecyclePhase; @@ -37,7 +36,6 @@ import com.swirlds.platform.eventhandling.EventConfig; import com.swirlds.platform.gossip.sync.SyncManagerImpl; import com.swirlds.platform.metrics.ReconnectMetrics; -import com.swirlds.platform.metrics.SyncMetrics; import com.swirlds.platform.network.Connection; import com.swirlds.platform.network.ConnectionTracker; import com.swirlds.platform.network.NetworkMetrics; @@ -73,7 +71,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -92,7 +89,6 @@ public abstract class AbstractGossip implements ConnectionTracker, Gossip { protected final NodeId selfId; protected final NetworkTopology topology; protected final NetworkMetrics networkMetrics; - protected final SyncMetrics syncMetrics; protected final ReconnectHelper reconnectHelper; protected final StaticConnectionManagers connectionManagers; protected final FallenBehindManagerImpl fallenBehindManager; @@ -107,11 +103,6 @@ public abstract class AbstractGossip implements ConnectionTracker, Gossip { protected final List thingsToStart = new ArrayList<>(); - /** - * the number of active connections this node has to other nodes - */ - private final AtomicInteger activeConnectionNumber = new AtomicInteger(0); - /** * Builds the gossip engine, depending on which flavor is requested in the configuration. * @@ -125,7 +116,6 @@ public abstract class AbstractGossip implements ConnectionTracker, Gossip { * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable - * @param syncMetrics metrics for sync * @param statusActionSubmitter enables submitting platform status actions * @param loadReconnectState a method that should be called when a state from reconnect is obtained * @param clearAllPipelinesForReconnect this method should be called to clear all pipelines prior to a reconnect @@ -141,7 +131,6 @@ protected AbstractGossip( @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, @NonNull final SignedStateNexus latestCompleteState, - @NonNull final SyncMetrics syncMetrics, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final Consumer loadReconnectState, @NonNull final Runnable clearAllPipelinesForReconnect) { @@ -150,7 +139,6 @@ protected AbstractGossip( this.addressBook = Objects.requireNonNull(addressBook); this.selfId = Objects.requireNonNull(selfId); this.statusActionSubmitter = Objects.requireNonNull(statusActionSubmitter); - this.syncMetrics = Objects.requireNonNull(syncMetrics); Objects.requireNonNull(time); final ThreadConfig threadConfig = platformContext.getConfiguration().getConfigData(ThreadConfig.class); @@ -311,8 +299,6 @@ public boolean hasFallenBehind() { @Override public void newConnectionOpened(@NonNull final Connection sc) { Objects.requireNonNull(sc); - - activeConnectionNumber.getAndIncrement(); networkMetrics.connectionEstablished(sc); } @@ -322,23 +308,9 @@ public void newConnectionOpened(@NonNull final Connection sc) { @Override public void connectionClosed(final boolean outbound, @NonNull final Connection conn) { Objects.requireNonNull(conn); - - final int connectionNumber = activeConnectionNumber.decrementAndGet(); - if (connectionNumber < 0) { - logger.error(EXCEPTION.getMarker(), "activeConnectionNumber is {}, this is a bug!", connectionNumber); - } - networkMetrics.recordDisconnect(conn); } - /** - * {@inheritDoc} - */ - @Override - public int activeConnectionNumber() { - return activeConnectionNumber.get(); - } - /** * Should the network layer do a version check prior to initiating a connection? * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/Gossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/Gossip.java index f1dd505ee8d2..76219e302275 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/Gossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/Gossip.java @@ -52,12 +52,6 @@ public interface Gossip extends Clearable, ConnectionTracker, Lifecycle { @Override void clear(); - /** - * Get the number of active connections. - * @return the number of active connections - */ - int activeConnectionNumber(); - /** * Stop gossiping until {@link #resume()} is called. If called when already paused then this has no effect. */ diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java index e755a7b9bd2a..159475e0652f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/GossipFactory.java @@ -29,15 +29,11 @@ import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.GossipEvent; import com.swirlds.platform.event.linking.EventLinker; -import com.swirlds.platform.event.validation.EventValidator; -import com.swirlds.platform.gossip.chatter.ChatterGossip; -import com.swirlds.platform.gossip.chatter.config.ChatterConfig; import com.swirlds.platform.gossip.shadowgraph.LatestEventTipsetTracker; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; import com.swirlds.platform.gossip.sync.SingleNodeSyncGossip; import com.swirlds.platform.gossip.sync.SyncGossip; import com.swirlds.platform.metrics.SyncMetrics; -import com.swirlds.platform.observers.EventObserverDispatcher; import com.swirlds.platform.recovery.EmergencyRecoveryManager; import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.nexus.SignedStateNexus; @@ -83,8 +79,6 @@ private GossipFactory() {} * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable - * @param eventValidator validates events and passes valid events further along the intake pipeline - * @param eventObserverDispatcher the object used to wire event intake * @param syncMetrics metrics for sync * @param eventLinker links together events, if chatter is enabled will also buffer orphans * @param platformStatusManager the platform status manager @@ -111,8 +105,6 @@ public static Gossip buildGossip( @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, @NonNull final SignedStateNexus latestCompleteState, - @NonNull final EventValidator eventValidator, - @NonNull final EventObserverDispatcher eventObserverDispatcher, @NonNull final SyncMetrics syncMetrics, @NonNull final EventLinker eventLinker, @NonNull final PlatformStatusManager platformStatusManager, @@ -135,8 +127,6 @@ public static Gossip buildGossip( Objects.requireNonNull(intakeQueue); Objects.requireNonNull(swirldStateManager); Objects.requireNonNull(latestCompleteState); - Objects.requireNonNull(eventValidator); - Objects.requireNonNull(eventObserverDispatcher); Objects.requireNonNull(syncMetrics); Objects.requireNonNull(eventLinker); Objects.requireNonNull(platformStatusManager); @@ -144,11 +134,26 @@ public static Gossip buildGossip( Objects.requireNonNull(clearAllPipelinesForReconnect); Objects.requireNonNull(intakeEventCounter); - final ChatterConfig chatterConfig = platformContext.getConfiguration().getConfigData(ChatterConfig.class); - - if (chatterConfig.useChatter()) { - logger.info(STARTUP.getMarker(), "Using ChatterGossip"); - return new ChatterGossip( + if (addressBook.getSize() == 1) { + logger.info(STARTUP.getMarker(), "Using SingleNodeSyncGossip"); + return new SingleNodeSyncGossip( + platformContext, + threadManager, + time, + keysAndCerts, + addressBook, + selfId, + appVersion, + shadowGraph, + intakeQueue, + swirldStateManager, + latestCompleteState, + platformStatusManager, + loadReconnectState, + clearAllPipelinesForReconnect); + } else { + logger.info(STARTUP.getMarker(), "Using SyncGossip"); + return new SyncGossip( platformContext, threadManager, time, @@ -159,65 +164,19 @@ public static Gossip buildGossip( appVersion, epochHash, shadowGraph, + latestEventTipsetTracker, emergencyRecoveryManager, consensusRef, intakeQueue, swirldStateManager, latestCompleteState, - eventValidator, - eventObserverDispatcher, syncMetrics, eventLinker, platformStatusManager, loadReconnectState, clearAllPipelinesForReconnect, + intakeEventCounter, emergencyStateSupplier); - } else { - if (addressBook.getSize() == 1) { - logger.info(STARTUP.getMarker(), "Using SingleNodeSyncGossip"); - return new SingleNodeSyncGossip( - platformContext, - threadManager, - time, - keysAndCerts, - addressBook, - selfId, - appVersion, - shadowGraph, - intakeQueue, - swirldStateManager, - latestCompleteState, - syncMetrics, - platformStatusManager, - loadReconnectState, - clearAllPipelinesForReconnect); - } else { - logger.info(STARTUP.getMarker(), "Using SyncGossip"); - return new SyncGossip( - platformContext, - threadManager, - time, - keysAndCerts, - notificationEngine, - addressBook, - selfId, - appVersion, - epochHash, - shadowGraph, - latestEventTipsetTracker, - emergencyRecoveryManager, - consensusRef, - intakeQueue, - swirldStateManager, - latestCompleteState, - syncMetrics, - eventLinker, - platformStatusManager, - loadReconnectState, - clearAllPipelinesForReconnect, - intakeEventCounter, - emergencyStateSupplier); - } } } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java index d3545968a9ba..81c5e89bb543 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/chatter/ChatterGossip.java @@ -16,356 +16,301 @@ package com.swirlds.platform.gossip.chatter; -import static com.swirlds.logging.legacy.LogMarker.RECONNECT; -import static com.swirlds.platform.SwirldsPlatform.PLATFORM_THREAD_POOL_NAME; - -import com.swirlds.base.state.LifecyclePhase; -import com.swirlds.base.time.Time; -import com.swirlds.base.utility.Pair; -import com.swirlds.common.config.BasicConfig; -import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.crypto.Hash; -import com.swirlds.common.merkle.synchronization.config.ReconnectConfig; -import com.swirlds.common.notification.NotificationEngine; -import com.swirlds.common.platform.NodeId; -import com.swirlds.common.threading.framework.QueueThread; -import com.swirlds.common.threading.framework.StoppableThread; -import com.swirlds.common.threading.framework.config.StoppableThreadConfiguration; -import com.swirlds.common.threading.manager.ThreadManager; -import com.swirlds.common.threading.pool.CachedPoolParallelExecutor; -import com.swirlds.common.threading.pool.ParallelExecutor; -import com.swirlds.common.threading.utility.SequenceCycle; -import com.swirlds.common.utility.Clearable; -import com.swirlds.common.utility.LoggingClearables; -import com.swirlds.platform.Consensus; -import com.swirlds.platform.crypto.KeysAndCerts; -import com.swirlds.platform.event.GossipEvent; -import com.swirlds.platform.event.linking.EventLinker; -import com.swirlds.platform.event.validation.EventValidator; -import com.swirlds.platform.gossip.AbstractGossip; -import com.swirlds.platform.gossip.FallenBehindManagerImpl; -import com.swirlds.platform.gossip.NoOpIntakeEventCounter; -import com.swirlds.platform.gossip.ProtocolConfig; -import com.swirlds.platform.gossip.chatter.communication.ChatterProtocol; -import com.swirlds.platform.gossip.chatter.config.ChatterConfig; -import com.swirlds.platform.gossip.chatter.protocol.ChatterCore; -import com.swirlds.platform.gossip.chatter.protocol.peer.PeerInstance; -import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; -import com.swirlds.platform.gossip.shadowgraph.ShadowGraphSynchronizer; -import com.swirlds.platform.metrics.SyncMetrics; -import com.swirlds.platform.network.communication.NegotiationProtocols; -import com.swirlds.platform.network.communication.NegotiatorThread; -import com.swirlds.platform.network.communication.handshake.HashCompareHandshake; -import com.swirlds.platform.network.communication.handshake.VersionCompareHandshake; -import com.swirlds.platform.observers.EventObserverDispatcher; -import com.swirlds.platform.reconnect.DefaultSignedStateValidator; -import com.swirlds.platform.reconnect.ReconnectController; -import com.swirlds.platform.reconnect.ReconnectProtocol; -import com.swirlds.platform.reconnect.emergency.EmergencyReconnectProtocol; -import com.swirlds.platform.recovery.EmergencyRecoveryManager; -import com.swirlds.platform.state.SwirldStateManager; -import com.swirlds.platform.state.nexus.SignedStateNexus; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.system.status.PlatformStatusManager; -import com.swirlds.platform.threading.PauseAndClear; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; -import java.util.function.Supplier; - /** * Gossip implemented with the chatter protocol. */ -public class ChatterGossip extends AbstractGossip { - - private final ReconnectController reconnectController; - private final ChatterCore chatterCore; - private final List chatterThreads = new LinkedList<>(); - private final SequenceCycle intakeCycle; - - /** - * Holds a list of objects that need to be cleared when {@link #clear()} is called on this object. - */ - private final Clearable clearAllInternalPipelines; - - /** - * Builds the gossip engine that implements the chatter v1 algorithm. - * - * @param platformContext the platform context - * @param threadManager the thread manager - * @param time the wall clock time - * @param keysAndCerts private keys and public certificates - * @param notificationEngine used to send notifications to the app - * @param addressBook the current address book - * @param selfId this node's ID - * @param appVersion the version of the app - * @param epochHash the epoch hash of the initial state - * @param shadowGraph contains non-ancient events - * @param emergencyRecoveryManager handles emergency recovery - * @param consensusRef a pointer to consensus - * @param intakeQueue the event intake queue - * @param swirldStateManager manages the mutable state - * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable - * @param eventValidator validates events and passes valid events along the intake pipeline - * @param eventObserverDispatcher the object used to wire event intake - * @param syncMetrics metrics for sync - * @param eventLinker links together events, if chatter is enabled will also buffer orphans - * @param platformStatusManager the platform status manager - * @param loadReconnectState a method that should be called when a state from reconnect is obtained - * @param clearAllPipelinesForReconnect this method should be called to clear all pipelines prior to a reconnect - * @param emergencyStateSupplier returns the emergency state if available - */ - public ChatterGossip( - @NonNull final PlatformContext platformContext, - @NonNull final ThreadManager threadManager, - @NonNull final Time time, - @NonNull final KeysAndCerts keysAndCerts, - @NonNull final NotificationEngine notificationEngine, - @NonNull final AddressBook addressBook, - @NonNull final NodeId selfId, - @NonNull final SoftwareVersion appVersion, - @Nullable final Hash epochHash, - @NonNull final ShadowGraph shadowGraph, - @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, - @NonNull final AtomicReference consensusRef, - @NonNull final QueueThread intakeQueue, - @NonNull final SwirldStateManager swirldStateManager, - @NonNull final SignedStateNexus latestCompleteState, - @NonNull final EventValidator eventValidator, - @NonNull final EventObserverDispatcher eventObserverDispatcher, - @NonNull final SyncMetrics syncMetrics, - @NonNull final EventLinker eventLinker, - @NonNull final PlatformStatusManager platformStatusManager, - @NonNull final Consumer loadReconnectState, - @NonNull final Runnable clearAllPipelinesForReconnect, - @NonNull final Supplier emergencyStateSupplier) { - super( - platformContext, - threadManager, - time, - keysAndCerts, - addressBook, - selfId, - appVersion, - intakeQueue, - swirldStateManager, - latestCompleteState, - syncMetrics, - platformStatusManager, - loadReconnectState, - clearAllPipelinesForReconnect); - - final BasicConfig basicConfig = platformContext.getConfiguration().getConfigData(BasicConfig.class); - final ChatterConfig chatterConfig = platformContext.getConfiguration().getConfigData(ChatterConfig.class); - final ProtocolConfig protocolConfig = platformContext.getConfiguration().getConfigData(ProtocolConfig.class); - - chatterCore = new ChatterCore<>( - time, - GossipEvent.class, - new PrepareChatterEvent(CryptographyHolder.get()), - chatterConfig, - networkMetrics::recordPingTime, - platformContext.getMetrics()); - - final ReconnectConfig reconnectConfig = - platformContext.getConfiguration().getConfigData(ReconnectConfig.class); - - reconnectController = new ReconnectController(reconnectConfig, threadManager, reconnectHelper, this::resume); - - // first create all instances because of thread safety - for (final NodeId otherId : topology.getNeighbors()) { - chatterCore.newPeerInstance(otherId, intakeQueue::add); - } - - if (emergencyRecoveryManager.isEmergencyStateRequired()) { - // If we still need an emergency recovery state, we need it via emergency reconnect. - // Start the helper first so that it is ready to receive a connection to perform reconnect with when the - // protocol is initiated. - thingsToStart.add(0, reconnectController::start); - } - - intakeCycle = new SequenceCycle<>(eventValidator::validateEvent); - - final ParallelExecutor parallelExecutor = new CachedPoolParallelExecutor(threadManager, "chatter"); - parallelExecutor.start(); - for (final NodeId otherId : topology.getNeighbors()) { - final PeerInstance chatterPeer = chatterCore.getPeerInstance(otherId); - final ParallelExecutor shadowgraphExecutor = new CachedPoolParallelExecutor(threadManager, "node-sync"); - shadowgraphExecutor.start(); - final ShadowGraphSynchronizer chatterSynchronizer = new ShadowGraphSynchronizer( - platformContext, - time, - shadowGraph, - null, - addressBook.getSize(), - syncMetrics, - consensusRef::get, - intakeQueue, - syncManager, - new NoOpIntakeEventCounter(), - shadowgraphExecutor, - false, - () -> { - // start accepting events into the chatter queue - chatterPeer.communicationState().chatterSyncStartingPhase3(); - // wait for any intake event currently being processed to finish - intakeCycle.waitForCurrentSequenceEnd(); - }); - - chatterThreads.add(new StoppableThreadConfiguration<>(threadManager) - .setPriority(Thread.NORM_PRIORITY) - .setNodeId(selfId) - .setComponent(PLATFORM_THREAD_POOL_NAME) - .setOtherNodeId(otherId) - .setThreadName("ChatterReader") - .setHangingThreadPeriod(basicConfig.hangingThreadDuration()) - .setWork(new NegotiatorThread( - connectionManagers.getManager(otherId, topology.shouldConnectTo(otherId)), - chatterConfig.sleepAfterFailedNegotiation(), - List.of( - new VersionCompareHandshake( - appVersion, !protocolConfig.tolerateMismatchedVersion()), - new HashCompareHandshake(epochHash, !protocolConfig.tolerateMismatchedEpochHash())), - new NegotiationProtocols(List.of( - new EmergencyReconnectProtocol( - time, - threadManager, - notificationEngine, - otherId, - emergencyRecoveryManager, - reconnectThrottle, - emergencyStateSupplier, - reconnectConfig.asyncStreamTimeout(), - reconnectMetrics, - reconnectController, - platformStatusManager, - platformContext.getConfiguration()), - new ReconnectProtocol( - threadManager, - otherId, - reconnectThrottle, - () -> latestCompleteState.getState("SwirldsPlatform: ReconnectProtocol"), - reconnectConfig.asyncStreamTimeout(), - reconnectMetrics, - reconnectController, - new DefaultSignedStateValidator(platformContext), - fallenBehindManager, - platformStatusManager, - platformContext.getConfiguration(), - time), - new ChatterSyncProtocol( - platformContext, - otherId, - chatterPeer.communicationState(), - chatterPeer.outputAggregator(), - chatterSynchronizer, - fallenBehindManager), - new ChatterProtocol(chatterPeer, parallelExecutor))))) - .build()); - } - - thingsToStart.add(() -> chatterThreads.forEach(StoppableThread::start)); - - eventObserverDispatcher.addObserver(new ChatterNotifier(selfId, chatterCore)); - - clearAllInternalPipelines = new LoggingClearables( - RECONNECT.getMarker(), - List.of( - Pair.of(intakeQueue, "intakeQueue"), - // eventLinker is not thread safe, so the intake thread needs to be paused while it's being - // cleared - Pair.of(new PauseAndClear(intakeQueue, eventLinker), "eventLinker"), - Pair.of(shadowGraph, "shadowGraph"))); - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean unidirectionalConnectionsEnabled() { - return false; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - protected FallenBehindManagerImpl buildFallenBehindManager() { - return new FallenBehindManagerImpl( - addressBook, - selfId, - topology.getConnectionGraph(), - statusActionSubmitter, - () -> getReconnectController().start(), - platformContext.getConfiguration().getConfigData(ReconnectConfig.class)); - } - - /** - * Get the reconnect controller. This method is needed to break a circular dependency. - */ - public ReconnectController getReconnectController() { - return reconnectController; - } - - /** - * {@inheritDoc} - */ - @Override - public void loadFromSignedState(@NonNull SignedState signedState) { - chatterCore.loadFromSignedState(signedState); - } - - /** - * {@inheritDoc} - */ - @Override - public void stop() { - super.stop(); - chatterCore.stopChatter(); - for (final StoppableThread thread : chatterThreads) { - thread.stop(); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected boolean shouldDoVersionCheck() { - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - clearAllInternalPipelines.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - public void pause() { - throwIfNotInPhase(LifecyclePhase.STARTED); - chatterCore.stopChatter(); - } +public class ChatterGossip /*extends AbstractGossip*/ { - /** - * {@inheritDoc} - */ - @Override - public void resume() { - throwIfNotInPhase(LifecyclePhase.STARTED); - chatterCore.startChatter(); - } + // private final ReconnectController reconnectController; + // private final ChatterCore chatterCore; + // private final List chatterThreads = new LinkedList<>(); + // private final SequenceCycle intakeCycle; + // + // /** + // * Holds a list of objects that need to be cleared when {@link #clear()} is called on this object. + // */ + // private final Clearable clearAllInternalPipelines; + // + // /** + // * Builds the gossip engine that implements the chatter v1 algorithm. + // * + // * @param platformContext the platform context + // * @param threadManager the thread manager + // * @param time the wall clock time + // * @param keysAndCerts private keys and public certificates + // * @param notificationEngine used to send notifications to the app + // * @param addressBook the current address book + // * @param selfId this node's ID + // * @param appVersion the version of the app + // * @param epochHash the epoch hash of the initial state + // * @param shadowGraph contains non-ancient events + // * @param emergencyRecoveryManager handles emergency recovery + // * @param consensusRef a pointer to consensus + // * @param intakeQueue the event intake queue + // * @param swirldStateManager manages the mutable state + // * @param latestCompleteState holds the latest signed state that has enough signatures to be + // verifiable + // * @param eventValidator validates events and passes valid events along the intake pipeline + // * @param eventObserverDispatcher the object used to wire event intake + // * @param syncMetrics metrics for sync + // * @param eventLinker links together events, if chatter is enabled will also buffer orphans + // * @param platformStatusManager the platform status manager + // * @param loadReconnectState a method that should be called when a state from reconnect is obtained + // * @param clearAllPipelinesForReconnect this method should be called to clear all pipelines prior to a + // reconnect + // * @param emergencyStateSupplier returns the emergency state if available + // */ + // public ChatterGossip( + // @NonNull final PlatformContext platformContext, + // @NonNull final ThreadManager threadManager, + // @NonNull final Time time, + // @NonNull final KeysAndCerts keysAndCerts, + // @NonNull final NotificationEngine notificationEngine, + // @NonNull final AddressBook addressBook, + // @NonNull final NodeId selfId, + // @NonNull final SoftwareVersion appVersion, + // @Nullable final Hash epochHash, + // @NonNull final ShadowGraph shadowGraph, + // @NonNull final EmergencyRecoveryManager emergencyRecoveryManager, + // @NonNull final AtomicReference consensusRef, + // @NonNull final QueueThread intakeQueue, + // @NonNull final SwirldStateManager swirldStateManager, + // @NonNull final SignedStateNexus latestCompleteState, + // @NonNull final EventValidator eventValidator, + // @NonNull final EventObserverDispatcher eventObserverDispatcher, + // @NonNull final SyncMetrics syncMetrics, + // @NonNull final EventLinker eventLinker, + // @NonNull final PlatformStatusManager platformStatusManager, + // @NonNull final Consumer loadReconnectState, + // @NonNull final Runnable clearAllPipelinesForReconnect, + // @NonNull final Supplier emergencyStateSupplier) { + // super( + // platformContext, + // threadManager, + // time, + // keysAndCerts, + // addressBook, + // selfId, + // appVersion, + // intakeQueue, + // swirldStateManager, + // latestCompleteState, + // syncMetrics, + // platformStatusManager, + // loadReconnectState, + // clearAllPipelinesForReconnect); + // + // final BasicConfig basicConfig = platformContext.getConfiguration().getConfigData(BasicConfig.class); + // final ChatterConfig chatterConfig = platformContext.getConfiguration().getConfigData(ChatterConfig.class); + // final ProtocolConfig protocolConfig = + // platformContext.getConfiguration().getConfigData(ProtocolConfig.class); + // + // chatterCore = new ChatterCore<>( + // time, + // GossipEvent.class, + // new PrepareChatterEvent(CryptographyHolder.get()), + // chatterConfig, + // networkMetrics::recordPingTime, + // platformContext.getMetrics()); + // + // final ReconnectConfig reconnectConfig = + // platformContext.getConfiguration().getConfigData(ReconnectConfig.class); + // + // reconnectController = new ReconnectController(reconnectConfig, threadManager, reconnectHelper, + // this::resume); + // + // // first create all instances because of thread safety + // for (final NodeId otherId : topology.getNeighbors()) { + // chatterCore.newPeerInstance(otherId, intakeQueue::add); + // } + // + // if (emergencyRecoveryManager.isEmergencyStateRequired()) { + // // If we still need an emergency recovery state, we need it via emergency reconnect. + // // Start the helper first so that it is ready to receive a connection to perform reconnect with when + // the + // // protocol is initiated. + // thingsToStart.add(0, reconnectController::start); + // } + // + // intakeCycle = new SequenceCycle<>(eventValidator::validateEvent); + // + // final ParallelExecutor parallelExecutor = new CachedPoolParallelExecutor(threadManager, "chatter"); + // parallelExecutor.start(); + // for (final NodeId otherId : topology.getNeighbors()) { + // final PeerInstance chatterPeer = chatterCore.getPeerInstance(otherId); + // final ParallelExecutor shadowgraphExecutor = new CachedPoolParallelExecutor(threadManager, + // "node-sync"); + // shadowgraphExecutor.start(); + // final ShadowGraphSynchronizer chatterSynchronizer = new ShadowGraphSynchronizer( + // platformContext, + // time, + // shadowGraph, + // null, + // addressBook.getSize(), + // syncMetrics, + // consensusRef::get, + // intakeQueue, + // syncManager, + // new NoOpIntakeEventCounter(), + // shadowgraphExecutor, + // false, + // () -> { + // // start accepting events into the chatter queue + // chatterPeer.communicationState().chatterSyncStartingPhase3(); + // // wait for any intake event currently being processed to finish + // intakeCycle.waitForCurrentSequenceEnd(); + // }); + // + // chatterThreads.add(new StoppableThreadConfiguration<>(threadManager) + // .setPriority(Thread.NORM_PRIORITY) + // .setNodeId(selfId) + // .setComponent(PLATFORM_THREAD_POOL_NAME) + // .setOtherNodeId(otherId) + // .setThreadName("ChatterReader") + // .setHangingThreadPeriod(basicConfig.hangingThreadDuration()) + // .setWork(new NegotiatorThread( + // connectionManagers.getManager(otherId, topology.shouldConnectTo(otherId)), + // chatterConfig.sleepAfterFailedNegotiation(), + // List.of( + // new VersionCompareHandshake( + // appVersion, !protocolConfig.tolerateMismatchedVersion()), + // new HashCompareHandshake(epochHash, + // !protocolConfig.tolerateMismatchedEpochHash())), + // new NegotiationProtocols(List.of( + // new EmergencyReconnectProtocol( + // time, + // threadManager, + // notificationEngine, + // otherId, + // emergencyRecoveryManager, + // reconnectThrottle, + // emergencyStateSupplier, + // reconnectConfig.asyncStreamTimeout(), + // reconnectMetrics, + // reconnectController, + // platformStatusManager, + // platformContext.getConfiguration()), + // new ReconnectProtocol( + // threadManager, + // otherId, + // reconnectThrottle, + // () -> latestCompleteState.getState("SwirldsPlatform: + // ReconnectProtocol"), + // reconnectConfig.asyncStreamTimeout(), + // reconnectMetrics, + // reconnectController, + // new DefaultSignedStateValidator(platformContext), + // fallenBehindManager, + // platformStatusManager, + // platformContext.getConfiguration(), + // time), + // new ChatterSyncProtocol( + // platformContext, + // otherId, + // chatterPeer.communicationState(), + // chatterPeer.outputAggregator(), + // chatterSynchronizer, + // fallenBehindManager), + // new ChatterProtocol(chatterPeer, parallelExecutor))))) + // .build()); + // } + // + // thingsToStart.add(() -> chatterThreads.forEach(StoppableThread::start)); + // + // eventObserverDispatcher.addObserver(new ChatterNotifier(selfId, chatterCore)); + // + // clearAllInternalPipelines = new LoggingClearables( + // RECONNECT.getMarker(), + // List.of( + // Pair.of(intakeQueue, "intakeQueue"), + // // eventLinker is not thread safe, so the intake thread needs to be paused while it's + // being + // // cleared + // Pair.of(new PauseAndClear(intakeQueue, eventLinker), "eventLinker"), + // Pair.of(shadowGraph, "shadowGraph"))); + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // protected boolean unidirectionalConnectionsEnabled() { + // return false; + // } + // + // /** + // * {@inheritDoc} + // */ + // @NonNull + // @Override + // protected FallenBehindManagerImpl buildFallenBehindManager() { + // return new FallenBehindManagerImpl( + // addressBook, + // selfId, + // topology.getConnectionGraph(), + // statusActionSubmitter, + // () -> getReconnectController().start(), + // platformContext.getConfiguration().getConfigData(ReconnectConfig.class)); + // } + // + // /** + // * Get the reconnect controller. This method is needed to break a circular dependency. + // */ + // public ReconnectController getReconnectController() { + // return reconnectController; + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // public void loadFromSignedState(@NonNull SignedState signedState) { + // chatterCore.loadFromSignedState(signedState); + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // public void stop() { + // super.stop(); + // chatterCore.stopChatter(); + // for (final StoppableThread thread : chatterThreads) { + // thread.stop(); + // } + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // protected boolean shouldDoVersionCheck() { + // return false; + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // public void clear() { + // clearAllInternalPipelines.clear(); + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // public void pause() { + // throwIfNotInPhase(LifecyclePhase.STARTED); + // chatterCore.stopChatter(); + // } + // + // /** + // * {@inheritDoc} + // */ + // @Override + // public void resume() { + // throwIfNotInPhase(LifecyclePhase.STARTED); + // chatterCore.startChatter(); + // } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java index 903bca7f4e88..14cd0d0ce101 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SingleNodeSyncGossip.java @@ -33,7 +33,6 @@ import com.swirlds.platform.gossip.AbstractGossip; import com.swirlds.platform.gossip.FallenBehindManagerImpl; import com.swirlds.platform.gossip.shadowgraph.ShadowGraph; -import com.swirlds.platform.metrics.SyncMetrics; import com.swirlds.platform.state.SwirldStateManager; import com.swirlds.platform.state.nexus.SignedStateNexus; import com.swirlds.platform.state.signed.SignedState; @@ -72,7 +71,6 @@ public class SingleNodeSyncGossip extends AbstractGossip { * @param intakeQueue the event intake queue * @param swirldStateManager manages the mutable state * @param latestCompleteState holds the latest signed state that has enough signatures to be verifiable - * @param syncMetrics metrics for sync * @param statusActionSubmitter enables submitting platform status actions * @param loadReconnectState a method that should be called when a state from reconnect is obtained * @param clearAllPipelinesForReconnect this method should be called to clear all pipelines prior to a reconnect @@ -89,7 +87,6 @@ public SingleNodeSyncGossip( @NonNull final QueueThread intakeQueue, @NonNull final SwirldStateManager swirldStateManager, @NonNull final SignedStateNexus latestCompleteState, - @NonNull final SyncMetrics syncMetrics, @NonNull final StatusActionSubmitter statusActionSubmitter, @NonNull final Consumer loadReconnectState, @NonNull final Runnable clearAllPipelinesForReconnect) { @@ -105,7 +102,6 @@ public SingleNodeSyncGossip( intakeQueue, swirldStateManager, latestCompleteState, - syncMetrics, statusActionSubmitter, loadReconnectState, clearAllPipelinesForReconnect); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java index 1e0f170bc189..2d4b102711f9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/gossip/sync/SyncGossip.java @@ -170,7 +170,6 @@ public SyncGossip( intakeQueue, swirldStateManager, latestCompleteState, - syncMetrics, platformStatusManager, loadReconnectState, clearAllPipelinesForReconnect); From 83c922be9e8bda31d8ea8faed4d7b9b988c36fe4 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 2 Jan 2024 10:43:50 -0600 Subject: [PATCH 69/80] chore: remove state info (#10685) Signed-off-by: Cody Littley --- .../com/swirlds/platform/state/StateInfo.java | 128 ------------------ 1 file changed, 128 deletions(-) delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/StateInfo.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/StateInfo.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/StateInfo.java deleted file mode 100644 index 80f84b0b1789..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/StateInfo.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.swirlds.platform.state; - -import com.swirlds.common.Releasable; -import com.swirlds.platform.internal.EventImpl; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.UnaryOperator; - -/** - * Holds a {@link State} and the last consensus event applied to it. - */ -public class StateInfo implements Releasable { - /** the SwirldState root that this object describes */ - private final AtomicReference state = new AtomicReference<>(); - - /** - * lastCons is a consensus event whose transactions have already been handled by state, as have all - * consensus events before it in the consensus order. - */ - private volatile EventImpl lastCons; - - /** - * pass in everything that is stored in the StateInfo - * - * @param state - * the state to feed transactions to - * @param lastCons - * the last consensus event, in consensus order - */ - public StateInfo(final State state, final EventImpl lastCons) { - this.initializeState(state); - this.setLastCons(lastCons); - } - - private StateInfo(final StateInfo stateToCopy) { - this.initializeState(stateToCopy.getState().copy()); - this.setLastCons(stateToCopy.getLastCons()); - } - - /** - * make a copy of this StateInfo, where it also makes a new copy of the state - * - * @return a copy of this StateInfo - */ - public StateInfo copy() { - return new StateInfo(this); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean release() { - return state.get().release(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isDestroyed() { - return state.get().isDestroyed(); - } - - /** - * Returns the current state. - */ - public State getState() { - return state.get(); - } - - private void initializeState(final State newState) { - final State currState = state.get(); - if (currState != null) { - currState.release(); - } - newState.reserve(); - this.state.set(newState); - } - - /** - * Sets the current state. Should only be invoked after a fast copy has been made. - * - * @param state - * the new state - */ - protected void setState(final State state) { - this.state.set(state); - } - - /** - * Returns the last consensus event applied to this state. - */ - public EventImpl getLastCons() { - return lastCons; - } - - public void setLastCons(final EventImpl lastCons) { - this.lastCons = lastCons; - } - - /** - * Updates the state object using the given {@code updateFunction}. This method uses {@link - * AtomicReference#getAndUpdate(UnaryOperator)} to update the state, so the update function must not have any side - * effects. - * - * @param updateFunction - * the side effect free update function to apply to the state - */ - protected void updateState(final UnaryOperator updateFunction) { - state.getAndUpdate(updateFunction); - } -} From 3d05607af345c4ddb16cb868eeed1552beec51c1 Mon Sep 17 00:00:00 2001 From: Stoyan Panayotov Date: Tue, 2 Jan 2024 19:18:05 +0200 Subject: [PATCH 70/80] chore: Rename contract causing services regression due to long name (#10700) Signed-off-by: Stoyan Panayotov --- .../services/bdd/suites/leaky/LeakyContractTestsSuite.java | 4 ++-- .../Create2SelfDestructContract.bin} | 0 .../Create2SelfDestructContract.json} | 0 .../Create2SelfDestructContract.sol} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename hedera-node/test-clients/src/main/resource/contract/contracts/{Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin => Create2SelfDestructContract/Create2SelfDestructContract.bin} (100%) rename hedera-node/test-clients/src/main/resource/contract/contracts/{Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json => Create2SelfDestructContract/Create2SelfDestructContract.json} (100%) rename hedera-node/test-clients/src/main/resource/contract/contracts/{Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol => Create2SelfDestructContract/Create2SelfDestructContract.sol} (100%) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java index c633ba699365..f2565407c820 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyContractTestsSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -332,7 +332,7 @@ public List getSpecsInSuite() { @HapiTest final HapiSpec canMergeCreate2ChildWithHollowAccountAndSelfDestructInConstructor() { final var tcValue = 1_234L; - final var contract = "Create2FactoryWithSelfDestructingContract"; + final var contract = "Create2SelfDestructContract"; final var creation = CREATION; final var salt = BigInteger.valueOf(42); final var adminKey = ADMIN_KEY; diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2SelfDestructContract/Create2SelfDestructContract.bin similarity index 100% rename from hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.bin rename to hedera-node/test-clients/src/main/resource/contract/contracts/Create2SelfDestructContract/Create2SelfDestructContract.bin diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2SelfDestructContract/Create2SelfDestructContract.json similarity index 100% rename from hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.json rename to hedera-node/test-clients/src/main/resource/contract/contracts/Create2SelfDestructContract/Create2SelfDestructContract.json diff --git a/hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol b/hedera-node/test-clients/src/main/resource/contract/contracts/Create2SelfDestructContract/Create2SelfDestructContract.sol similarity index 100% rename from hedera-node/test-clients/src/main/resource/contract/contracts/Create2FactoryWithSelfDestructingContract/Create2FactoryWithSelfDestructingContract.sol rename to hedera-node/test-clients/src/main/resource/contract/contracts/Create2SelfDestructContract/Create2SelfDestructContract.sol From e3434d1f9229e8141bdf8c84c70961bbecd1c721 Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 2 Jan 2024 11:39:21 -0600 Subject: [PATCH 71/80] fix: state leak (#10690) Signed-off-by: Cody Littley --- .../common/merkle/copy/MerkleInitialize.java | 7 ++- .../platform/state/SwirldStateManager.java | 11 ++-- .../test/merkle/MerkleSerializationTests.java | 55 ++++++++++++++++--- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java index e0d84f23b79c..cf1d6726e2aa 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java @@ -82,7 +82,12 @@ public static MerkleNode initializeAndMigrateTreeAfterDeserialization( final int deserializationVersion = Objects.requireNonNull( deserializationVersions.get(root.getClassId()), "class not discovered during deserialization"); - return root.migrate(deserializationVersion); + final MerkleNode migratedRoot = root.migrate(deserializationVersion); + if (migratedRoot != root) { + root.release(); + } + + return migratedRoot; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index 6f8260f40440..4477012380e3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -151,11 +151,14 @@ public void prehandleApplicationTransactions(final EventImpl event) { while (!immutableState.tryReserve()) { immutableState = latestImmutableState.get(); } - transactionHandler.preHandle(event, immutableState.getSwirldState()); - event.getBaseEvent().signalPrehandleCompletion(); - immutableState.release(); + try { + transactionHandler.preHandle(event, immutableState.getSwirldState()); + } finally { + event.getBaseEvent().signalPrehandleCompletion(); + immutableState.release(); - stats.preHandleTime(startTime, System.nanoTime()); + stats.preHandleTime(startTime, System.nanoTime()); + } } /** diff --git a/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java b/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java index 8ce0647079b3..78ca867a0764 100644 --- a/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java +++ b/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java @@ -18,11 +18,13 @@ import static com.swirlds.common.io.utility.FileUtils.deleteDirectory; import static com.swirlds.common.test.merkle.util.MerkleTestUtils.areTreesEqual; +import static com.swirlds.common.test.merkle.util.MerkleTestUtils.buildLessSimpleTree; import static com.swirlds.common.test.merkle.util.MerkleTestUtils.buildLessSimpleTreeExtended; import static com.swirlds.common.test.merkle.util.MerkleTestUtils.isFullyInitialized; import static com.swirlds.test.framework.ResourceLoader.getFile; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -61,6 +63,7 @@ import java.nio.file.Path; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -219,22 +222,22 @@ void deserializeInvalidTrees() throws IOException { final List trees = new LinkedList<>(); // Too low of a version on a leaf - DummyMerkleInternal root = MerkleTestUtils.buildLessSimpleTree(); + DummyMerkleInternal root = buildLessSimpleTree(); ((DummyMerkleLeaf) root.asInternal().getChild(0)).setVersion(0); trees.add(root); // Too high of a version on a leaf - root = MerkleTestUtils.buildLessSimpleTree(); + root = buildLessSimpleTree(); ((DummyMerkleLeaf) root.asInternal().getChild(0)).setVersion(1234); trees.add(root); // Too low of a version on an internal node - root = MerkleTestUtils.buildLessSimpleTree(); + root = buildLessSimpleTree(); root.setVersion(0); trees.add(root); // Too high of a version on an internal node - root = MerkleTestUtils.buildLessSimpleTree(); + root = buildLessSimpleTree(); root.setVersion(1234); trees.add(root); @@ -245,9 +248,9 @@ void deserializeInvalidTrees() throws IOException { /** * This test asserts that the hash of a tree does not change due to future code changes. - * - * Although there is no contractual requirement not to change the hash between versions, - * we should at least be aware if a change occurs. + *

    + * Although there is no contractual requirement not to change the hash between versions, we should at least be aware + * if a change occurs. */ @Test @Tag(TestTypeTags.FUNCTIONAL) @@ -259,7 +262,7 @@ void testHashFromFile() throws IOException { new DataInputStream(ResourceLoader.loadFileAsStream("merkle/hashed-tree-merkle-v1.dat")); final Hash oldHash = new Hash(dataStream.readAllBytes(), DigestType.SHA_384); - final MerkleNode tree = MerkleTestUtils.buildLessSimpleTree(); + final MerkleNode tree = buildLessSimpleTree(); assertEquals( oldHash, @@ -397,6 +400,42 @@ void nodeMigrationTest() throws IOException { DummyMerkleInternal.resetMigrationMapper(); } + @Test + void migrateRootTest() throws IOException { + + // In this test, we will attempt to swap out originalRoot with newRoot + + final MerkleNode originalRoot = buildLessSimpleTreeExtended(); + final MerkleNode newRoot = buildLessSimpleTree(); + + final AtomicReference deserializedRootBeforeMigration = new AtomicReference<>(); + DummyMerkleInternal.setMigrationMapper((node, version) -> { + if (node.getDepth() == 0) { + assertNull(deserializedRootBeforeMigration.get(), "deserialized root should not have been set yet"); + deserializedRootBeforeMigration.set(node); + return newRoot; + } + return node; + }); + + final ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + final MerkleDataOutputStream out = new MerkleDataOutputStream(byteOut); + + out.writeMerkleTree(testDirectory, originalRoot); + out.flush(); + + final MerkleDataInputStream in = + new DebuggableMerkleDataInputStream(new ByteArrayInputStream(byteOut.toByteArray())); + + final MerkleInternal deserializedRoot = in.readMerkleTree(testDirectory, Integer.MAX_VALUE); + + assertTrue(areTreesEqual(newRoot, deserializedRoot), "deserialized tree should match new root"); + assertNotNull(deserializedRootBeforeMigration.get(), "deserialized root should have been set"); + assertTrue(deserializedRootBeforeMigration.get().isDestroyed(), "deserialized root should have been destroyed"); + + DummyMerkleInternal.resetMigrationMapper(); + } + /** * Disabled on Windows because it does not support changing a file or directory's readability and write-ability. * From e50843cf1ca4f412769b9553346c09189069b59c Mon Sep 17 00:00:00 2001 From: Cody Littley <56973212+cody-littley@users.noreply.github.com> Date: Tue, 2 Jan 2024 13:06:07 -0600 Subject: [PATCH 72/80] fix: state leak during migration (#10706) Signed-off-by: Cody Littley --- .../java/com/swirlds/common/merkle/copy/MerkleInitialize.java | 2 +- .../java/com/swirlds/platform/state/SwirldStateManager.java | 2 +- .../swirlds/common/test/merkle/MerkleSerializationTests.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java index cf1d6726e2aa..26e3bc611c73 100644 --- a/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java +++ b/platform-sdk/swirlds-common/src/main/java/com/swirlds/common/merkle/copy/MerkleInitialize.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index 4477012380e3..ff0e9fbc0379 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2023 Hedera Hashgraph, LLC + * Copyright (C) 2016-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java b/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java index 78ca867a0764..44946db6a85e 100644 --- a/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java +++ b/platform-sdk/swirlds-unit-tests/common/swirlds-common-test/src/test/java/com/swirlds/common/test/merkle/MerkleSerializationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From fef4831c711af671ea54fab150ea630ecb71ef6b Mon Sep 17 00:00:00 2001 From: Austin Littley <102969658+alittley@users.noreply.github.com> Date: Tue, 2 Jan 2024 16:58:45 -0500 Subject: [PATCH 73/80] fix: Mark prehandle as complete in legacy intake pipeline (#10711) Signed-off-by: Austin Littley --- .../java/com/swirlds/platform/components/EventIntake.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java index 42197fc3674d..01be38e274fe 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/components/EventIntake.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -238,7 +238,10 @@ public void addEvent(final EventImpl event) { */ @NonNull private Runnable buildPrehandleTask(@NonNull final EventImpl event) { - return () -> prehandleEvent.accept(event); + return () -> { + prehandleEvent.accept(event); + event.getBaseEvent().signalPrehandleCompletion(); + }; } /** From 9a8ddf72f2b8c9679831bb08022b77ab781a351f Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Wed, 3 Jan 2024 01:23:41 -0600 Subject: [PATCH 74/80] chore: fix mutability exception in pre-handle; stabilize CI (#10716) Signed-off-by: Michael Tinker --- .../state/recordcache/RecordCacheImpl.java | 5 +- .../services/bdd/spec/utilops/UtilVerbs.java | 42 +++++++++- .../crypto/AutoAccountCreationSuite.java | 63 ++++++--------- .../HollowAccountFinalizationSuite.java | 77 +++++++++++-------- 4 files changed, 110 insertions(+), 77 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java index 32d67f653e33..705331732b45 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/recordcache/RecordCacheImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,6 @@ import com.hedera.hapi.node.transaction.TransactionRecord; import com.hedera.node.app.spi.state.CommittableWritableStates; import com.hedera.node.app.spi.state.ReadableQueueState; -import com.hedera.node.app.spi.state.ReadableStates; import com.hedera.node.app.spi.state.WritableQueueState; import com.hedera.node.app.spi.state.WritableStates; import com.hedera.node.app.spi.validation.TruePredicate; @@ -360,7 +359,7 @@ private WritableStates getWritableState() { /** Utility method that get the readable queue from the working state */ private ReadableQueueState getReadableQueue() { - final ReadableStates states = getWritableState(); + final var states = requireNonNull(workingStateAccessor.getHederaState()).createReadableStates(NAME); return states.getQueue(TXN_RECORD_QUEUE); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java index 376d662d5475..ff66aae2c3a0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FEE_SCHEDULE_FILE_PART_UPLOADED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.PLATFORM_TRANSACTION_NOT_CREATED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION; @@ -83,6 +84,7 @@ import com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts; import com.hedera.services.bdd.spec.infrastructure.OpProvider; import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; +import com.hedera.services.bdd.spec.transactions.HapiTxnOp; import com.hedera.services.bdd.spec.transactions.consensus.HapiMessageSubmit; import com.hedera.services.bdd.spec.transactions.contract.HapiContractCall; import com.hedera.services.bdd.spec.transactions.contract.HapiEthereumCall; @@ -1447,6 +1449,44 @@ public static TransferListBuilder transferList() { return new TransferListBuilder(); } + /** + * Returns an operation that attempts to execute the given transaction passing the + * provided name to {@link HapiTxnOp#via(String)}; and accepting either + * {@link com.hedera.hapi.node.base.ResponseCodeEnum#SUCCESS} or + * {@link com.hedera.hapi.node.base.ResponseCodeEnum#MAX_CHILD_RECORDS_EXCEEDED} + * as the final status. + * + *

    On success, executes the remaining operations. This lets us stabilize operations + * in CI that need to use all preceding child records to succeed; and hence fail if + * their transaction triggers an end-of-day staking record. + * + * @param txnRequiringMaxChildRecords the transaction requiring all child records + * @param name the transaction name to use + * @param onSuccess the operations to run on success + * @return the operation doing this conditional execution + */ + public static HapiSpecOperation assumingNoStakingChildRecordCausesMaxChildRecordsExceeded( + @NonNull final HapiTxnOp txnRequiringMaxChildRecords, + @NonNull final String name, + @NonNull final HapiSpecOperation... onSuccess) { + return blockingOrder( + txnRequiringMaxChildRecords + .via(name) + // In CI this could fail due to an end-of-staking period record already + // being added as a child to this transaction before its auto-creations + .hasKnownStatusFrom(SUCCESS, MAX_CHILD_RECORDS_EXCEEDED), + withOpContext((spec, opLog) -> { + final var lookup = getTxnRecord(name); + allRunFor(spec, lookup); + final var actualStatus = + lookup.getResponseRecord().getReceipt().getStatus(); + // Continue with more assertions given the normal case the preceding transfer succeeded + if (actualStatus == SUCCESS) { + allRunFor(spec, onSuccess); + } + })); + } + public static class TransferListBuilder { private Tuple transferList; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java index 703b9fe69a81..2cfcfdbd1979 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/AutoAccountCreationSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assumingNoStakingChildRecordCausesMaxChildRecordsExceeded; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; @@ -83,7 +84,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALIAS_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_REMAINING_AUTOMATIC_ASSOCIATIONS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.TokenSupplyType.FINITE; @@ -724,7 +724,7 @@ final HapiSpec canAutoCreateWithFungibleTokenTransfersToAlias() { final var sponsor = spec.registry().getAccountID(DEFAULT_PAYER); final var payer = spec.registry().getAccountID(CIVILIAN); final var parent = lookup.getResponseRecord(); - final var child = lookup.getChildRecord(0); + final var child = lookup.getFirstNonStakingChildRecord(); assertAliasBalanceAndFeeInChildRecord(parent, child, sponsor, payer, 0L, approxTransferFee); })) .then( @@ -1331,43 +1331,28 @@ final HapiSpec multipleAutoAccountCreations() { newKeyNamed(ALIAS_2), newKeyNamed("alias3"), newKeyNamed("alias4"), - newKeyNamed("alias5"), + newKeyNamed("alias5")) + .then(assumingNoStakingChildRecordCausesMaxChildRecordsExceeded( cryptoTransfer( - tinyBarsFromToWithAlias(PAYER, "alias1", ONE_HUNDRED_HBARS), - tinyBarsFromToWithAlias(PAYER, ALIAS_2, ONE_HUNDRED_HBARS), - tinyBarsFromToWithAlias(PAYER, "alias3", ONE_HUNDRED_HBARS)) - .via("multipleAutoAccountCreates") - // In CI this could fail due to an end-of-staking period record already - // being added as a child to this transaction before its auto-creations - .hasKnownStatusFrom(SUCCESS, MAX_CHILD_RECORDS_EXCEEDED)) - .then(withOpContext((spec, opLog) -> { - final var lookup = getTxnRecord("multipleAutoAccountCreates"); - allRunFor(spec, lookup); - final var actualStatus = - lookup.getResponseRecord().getReceipt().getStatus(); - // Continue with more assertions given the normal case the preceding transfer succeeded - if (actualStatus == SUCCESS) { - allRunFor( - spec, - getTxnRecord("multipleAutoAccountCreates") - .hasNonStakingChildRecordCount(3) - .logged(), - getAccountInfo(PAYER) - .has(accountWith() - .balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS)), - cryptoTransfer( - tinyBarsFromToWithAlias(PAYER, "alias4", 7 * ONE_HUNDRED_HBARS), - tinyBarsFromToWithAlias(PAYER, "alias5", 100)) - .via("failedAutoCreate") - .hasKnownStatus(INSUFFICIENT_ACCOUNT_BALANCE), - getTxnRecord("failedAutoCreate") - .hasNonStakingChildRecordCount(0) - .logged(), - getAccountInfo(PAYER) - .has(accountWith() - .balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS))); - } - })); + tinyBarsFromToWithAlias(PAYER, "alias1", ONE_HUNDRED_HBARS), + tinyBarsFromToWithAlias(PAYER, ALIAS_2, ONE_HUNDRED_HBARS), + tinyBarsFromToWithAlias(PAYER, "alias3", ONE_HUNDRED_HBARS)), + "multipleAutoAccountCreates", + getTxnRecord("multipleAutoAccountCreates") + .hasNonStakingChildRecordCount(3) + .logged(), + getAccountInfo(PAYER) + .has(accountWith().balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS)), + cryptoTransfer( + tinyBarsFromToWithAlias(PAYER, "alias4", 7 * ONE_HUNDRED_HBARS), + tinyBarsFromToWithAlias(PAYER, "alias5", 100)) + .via("failedAutoCreate") + .hasKnownStatus(INSUFFICIENT_ACCOUNT_BALANCE), + getTxnRecord("failedAutoCreate") + .hasNonStakingChildRecordCount(0) + .logged(), + getAccountInfo(PAYER) + .has(accountWith().balance((INITIAL_BALANCE * ONE_HBAR) - 3 * ONE_HUNDRED_HBARS)))); } @HapiTest diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java index 56abbd8b67ee..5f9246313f4e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/HollowAccountFinalizationSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Hedera Hashgraph, LLC + * Copyright (C) 2023-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.accountAmount; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assumingNoStakingChildRecordCausesMaxChildRecordsExceeded; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.emptyChildRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; @@ -797,40 +798,48 @@ final HapiSpec hollowPayerAndOtherReqSignerBothGetCompletedInASingleTransaction( spec.registry().saveAccountId(SECP_256K1_SOURCE_KEY, newAccountID); })) .then(withOpContext((spec, opLog) -> { - // send a crypto transfer from the hollow payer - // also sending hbars from the other hollow account - final var op3 = cryptoTransfer(sendFromEvmAddressFromECDSAKey( - spec, - spec.registry().getKey(recipientKey).toByteString(), - ecdsaKey2) - .toArray(Function[]::new)) - .payingWith(SECP_256K1_SOURCE_KEY) - .signedBy(SECP_256K1_SOURCE_KEY, ecdsaKey2) - .sigMapPrefixes(uniqueWithFullPrefixesFor(SECP_256K1_SOURCE_KEY, ecdsaKey2)) - .hasKnownStatus(SUCCESS) - .via(TRANSFER_TXN_2); - final var childRecordCheck = childRecordsCheck( + final var op = assumingNoStakingChildRecordCausesMaxChildRecordsExceeded( + // send a crypto transfer from the hollow payer + // also sending hbars from the other hollow account + cryptoTransfer(sendFromEvmAddressFromECDSAKey( + spec, + spec.registry() + .getKey(recipientKey) + .toByteString(), + ecdsaKey2) + .toArray(Function[]::new)) + .payingWith(SECP_256K1_SOURCE_KEY) + .signedBy(SECP_256K1_SOURCE_KEY, ecdsaKey2) + .sigMapPrefixes(uniqueWithFullPrefixesFor(SECP_256K1_SOURCE_KEY, ecdsaKey2)), TRANSFER_TXN_2, - SUCCESS, - recordWith().status(SUCCESS), - recordWith().status(SUCCESS), - recordWith().status(SUCCESS)); - // assert that the payer has been finalized - final var ecdsaKey = spec.registry().getKey(SECP_256K1_SOURCE_KEY); - final var payerEvmAddress = ByteString.copyFrom(recoverAddressFromPubKey( - ecdsaKey.getECDSASecp256K1().toByteArray())); - final var op4 = getAliasedAccountInfo(payerEvmAddress) - .has(accountWith() - .key(SECP_256K1_SOURCE_KEY) - .noAlias() - .evmAddress(payerEvmAddress)); - // assert that the other hollow account has been finalized - final var otherEcdsaKey = spec.registry().getKey(ecdsaKey2); - final var otherEvmAddress = ByteString.copyFrom(recoverAddressFromPubKey( - otherEcdsaKey.getECDSASecp256K1().toByteArray())); - final var op5 = getAliasedAccountInfo(otherEvmAddress) - .has(accountWith().key(ecdsaKey2).noAlias().evmAddress(otherEvmAddress)); - allRunFor(spec, op3, childRecordCheck, op4, op5); + childRecordsCheck( + TRANSFER_TXN_2, + SUCCESS, + recordWith().status(SUCCESS), + recordWith().status(SUCCESS), + recordWith().status(SUCCESS)), + withOpContext((ignoredSpec, ignoredOpLog) -> { + final var ecdsaKey = spec.registry().getKey(SECP_256K1_SOURCE_KEY); + final var payerEvmAddress = ByteString.copyFrom(recoverAddressFromPubKey( + ecdsaKey.getECDSASecp256K1().toByteArray())); + // assert that the payer has been finalized + final var op4 = getAliasedAccountInfo(payerEvmAddress) + .has(accountWith() + .key(SECP_256K1_SOURCE_KEY) + .noAlias() + .evmAddress(payerEvmAddress)); + // assert that the other hollow account has been finalized + final var otherEcdsaKey = spec.registry().getKey(ecdsaKey2); + final var otherEvmAddress = ByteString.copyFrom(recoverAddressFromPubKey( + otherEcdsaKey.getECDSASecp256K1().toByteArray())); + final var op5 = getAliasedAccountInfo(otherEvmAddress) + .has(accountWith() + .key(ecdsaKey2) + .noAlias() + .evmAddress(otherEvmAddress)); + allRunFor(spec, op4, op5); + })); + allRunFor(spec, op); })); } From c1831c0baea5fbaf27e57010e63a54628f5992cb Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Wed, 3 Jan 2024 09:35:14 -0600 Subject: [PATCH 75/80] chore: add s6-overlay based init process support (#10717) Signed-off-by: Nathan Klick --- .../main-network-node/Dockerfile | 86 +++++++++++++++++-- .../main-network-node/entrypoint.sh | 41 ++++++++- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile index 0388b161813b..2681db967774 100644 --- a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile +++ b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/Dockerfile @@ -4,6 +4,7 @@ # ######################################################################################################################## ARG UBUNTU_TAG="lunar-20231128" +ARG S6_OVERLAY_VERSION="3.1.6.2" ARG SOURCE_DATE_EPOCH="0" ######################################################################################################################## @@ -81,28 +82,94 @@ COPY --from=java-builder-interim / / ######################################################################################################################## # -# Setup OS Base Layer +# Setup S6 Overlay Base Layer # ######################################################################################################################## -FROM ubuntu:${UBUNTU_TAG} AS operating-system-base-interim +FROM ubuntu:${UBUNTU_TAG} AS s6-overlay-interim # Define Build Arguments ARG SOURCE_DATE_EPOCH - -# Define Standard Environment Variables -ENV LANG C.UTF-8 -ENV LC_ALL C.UTF-8 -ENV DEBIAN_FRONTEND noninteractive +ARG S6_OVERLAY_VERSION # Install basic OS utilities RUN --mount=type=bind,source=./repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ repro-sources-list.sh && \ apt-get update && \ - apt-get install -y tar gzip openssl zlib1g libsodium23 libreadline8 sudo netcat-traditional net-tools && \ + apt-get install --yes --no-install-recommends tar gzip openssl zlib1g libsodium23 libreadline8 sudo netcat-traditional net-tools xz-utils curl + +########################### +#### S6 Install #### +########################### +RUN set -eux; \ + NOARCH_PKG_ESUM="05af2536ec4fb23f087a43ce305f8962512890d7c71572ed88852ab91d1434e3" \ + NOARCH_BINARY_URL="https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + ARCH_PKG_ESUM='3fc0bae418a0e3811b3deeadfca9cc2f0869fb2f4787ab8a53f6944067d140ee'; \ + ARCH_BINARY_URL="https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-aarch64.tar.xz"; \ + ;; \ + amd64|i386:x86-64) \ + ARCH_PKG_ESUM='95081f11c56e5a351e9ccab4e70c2b1c3d7d056d82b72502b942762112c03d1c'; \ + ARCH_BINARY_URL="https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz"; \ + ;; \ + ppc64el|powerpc:common64) \ + ARCH_PKG_ESUM='6747f7415d5d8f1b4f51a180af81435de3d0b99762e16bd42c7688dc5dbd089f'; \ + ARCH_BINARY_URL="https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-powerpc64le.tar.xz"; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + curl -sSLo /tmp/s6-overlay-noarch.tar.xz ${NOARCH_BINARY_URL}; \ + curl -sSLo /tmp/s6-overlay-arch.tar.xz ${ARCH_BINARY_URL}; \ + echo "${NOARCH_PKG_ESUM} */tmp/s6-overlay-noarch.tar.xz" | sha256sum -c -; \ + echo "${ARCH_PKG_ESUM} */tmp/s6-overlay-arch.tar.xz" | sha256sum -c -; \ + tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz; \ + tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz; \ + rm -f /tmp/s6-overlay-noarch.tar.xz; \ + rm -f /tmp/s6-overlay-arch.tar.xz + +# Remove Unneeded Utilities +RUN --mount=type=bind,source=./repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ + repro-sources-list.sh && \ + apt-get remove --yes xz-utils curl && \ + apt-get autoremove --yes && \ apt-get autoclean --yes && \ apt-get clean all --yes && \ rm -rf /var/log/ && \ rm -rf /var/cache/ + +######################################## +#### Deterministic Build Hack #### +######################################## + +# === Workarounds below will not be needed when https://github.com/moby/buildkit/pull/4057 is merged === +# NOTE: PR #4057 has been merged but will not be available until the v0.13.x series of releases. +# Limit the timestamp upper bound to SOURCE_DATE_EPOCH. +# Workaround for https://github.com/moby/buildkit/issues/3180 +RUN find $( ls / | grep -E -v "^(dev|mnt|proc|sys)$" ) \ + -newermt "@${SOURCE_DATE_EPOCH}" -writable -xdev \ + | xargs touch --date="@${SOURCE_DATE_EPOCH}" --no-dereference + +FROM scratch AS s6-overlay +COPY --from=s6-overlay-interim / / + +######################################################################################################################## +# +# Setup OS Base Layer +# +######################################################################################################################## +FROM s6-overlay AS operating-system-base-interim +# Define Build Arguments +ARG SOURCE_DATE_EPOCH + +# Define Standard Environment Variables +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 +ENV DEBIAN_FRONTEND noninteractive + # Create Application Folders RUN mkdir -p "/opt/hgcapp" && \ mkdir -p "/opt/hgcapp/accountBalances" && \ @@ -224,4 +291,5 @@ EXPOSE 50111/tcp 50211/tcp 50212/tcp # Set Final Working Directory, User, and Entrypoint USER 2000 WORKDIR "/opt/hgcapp" -ENTRYPOINT ["/opt/hgcapp/services-hedera/HapiApp2.0/entrypoint.sh"] +ENTRYPOINT ["/init"] +CMD ["/opt/hgcapp/services-hedera/HapiApp2.0/entrypoint.sh"] diff --git a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh index 731ec150df18..03a3d3116c59 100644 --- a/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh +++ b/hedera-node/infrastructure/docker/containers/production-next/main-network-node/entrypoint.sh @@ -59,6 +59,35 @@ if [[ ! -d "${SCRIPT_PATH}/${LOG_DIR_NAME}" ]]; then mkdir -p "${SCRIPT_PATH}/${LOG_DIR_NAME}" fi +cat <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN USER IDENT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" id echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END USER IDENT <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" @@ -69,5 +98,15 @@ echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN JAVA VERSION >>>>>>>>>>>>> echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END JAVA VERSION <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" echo +set +e +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN NODE OUTPUT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" /usr/bin/env java ${JAVA_HEAP_OPTS} ${JAVA_OPTS} -cp "${JAVA_CLASS_PATH}" "${JAVA_MAIN_CLASS}" -printf "java exit code %s" "${?}\n" +EC="${?}" +echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END NODE OUTPUT <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + +echo +echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> BEGIN EXIT CODE >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +printf "Process Exit Code: %s\n" "${EC}" +echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< END EXIT CODE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + +printf "\n\n#### Hedera Services Node Software Stopped ####\n\n" From efb421719f5e9346ac5b5eaa79febdc7864a7fa5 Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Wed, 3 Jan 2024 10:08:31 -0600 Subject: [PATCH 76/80] fix: restore accidentally disable reconnect tests (#10560) Signed-off-by: Jeffrey Tang --- .../node-flow-fsts-daily-interval-05.yaml | 40 +++++++++---------- .../node-flow-fsts-daily-regression.yaml | 26 ++++++------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/node-flow-fsts-daily-interval-05.yaml b/.github/workflows/node-flow-fsts-daily-interval-05.yaml index a918de2a95a6..468621c68b9d 100644 --- a/.github/workflows/node-flow-fsts-daily-interval-05.yaml +++ b/.github/workflows/node-flow-fsts-daily-interval-05.yaml @@ -1,5 +1,5 @@ ## -# Copyright (C) 2022-2023 Hedera Hashgraph, LLC +# Copyright (C) 2022-2024 Hedera Hashgraph, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -68,22 +68,22 @@ jobs: if: ${{ !cancelled() && always() }} - comp-ni-reconnect-correctness-6n-1c: - name: Comp-NI-Reconnect-Correctness-6N-1C - uses: ./.github/workflows/zxc-jrs-regression.yaml - with: - ref: ${{ github.event.inputs.ref }} - branch-name: ${{ github.event.inputs.branch-name }} - hedera-tests-enabled: true - use-branch-for-slack-channel: true - panel-config: "configs/services/suites/daily/GCP-Daily-Services-Comp-NI-Reconnect-Correctness-6N-1C.json" - secrets: - access-token: ${{ secrets.PLATFORM_GH_ACCESS_TOKEN }} - jrs-ssh-user-name: ${{ secrets.PLATFORM_JRS_SSH_USER_NAME }} - jrs-ssh-key-file: ${{ secrets.PLATFORM_JRS_SSH_KEY_FILE }} - gcp-project-number: ${{ secrets.PLATFORM_GCP_PROJECT_NUMBER }} - gcp-sa-key-contents: ${{ secrets.PLATFORM_GCP_KEY_FILE }} - slack-api-token: ${{ secrets.PLATFORM_SLACK_API_TOKEN }} - gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} - gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} - if: ${{ !cancelled() && always() }} +# comp-ni-reconnect-correctness-6n-1c: +# name: Comp-NI-Reconnect-Correctness-6N-1C +# uses: ./.github/workflows/zxc-jrs-regression.yaml +# with: +# ref: ${{ github.event.inputs.ref }} +# branch-name: ${{ github.event.inputs.branch-name }} +# hedera-tests-enabled: true +# use-branch-for-slack-channel: true +# panel-config: "configs/services/suites/daily/GCP-Daily-Services-Comp-NI-Reconnect-Correctness-6N-1C.json" +# secrets: +# access-token: ${{ secrets.PLATFORM_GH_ACCESS_TOKEN }} +# jrs-ssh-user-name: ${{ secrets.PLATFORM_JRS_SSH_USER_NAME }} +# jrs-ssh-key-file: ${{ secrets.PLATFORM_JRS_SSH_KEY_FILE }} +# gcp-project-number: ${{ secrets.PLATFORM_GCP_PROJECT_NUMBER }} +# gcp-sa-key-contents: ${{ secrets.PLATFORM_GCP_KEY_FILE }} +# slack-api-token: ${{ secrets.PLATFORM_SLACK_API_TOKEN }} +# gradle-cache-username: ${{ secrets.GRADLE_CACHE_USERNAME }} +# gradle-cache-password: ${{ secrets.GRADLE_CACHE_PASSWORD }} +# if: ${{ !cancelled() && always() }} diff --git a/.github/workflows/node-flow-fsts-daily-regression.yaml b/.github/workflows/node-flow-fsts-daily-regression.yaml index 4f95c65df8cc..1c5cd336b3f6 100644 --- a/.github/workflows/node-flow-fsts-daily-regression.yaml +++ b/.github/workflows/node-flow-fsts-daily-regression.yaml @@ -1,5 +1,5 @@ ## -# Copyright (C) 2022-2023 Hedera Hashgraph, LLC +# Copyright (C) 2022-2024 Hedera Hashgraph, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -82,18 +82,18 @@ jobs: secrets: access-token: ${{ secrets.PLATFORM_GH_ACCESS_TOKEN }} -# interval-05: -# name: Interval 5 -# uses: ./.github/workflows/platform-zxc-launch-jrs-workflow.yaml -# needs: -# - interval-04 -# with: -# ref: ${{ github.event.inputs.ref || github.sha }} -# branch-name: ${{ github.event.inputs.branch-name || github.ref_name }} -# workflow-file: node-flow-fsts-daily-interval-05.yaml -# concurrency-group: ${{ github.event.inputs.ref || github.sha }}-node-flow-fsts-daily-group-05 -# secrets: -# access-token: ${{ secrets.PLATFORM_GH_ACCESS_TOKEN }} + interval-05: + name: Interval 5 + uses: ./.github/workflows/platform-zxc-launch-jrs-workflow.yaml + needs: + - interval-04 + with: + ref: ${{ github.event.inputs.ref || github.sha }} + branch-name: ${{ github.event.inputs.branch-name || github.ref_name }} + workflow-file: node-flow-fsts-daily-interval-05.yaml + concurrency-group: ${{ github.event.inputs.ref || github.sha }}-node-flow-fsts-daily-group-05 + secrets: + access-token: ${{ secrets.PLATFORM_GH_ACCESS_TOKEN }} # interval-06: # name: Interval 6 From 0a36cfa70e22e6d1475596136d24df53fc3b1fdb Mon Sep 17 00:00:00 2001 From: Nathan Klick Date: Wed, 3 Jan 2024 10:52:09 -0600 Subject: [PATCH 77/80] fix: update version file for release 0.47 Signed-off-by: Nathan Klick --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 8e180300a875..864059b0c03f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.45.0-SNAPSHOT \ No newline at end of file +0.47.0-SNAPSHOT From a4683b786d83696e1b32a1bfaa72f7a8a97edede Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:02:23 -0600 Subject: [PATCH 78/80] ci: turn off regression for release 0.44 (#10708) --- .github/workflows/node-zxcron-release-fsts-regression.yaml | 4 ++-- .github/workflows/platform-zxcron-release-jrs-regression.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/node-zxcron-release-fsts-regression.yaml b/.github/workflows/node-zxcron-release-fsts-regression.yaml index 812c30d8dc6e..7e0fdb2c3544 100644 --- a/.github/workflows/node-zxcron-release-fsts-regression.yaml +++ b/.github/workflows/node-zxcron-release-fsts-regression.yaml @@ -1,5 +1,5 @@ ## -# Copyright (C) 2022-2023 Hedera Hashgraph, LLC +# Copyright (C) 2022-2024 Hedera Hashgraph, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ jobs: major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" - if [[ "${major}" -eq 0 && "${minor}" -lt 44 ]]; then + if [[ "${major}" -eq 0 && "${minor}" -lt 45 ]]; then continue fi diff --git a/.github/workflows/platform-zxcron-release-jrs-regression.yaml b/.github/workflows/platform-zxcron-release-jrs-regression.yaml index b0063fcb03ef..f45b149eb0e2 100644 --- a/.github/workflows/platform-zxcron-release-jrs-regression.yaml +++ b/.github/workflows/platform-zxcron-release-jrs-regression.yaml @@ -1,5 +1,5 @@ ## -# Copyright (C) 2022-2023 Hedera Hashgraph, LLC +# Copyright (C) 2022-2024 Hedera Hashgraph, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ jobs: major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" - if [[ "${major}" -eq 0 && "${minor}" -lt 44 ]]; then + if [[ "${major}" -eq 0 && "${minor}" -lt 45 ]]; then continue fi From eb12fed8751cf965b4faa195ed038c9e28ffa8a8 Mon Sep 17 00:00:00 2001 From: Joseph Sinclair <121976561+jsync-swirlds@users.noreply.github.com> Date: Wed, 3 Jan 2024 10:45:39 -0700 Subject: [PATCH 79/80] chore: Clean up schedule HapiSpec suites (#10710) Signed-off-by: Joseph Sinclair --- .../suites/leaky/LeakyCryptoTestsSuite.java | 4 +- .../suites/schedule/ScheduleCreateSpecs.java | 57 ++-- .../suites/schedule/ScheduleDeleteSpecs.java | 14 +- .../ScheduleExecutionSpecStateful.java | 65 ++-- .../schedule/ScheduleExecutionSpecs.java | 283 +++++++----------- .../ScheduleLongTermExecutionSpecs.java | 175 ++++------- .../schedule/ScheduleLongTermSignSpecs.java | 61 ++-- .../suites/schedule/ScheduleRecordSpecs.java | 37 ++- .../suites/schedule/ScheduleSignSpecs.java | 42 ++- .../bdd/suites/schedule/ScheduleUtils.java | 237 +++++++++++++-- 10 files changed, 500 insertions(+), 475 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java index 63a9378e6ada..97417e8db154 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyCryptoTestsSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,7 +106,6 @@ import static com.hedera.services.bdd.suites.crypto.CryptoCreateSuite.ED_25519_KEY; import static com.hedera.services.bdd.suites.crypto.CryptoCreateSuite.LAZY_CREATION_ENABLED; import static com.hedera.services.bdd.suites.file.FileUpdateSuite.CIVILIAN; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.SENDER_TXN; import static com.hedera.services.bdd.suites.token.TokenPauseSpecs.DEFAULT_MIN_AUTO_RENEW_PERIOD; import static com.hedera.services.bdd.suites.token.TokenPauseSpecs.LEDGER_AUTO_RENEW_PERIOD_MIN_DURATION; import static com.hedera.services.bdd.suites.token.TokenPauseSpecs.TokenIdOrderingAsserts.withOrderedTokenIds; @@ -187,6 +186,7 @@ public class LeakyCryptoTestsSuite extends HapiSuite { public static final String PAY_TXN = "payTxn"; public static final String CREATE_TX = "createTX"; public static final String V_0_34 = "v0.34"; + private static final String SENDER_TXN = "senderTxn"; public static void main(String... args) { new LeakyCryptoTestsSuite().runSuiteSync(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java index 16d2f127df44..20fcedbec8f7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleCreateSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,29 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BASIC_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CONTINUE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.COPYCAT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATION; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_TX_EXPIRY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DESIGNATING_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ENTITY_MEMO; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FIRST_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.NEVER_TO_BE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ONLY_BODY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ONLY_BODY_AND_ADMIN_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ONLY_BODY_AND_MEMO; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ONLY_BODY_AND_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ORIGINAL; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SECOND_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_ID_DOES_NOT_EXIST; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.IDENTICAL_SCHEDULE_ALREADY_CREATED; @@ -75,31 +97,6 @@ public class ScheduleCreateSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleCreateSpecs.class); - private static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; - private static final String defaultWhitelist = - HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST); - private static final String defaultTxExpiry = - HapiSpecSetup.getDefaultNodeProps().get("ledger.schedule.txExpiryTimeSecs"); - private static final String DESIGNATING_PAYER = "1.2.3"; - private static final String ONLY_BODY = "onlyBody"; - private static final String ONLY_BODY_AND_ADMIN_KEY = "onlyBodyAndAdminKey"; - private static final String ONLY_BODY_AND_MEMO = "onlyBodyAndMemo"; - private static final String CREATION = "creation"; - private static final String BASIC_XFER = "basicXfer"; - private static final String NEVER_TO_BE = "neverToBe"; - private static final String SENDER = "sender"; - private static final String VALID_SCHEDULE = "validSchedule"; - private static final String ADMIN = "admin"; - private static final String PAYER = "payer"; - private static final String ONLY_BODY_AND_PAYER = "onlyBodyAndPayer"; - private static final String ORIGINAL = "original"; - private static final String CONTINUE = "continue"; - private static final String ENTITY_MEMO = "This was Mr. Bleaney's room. He stayed"; - private static final String SECOND_PAYER = "secondPayer"; - private static final String FIRST_PAYER = "firstPayer"; - private static final String COPYCAT = "copycat"; - private static final String RECEIVER = "receiver"; - public static void main(String... args) { new ScheduleCreateSpecs().runSuiteSync(); } @@ -141,7 +138,7 @@ final HapiSpec suiteCleanup() { return defaultHapiSpec("suiteCleanup") .given() .when() - .then(overriding("ledger.schedule.txExpiryTimeSecs", defaultTxExpiry)); + .then(overriding("ledger.schedule.txExpiryTimeSecs", DEFAULT_TX_EXPIRY)); } @HapiTest @@ -441,7 +438,7 @@ final HapiSpec preservesRevocationServiceSemanticsForFileDelete() { .sigControl(forKey(shouldBeDeletedEventually, compensatorySigs)), sleepFor(1_000L), getFileInfo(shouldBeDeletedEventually).hasDeleted(true), - overriding(SCHEDULING_WHITELIST, defaultWhitelist)); + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT)); } @HapiTest @@ -585,7 +582,7 @@ public HapiSpec whitelistWorks() { scheduleCreate("ok", createTopic(NEVER_TO_BE)) // prevent multiple runs of this test causing duplicates .withEntityMemo("" + new SecureRandom().nextLong())) - .then(overriding(SCHEDULING_WHITELIST, defaultWhitelist)); + .then(overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT)); } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java index 2df8ebf2742b..b62762f26186 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleDeleteSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,12 @@ import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermSignSpecs.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULED_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_DELETED; @@ -45,10 +49,6 @@ @HapiTestSuite public class ScheduleDeleteSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleDeleteSpecs.class); - private static final String VALID_SCHEDULED_TXN = "validScheduledTxn"; - private static final String SENDER = "sender"; - private static final String RECEIVER = "receiver"; - private static final String ADMIN = "admin"; public static void main(String... args) { new ScheduleDeleteSpecs().runSuiteAsync(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java index b4fc8edde973..64d208975c87 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecStateful.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,8 +37,26 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs.addAllToWhitelist; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.A_TOKEN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_MAX_TOKEN_TRANSFER_LEN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_MAX_TRANSFER_LEN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_TX_EXPIRY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FAILING_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.LEDGER_TOKEN_TRANSFERS_MAX_LEN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.LEDGER_TRANSFERS_MAX_LEN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER_A; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER_B; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER_C; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULE_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SUPPLY_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TOKENS_NFTS_ARE_ENABLED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TREASURY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.addAllToWhitelist; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NOT_SUPPORTED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_TRANSFER_LIST_SIZE_LIMIT_EXCEEDED; @@ -49,7 +67,6 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.suites.HapiSuite; import com.hederahashgraph.api.proto.java.TokenType; import java.util.List; @@ -69,30 +86,6 @@ public class ScheduleExecutionSpecStateful extends HapiSuite { private static final int TMP_MAX_TRANSFER_LENGTH = 2; private static final int TMP_MAX_TOKEN_TRANSFER_LENGTH = 2; - private static final String defaultTxExpiry = - HapiSpecSetup.getDefaultNodeProps().get("ledger.schedule.txExpiryTimeSecs"); - private static final String LEDGER_TRANSFERS_MAX_LEN = "ledger.transfers.maxLen"; - private static final String defaultMaxTransferLen = - HapiSpecSetup.getDefaultNodeProps().get(LEDGER_TRANSFERS_MAX_LEN); - private static final String LEDGER_TOKEN_TRANSFERS_MAX_LEN = "ledger.tokenTransfers.maxLen"; - private static final String defaultMaxTokenTransferLen = - HapiSpecSetup.getDefaultNodeProps().get(LEDGER_TOKEN_TRANSFERS_MAX_LEN); - private static final String defaultWhitelist = - HapiSpecSetup.getDefaultNodeProps().get("scheduling.whitelist"); - - private static final String A_TOKEN = "token"; - private static final String VALID_SCHEDULE = "validSchedule"; - private static final String SCHEDULE_PAYER = "schedulePayer"; - private static final String TOKENS_NFTS_ARE_ENABLED = "tokens.nfts.areEnabled"; - private static final String TREASURY = "treasury"; - private static final String SUPPLY_KEY = "supplyKey"; - private static final String SENDER = "sender"; - private static final String RECEIVER_A = "receiverA"; - private static final String RECEIVER_B = "receiverB"; - private static final String RECEIVER_C = "receiverC"; - private static final String PAYING_ACCOUNT = "payingAccount"; - String failingTxn = "failingTxn"; - public static void main(String... args) { new ScheduleExecutionSpecStateful().runSuiteSync(); } @@ -143,7 +136,7 @@ final HapiSpec scheduledUniqueMintFailsWithNftsDisabled() { .initialSupply(0), scheduleCreate(VALID_SCHEDULE, mintToken(A_TOKEN, List.of(ByteString.copyFromUtf8("m1")))) .designatingPayer(SCHEDULE_PAYER) - .via(failingTxn), + .via(FAILING_TXN), fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) .overridingProps(Map.of(TOKENS_NFTS_ARE_ENABLED, "false"))) @@ -151,7 +144,7 @@ final HapiSpec scheduledUniqueMintFailsWithNftsDisabled() { .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) .hasKnownStatus(SUCCESS)) .then( - getTxnRecord(failingTxn) + getTxnRecord(FAILING_TXN) .scheduled() .hasPriority(recordWith().status(NOT_SUPPORTED)), getTokenInfo(A_TOKEN).hasTotalSupply(0), @@ -175,7 +168,7 @@ final HapiSpec scheduledUniqueBurnFailsWithNftsDisabled() { .initialSupply(0), scheduleCreate(VALID_SCHEDULE, burnToken(A_TOKEN, List.of(1L, 2L))) .designatingPayer(SCHEDULE_PAYER) - .via(failingTxn), + .via(FAILING_TXN), fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) .overridingProps(Map.of(TOKENS_NFTS_ARE_ENABLED, "false"))) @@ -183,7 +176,7 @@ final HapiSpec scheduledUniqueBurnFailsWithNftsDisabled() { .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) .hasKnownStatus(SUCCESS)) .then( - getTxnRecord(failingTxn) + getTxnRecord(FAILING_TXN) .scheduled() .hasPriority(recordWith().status(NOT_SUPPORTED)), getTokenInfo(A_TOKEN).hasTotalSupply(0), @@ -223,7 +216,7 @@ public HapiSpec executionWithTransferListWrongSizedFails() { .via("signTx") .hasKnownStatus(SUCCESS)) .then( - overriding(LEDGER_TRANSFERS_MAX_LEN, defaultMaxTransferLen), + overriding(LEDGER_TRANSFERS_MAX_LEN, DEFAULT_MAX_TRANSFER_LEN), getAccountBalance(SENDER).hasTinyBars(senderBalance), getAccountBalance(RECEIVER_A).hasTinyBars(noBalance), getAccountBalance(RECEIVER_B).hasTinyBars(noBalance), @@ -270,7 +263,7 @@ final HapiSpec executionWithTokenTransferListSizeExceedFails() { .alsoSigningWith(xTreasury, schedulePayer) .designatingPayer(schedulePayer)) .then( - overriding(LEDGER_TOKEN_TRANSFERS_MAX_LEN, defaultMaxTokenTransferLen), + overriding(LEDGER_TOKEN_TRANSFERS_MAX_LEN, DEFAULT_MAX_TOKEN_TRANSFER_LEN), getTxnRecord(failedTxn) .scheduled() .hasPriority(recordWith().status(TOKEN_TRANSFER_LIST_SIZE_LIMIT_EXCEEDED)), @@ -284,8 +277,8 @@ final HapiSpec suiteCleanup() { .given() .when() .then( - overriding("ledger.schedule.txExpiryTimeSecs", defaultTxExpiry), - overriding("scheduling.whitelist", defaultWhitelist), + overriding("ledger.schedule.txExpiryTimeSecs", DEFAULT_TX_EXPIRY), + overriding("scheduling.whitelist", WHITELIST_DEFAULT), fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) .overridingProps(Map.of(TOKENS_NFTS_ARE_ENABLED, "true"))); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java index 3b43cdad29e7..ff3d65d31db6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleExecutionSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,9 +73,52 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.poeticUpgradeLoc; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.standardUpdateFile; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; -import static com.hedera.services.bdd.suites.schedule.ScheduleRecordSpecs.scheduledVersionOf; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ACCOUNT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.A_SCHEDULE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.A_TOKEN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BASIC_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATE_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATION; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_MAX_BATCH_SIZE_MINT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FAILED_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FAILING_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.LUCKY_RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ORIG_FILE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT_2; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RANDOM_MSG; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULED_TRANSACTION_MUST_SUCCEED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULE_CREATE_FEE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULE_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_1; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_2; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_3; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIGN_TX; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIGN_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SUCCESS_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SUPPLY_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TOKENS_NFTS_MAX_BATCH_SIZE_MINT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TRANSACTION_NOT_SCHEDULED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TREASURY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WEIRDLY_POPULAR_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_MINIMUM; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_CONSENSUS_TIMESTAMP; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_RECORD_ACCOUNT_ID; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_SCHEDULE_ID; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_TRANSACTION_VALID_START; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_TRANSFER_LIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.addAllToWhitelist; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.getPoeticUpgradeHash; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.scheduledVersionOf; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.transferListCheck; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; @@ -111,25 +154,21 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.UNRESOLVABLE_REQUIRED_SIGNERS; import com.google.protobuf.ByteString; -import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.node.config.data.ConsensusConfig; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.HapiSpecOperation; import com.hedera.services.bdd.spec.HapiSpecSetup; -import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.suites.BddMethodIsNotATest; import com.hedera.services.bdd.suites.HapiSuite; -import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TransactionID; import com.swirlds.test.framework.config.TestConfigBuilder; import java.util.Arrays; import java.util.EnumSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; @@ -147,47 +186,6 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ScheduleExecutionSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleExecutionSpecs.class); - private static final String A_TOKEN = "token"; - private static final String TREASURY = "treasury"; - private static final String SCHEDULE_PAYER = "schedulePayer"; - private static final String SUPPLY_KEY = "supplyKey"; - private static final String WRONG_CONSENSUS_TIMESTAMP = "Wrong consensus timestamp!"; - private static final String WRONG_TRANSACTION_VALID_START = "Wrong transaction valid start!"; - private static final String WRONG_RECORD_ACCOUNT_ID = "Wrong record account ID!"; - private static final String TRANSACTION_NOT_SCHEDULED = "Transaction not scheduled!"; - private static final String RECEIVER = "receiver"; - private static final String SENDER = "sender"; - private static final String PAYING_ACCOUNT = "payingAccount"; - private static final String BASIC_XFER = "basicXfer"; - private static final String WRONG_SCHEDULE_ID = "Wrong schedule ID!"; - private static final String WRONG_TRANSFER_LIST = "Wrong transfer list!"; - private static final String LUCKY_RECEIVER = "luckyReceiver"; - private static final String SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED = "Scheduled transaction must not succeed"; - private static final String FAILED_XFER = "failedXfer"; - private static final String SCHEDULED_TRANSACTION_MUST_SUCCEED = "Scheduled transaction must succeed"; - private static final String PAYING_ACCOUNT_2 = "payingAccount2"; - private static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; - private static final String FAILING_TXN = "failingTxn"; - private static final String ADMIN = "admin"; - private static final String CREATION = "creation"; - private static final String WEIRDLY_POPULAR_KEY = "weirdlyPopularKey"; - private static final String SENDER_1 = "sender1"; - private static final String SENDER_2 = "sender2"; - private static final String SENDER_3 = "sender3"; - static final byte[] ORIG_FILE = "SOMETHING".getBytes(); - private static final String A_SCHEDULE = "validSchedule"; - private static final String PAYER = "somebody"; - private static final String RANDOM_MSG = - "Little did they care who danced between / And little she by whom her dance was seen"; - private static final String CREATE_TXN = "createTx"; - private static final String SIGN_TXN = "signTx"; - private static final String SCHEDULE_CREATE_FEE = "scheduleCreateFee"; - private static final String ACCOUNT = "civilian"; - private static final String TOKENS_NFTS_MAX_BATCH_SIZE_MINT = "tokens.nfts.maxBatchSizeMint"; - private static final String defaultMaxBatchSizeMint = - HapiSpecSetup.getDefaultNodeProps().get(TOKENS_NFTS_MAX_BATCH_SIZE_MINT); - private static final String defaultWhitelist = - HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST); /** * This is matched to ConsensusTimeTracker.MAX_PRECEDING_RECORDS_REMAINING_TXN + 1. @@ -198,9 +196,6 @@ public class ScheduleExecutionSpecs extends HapiSuite { private final long normalTriggeredTxnTimestampOffset = getTestConfig(ConsensusConfig.class).handleMaxPrecedingRecords() + 1; - private static final String successTxn = "successTxn"; - private static final String signTxn = "signTxn"; - @SuppressWarnings("java:S2245") // using java.util.Random in tests is fine private final Random r = new Random(882654L); @@ -278,8 +273,6 @@ final HapiSpec suiteSetup() { return defaultHapiSpec("suiteSetup").given().when().then(addAllToWhitelist()); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(18) final HapiSpec scheduledBurnFailsWithInvalidTxBody() { @@ -309,8 +302,6 @@ final HapiSpec scheduledBurnFailsWithInvalidTxBody() { .hasPriority(recordWith().status(INVALID_TRANSACTION_BODY)))); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(23) final HapiSpec scheduledMintFailsWithInvalidTxBody() { @@ -411,18 +402,18 @@ final HapiSpec scheduledUniqueBurnExecutesProperly() { mintToken(A_TOKEN, List.of(ByteString.copyFromUtf8("metadata"))), scheduleCreate(A_SCHEDULE, burnToken(A_TOKEN, List.of(1L))) .designatingPayer(SCHEDULE_PAYER) - .via(successTxn)) + .via(SUCCESS_TXN)) .when( getTokenInfo(A_TOKEN).hasTotalSupply(1), scheduleSign(A_SCHEDULE) .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( withOpContext((spec, opLog) -> { - var createTx = getTxnRecord(successTxn); - var signTx = getTxnRecord(signTxn); - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var createTx = getTxnRecord(SUCCESS_TXN); + var signTx = getTxnRecord(SIGN_TX); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, createTx, signTx, triggeredTx); Assertions.assertEquals( @@ -541,19 +532,17 @@ final HapiSpec scheduledBurnForUniqueSucceedsWithExistingAmount() { .initialSupply(0), scheduleCreate(A_SCHEDULE, burnToken(A_TOKEN, 123L)) .designatingPayer(SCHEDULE_PAYER) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) .hasKnownStatus(SUCCESS)) .then( - getTxnRecord(successTxn) + getTxnRecord(SUCCESS_TXN) .scheduled() .hasPriority(recordWith().status(INVALID_TOKEN_BURN_METADATA)), getTokenInfo(A_TOKEN).hasTotalSupply(0)); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(19) final HapiSpec scheduledBurnForUniqueFailsWithInvalidAmount() { @@ -631,11 +620,9 @@ final HapiSpec scheduledUniqueMintFailsWithInvalidBatchSize() { .scheduled() .hasPriority(recordWith().status(BATCH_SIZE_LIMIT_EXCEEDED)), getTokenInfo(A_TOKEN).hasTotalSupply(0), - overriding(TOKENS_NFTS_MAX_BATCH_SIZE_MINT, defaultMaxBatchSizeMint)); + overriding(TOKENS_NFTS_MAX_BATCH_SIZE_MINT, DEFAULT_MAX_BATCH_SIZE_MINT)); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(22) final HapiSpec scheduledMintFailsWithInvalidAmount() { @@ -692,16 +679,16 @@ final HapiSpec scheduledUniqueMintExecutesProperly() { ByteString.copyFromUtf8("somemetadata1"), ByteString.copyFromUtf8("somemetadata2")))) .designatingPayer(SCHEDULE_PAYER) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( withOpContext((spec, opLog) -> { - var createTx = getTxnRecord(successTxn); - var signTx = getTxnRecord(signTxn); - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var createTx = getTxnRecord(SUCCESS_TXN); + var signTx = getTxnRecord(SIGN_TX); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, createTx, signTx, triggeredTx); Assertions.assertEquals( @@ -765,16 +752,16 @@ final HapiSpec scheduledMintExecutesProperly() { .initialSupply(101), scheduleCreate(A_SCHEDULE, mintToken(A_TOKEN, 10)) .designatingPayer(SCHEDULE_PAYER) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( withOpContext((spec, opLog) -> { - var createTx = getTxnRecord(successTxn); - var signTx = getTxnRecord(signTxn); - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var createTx = getTxnRecord(SUCCESS_TXN); + var signTx = getTxnRecord(SIGN_TX); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, createTx, signTx, triggeredTx); Assertions.assertEquals( @@ -839,16 +826,16 @@ final HapiSpec scheduledBurnExecutesProperly() { .initialSupply(101), scheduleCreate(A_SCHEDULE, burnToken(A_TOKEN, 10)) .designatingPayer(SCHEDULE_PAYER) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(SUPPLY_KEY, SCHEDULE_PAYER, TREASURY) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( withOpContext((spec, opLog) -> { - var createTx = getTxnRecord(successTxn); - var signTx = getTxnRecord(signTxn); - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var createTx = getTxnRecord(SUCCESS_TXN); + var signTx = getTxnRecord(SIGN_TX); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, createTx, signTx, triggeredTx); Assertions.assertEquals( @@ -1168,8 +1155,6 @@ final HapiSpec scheduledXferFailingWithUnassociatedAccountTransferPaysServiceFee assertBasicallyIdentical(successFeesObs.get(), failureFeesObs.get(), 1.0))); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(45) final HapiSpec scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButNoImpact() { @@ -1228,8 +1213,6 @@ final HapiSpec scheduledXferFailingWithNonNetZeroTokenTransferPaysServiceFeeButN getAccountBalance(xCivilian).hasTokenBalance(xToken, 1)); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(46) final HapiSpec scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact() { @@ -1297,8 +1280,6 @@ final HapiSpec scheduledXferFailingWithRepeatedTokenIdPaysServiceFeeButNoImpact( getAccountBalance(yTreasury).hasTokenBalance(xToken, 1)); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(42) final HapiSpec scheduledXferFailingWithEmptyTokenTransferAccountAmountsPaysServiceFeeButNoImpact() { @@ -1554,8 +1535,6 @@ private void assertBasicallyIdentical( } } - // @todo('9974') Need to work out why this succeeds instead - // of failing with UNRESOLVABLE_REQUIRED_SIGNERS @HapiTest @Order(31) final HapiSpec scheduledSubmitThatWouldFailWithTopicDeletedCannotBeSigned() { @@ -1657,7 +1636,6 @@ final HapiSpec executionTriggersWithWeirdlyRepeatedKey() { getTxnRecord("repeatedSigning").logged()); } - // @todo('9976') Need to work out why this does not produce the expected transfer list @HapiTest @Order(14) final HapiSpec executionWithDefaultPayerWorks() { @@ -1717,7 +1695,6 @@ final HapiSpec executionWithDefaultPayerWorks() { })); } - // @todo('9977') Need to figure out why the ending balance does not match ex @HapiTest @Order(13) final HapiSpec executionWithDefaultPayerButNoFundsFails() { @@ -1858,7 +1835,6 @@ final HapiSpec executionWithDefaultPayerButAccountDeletedFails() { recordWith().statusFrom(INSUFFICIENT_PAYER_BALANCE, PAYER_ACCOUNT_DELETED))); } - // @todo('9977') Need to figure out why the ending balance does not match expected @Order(7) @HapiTest final HapiSpec executionWithCustomPayerButAccountDeletedFails() { @@ -2007,8 +1983,6 @@ final HapiSpec executionWithTokenInsufficientAccountBalanceFails() { getAccountBalance(xTreasury).hasTokenBalance(xToken, 100)); } - // This should not be run for modular service due to key gathering behavior differences. - // c.f. Issue #9970 for explanation @HapiTest @Order(15) final HapiSpec executionWithInvalidAccountAmountsFails() { @@ -2121,7 +2095,6 @@ final HapiSpec executionWithCustomPayerWorks() { })); } - // @todo('9976') Need to work out why this does not produce the expected transfer list @HapiTest @Order(6) final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { @@ -2191,7 +2164,6 @@ final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { })); } - // @todo('9976') Need to work out why this does not produce the expected transfer list @HapiTest @Order(9) final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { @@ -2260,38 +2232,10 @@ final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { })); } - public static boolean transferListCheck( - HapiGetTxnRecord triggered, - AccountID givingAccountID, - AccountID receivingAccountID, - AccountID payingAccountID, - Long amount) { - AccountAmount givingAmount = AccountAmount.newBuilder() - .setAccountID(givingAccountID) - .setAmount(-amount) - .build(); - - AccountAmount receivingAmount = AccountAmount.newBuilder() - .setAccountID(receivingAccountID) - .setAmount(amount) - .build(); - - var accountAmountList = triggered.getResponseRecord().getTransferList().getAccountAmountsList(); - System.out.println("accountAmountList: " + accountAmountList); - boolean payerHasPaid = - accountAmountList.stream().anyMatch(a -> a.getAccountID().equals(payingAccountID) && a.getAmount() < 0); - System.out.println("payerHasPaid: " + payerHasPaid); - boolean amountHasBeenTransferred = - accountAmountList.contains(givingAmount) && accountAmountList.contains(receivingAmount); - System.out.println("amountHasBeenTransferred: " + amountHasBeenTransferred); - - return amountHasBeenTransferred && payerHasPaid; - } - // Currently this cannot be run as HapiTest because it stops the captive nodes. @BddMethodIsNotATest final HapiSpec scheduledFreezeWorksAsExpected() { - final byte[] poeticUpgradeHash = ScheduleUtils.getPoeticUpgradeHash(); + final byte[] poeticUpgradeHash = getPoeticUpgradeHash(); return defaultHapiSpec("ScheduledFreezeWorksAsExpected") .given( @@ -2310,18 +2254,18 @@ final HapiSpec scheduledFreezeWorksAsExpected() { .withEntityMemo(randomUppercase(100)) .designatingPayer(GENESIS) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(GENESIS) .payingWith(PAYING_ACCOUNT) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( freezeAbort().payingWith(GENESIS), - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2334,7 +2278,7 @@ final HapiSpec scheduledFreezeWorksAsExpected() { // Currently this cannot be run as HapiTest because it stops the captive nodes. @BddMethodIsNotATest final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabled) { - final byte[] poeticUpgradeHash = ScheduleUtils.getPoeticUpgradeHash(); + final byte[] poeticUpgradeHash = getPoeticUpgradeHash(); if (isLongTermEnabled) { return defaultHapiSpec("ScheduledFreezeWithUnauthorizedPayerFails") @@ -2362,7 +2306,7 @@ final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabl // there are no throttles for freeze and we deeply check with // long term enabled .hasPrecheck(BUSY), - overriding(SCHEDULING_WHITELIST, defaultWhitelist)); + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT)); } return defaultHapiSpec("ScheduledFreezeWithUnauthorizedPayerFails") .given( @@ -2382,18 +2326,18 @@ final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabl .withEntityMemo(randomUppercase(100)) .designatingPayer(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .payingWith(PAYING_ACCOUNT) .alsoSigningWith(PAYING_ACCOUNT_2) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( freezeAbort().payingWith(GENESIS), - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2403,7 +2347,6 @@ final HapiSpec scheduledFreezeWithUnauthorizedPayerFails(boolean isLongTermEnabl })); } - // @todo('9973') Need to work out why this does not actually execute @HapiTest @Order(26) final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { @@ -2417,17 +2360,17 @@ final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { .withEntityMemo(randomUppercase(100)) .designatingPayer(FREEZE_ADMIN) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(FREEZE_ADMIN) .payingWith(PAYING_ACCOUNT) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2437,7 +2380,6 @@ final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { })); } - // @todo('9973') Work out permissioned file update issues @HapiTest @Order(25) final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { @@ -2453,17 +2395,17 @@ final HapiSpec scheduledPermissionedFileUpdateUnauthorizedPayerFails() { .withEntityMemo(randomUppercase(100)) .designatingPayer(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(PAYING_ACCOUNT_2, FREEZE_ADMIN) .payingWith(PAYING_ACCOUNT) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2486,18 +2428,18 @@ final HapiSpec scheduledSystemDeleteWorksAsExpected() { .withEntityMemo(randomUppercase(100)) .designatingPayer(SYSTEM_DELETE_ADMIN) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(SYSTEM_DELETE_ADMIN) .payingWith(PAYING_ACCOUNT) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), getFileInfo("misc").nodePayment(1_234L).hasAnswerOnlyPrecheck(INVALID_FILE_ID), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2521,18 +2463,18 @@ final HapiSpec hapiTestScheduledSystemDeleteUnauthorizedPayerFails() { .withEntityMemo(randomUppercase(100)) .designatingPayer(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), getFileInfo("misc").nodePayment(1_234L), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2559,12 +2501,10 @@ final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEna .designatingPayer(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) // we are always busy with long term enabled in this case - // because - // there are no throttles for SystemDelete and we deeply check - // with - // long term enabled + // because there are no throttles for SystemDelete and we deeply + // check with long term enabled .hasPrecheck(BUSY), - overriding(SCHEDULING_WHITELIST, defaultWhitelist)); + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT)); } return defaultHapiSpec("ScheduledSystemDeleteUnauthorizedPayerFails") @@ -2578,18 +2518,18 @@ final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEna .withEntityMemo(randomUppercase(100)) .designatingPayer(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) - .via(successTxn)) + .via(SUCCESS_TXN)) .when(scheduleSign(A_SCHEDULE) .alsoSigningWith(PAYING_ACCOUNT_2) .payingWith(PAYING_ACCOUNT) - .via(signTxn) + .via(SIGN_TX) .hasKnownStatus(SUCCESS)) .then( - overriding(SCHEDULING_WHITELIST, defaultWhitelist), + overriding(SCHEDULING_WHITELIST, WHITELIST_DEFAULT), getScheduleInfo(A_SCHEDULE).isExecuted(), getFileInfo("misc").nodePayment(1_234L), withOpContext((spec, opLog) -> { - var triggeredTx = getTxnRecord(successTxn).scheduled(); + var triggeredTx = getTxnRecord(SUCCESS_TXN).scheduled(); allRunFor(spec, triggeredTx); Assertions.assertEquals( @@ -2599,8 +2539,8 @@ final HapiSpec scheduledSystemDeleteUnauthorizedPayerFails(boolean isLongTermEna })); } - // @todo('') Work out why we get `PLATFORM_TRANSACTION_NOT_CREATED` instead of `OK` for UncheckedSubmit... - // This appears to come from `blockingOrder` call in `when` clause + // @todo('FUTURE') Work out why we get `PLATFORM_TRANSACTION_NOT_CREATED` instead of `OK` + // for UncheckedSubmit... This appears to come from `blockingOrder` call in `when` clause final HapiSpec congestionPricingAffectsImmediateScheduleExecution() { var artificialLimits = protoDefsFromResource("testSystemFiles/artificial-limits-congestion.json"); var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json"); @@ -2680,7 +2620,7 @@ final HapiSpec congestionPricingAffectsImmediateScheduleExecution() { minCongestionPeriod, HapiSpecSetup.getDefaultNodeProps().get(minCongestionPeriod), SCHEDULING_WHITELIST, - defaultWhitelist)), + WHITELIST_DEFAULT)), cryptoTransfer(HapiCryptoTransfer.tinyBarsFromTo(GENESIS, FUNDING, 1)) .payingWith(GENESIS), getScheduleInfo(A_SCHEDULE).isExecuted(), @@ -2711,20 +2651,11 @@ final HapiSpec suiteCleanup() { .when() .then(fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_WHITELIST, defaultWhitelist))); + .overridingProps(Map.of(SCHEDULING_WHITELIST, WHITELIST_DEFAULT))); } private T getTestConfig(Class configClass) { final TestConfigBuilder builder = new TestConfigBuilder(configClass); return builder.getOrCreateConfig().getConfigData(configClass); } - - public static HapiSpecOperation addAllToWhitelist() { - final List whitelistNames = new LinkedList<>(); - for (final HederaFunctionality enumValue : HederaFunctionality.values()) { - whitelistNames.add(enumValue.protoName()); - } - final String fullWhitelist = String.join(",", whitelistNames); - return overriding(SCHEDULING_WHITELIST, fullWhitelist); - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java index a16e2e10de9c..306e9befe37e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermExecutionSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Hedera Hashgraph, LLC + * Copyright (C) 2022-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,9 +47,49 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.poeticUpgradeLoc; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.standardUpdateFile; -import static com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs.ORIG_FILE; -import static com.hedera.services.bdd.suites.schedule.ScheduleExecutionSpecs.transferListCheck; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BASIC_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATE_TX; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_TX_EXPIRY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FAILED_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.FALSE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.LUCKY_RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ORIG_FILE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT_2; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULE_CREATE_FEE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_LONG_TERM_ENABLED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_MAX_TXN_PER_SECOND; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_1; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_2; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_3; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIGN_TX; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIMPLE_UPDATE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SUCCESS_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.THREE_SIG_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TRANSACTION_NOT_SCHEDULED; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TRIGGERING_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.VALID_SCHEDULE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WEIRDLY_POPULAR_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WEIRDLY_POPULAR_KEY_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_CONSENSUS_TIMESTAMP; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_RECORD_ACCOUNT_ID; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_SCHEDULE_ID; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_TRANSACTION_VALID_START; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WRONG_TRANSFER_LIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.disableLongTermScheduledTransactions; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.enableLongTermScheduledTransactions; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.getPoeticUpgradeHash; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.setLongTermScheduledTransactionsToDefault; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.transferListCheck; import static com.hedera.services.bdd.suites.utils.sysfiles.serdes.ThrottleDefsLoader.protoDefsFromResource; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; @@ -68,13 +108,9 @@ import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; import com.hedera.services.bdd.suites.HapiSuite; import java.time.Instant; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -84,50 +120,6 @@ public class ScheduleLongTermExecutionSpecs extends HapiSuite { private static final Logger LOG = LogManager.getLogger(ScheduleLongTermExecutionSpecs.class); - private static final String SCHEDULING_LONG_TERM_ENABLED = "scheduling.longTermEnabled"; - private static final String DEFAULT_LONG_TERM_ENABLED = - HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_LONG_TERM_ENABLED); - - private static final String LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS = "ledger.schedule.txExpiryTimeSecs"; - private static final String DEFAULT_TX_EXPIRY = - HapiSpecSetup.getDefaultNodeProps().get(LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS); - private static final String PAYING_ACCOUNT = "payingAccount"; - private static final String RECEIVER = "receiver"; - private static final String SENDER = "sender"; - public static final String SENDER_TXN = "senderTxn"; - private static final String BASIC_XFER = "basicXfer"; - private static final String CREATE_TX = "createTx"; - private static final String SIGN_TX = "signTx"; - private static final String SCHEDULED_TRANSACTION_BE_SUCCESSFUL = "Scheduled transaction be successful!"; - private static final String TRIGGERING_TXN = "triggeringTxn"; - private static final String PAYING_ACCOUNT_2 = "payingAccount2"; - private static final String FALSE = "false"; - private static final String VALID_SCHEDULE = "validSchedule"; - private static final String SUCCESS_TXN = "successTxn"; - private static final String PAYER_TXN = "payerTxn"; - private static final String WRONG_RECORD_ACCOUNT_ID = "Wrong record account ID!"; - private static final String TRANSACTION_NOT_SCHEDULED = "Transaction not scheduled!"; - private static final String WRONG_SCHEDULE_ID = "Wrong schedule ID!"; - private static final String SCHEDULING_MAX_TXN_PER_SECOND = "scheduling.maxTxnPerSecond"; - private static final String WRONG_TRANSACTION_VALID_START = "Wrong transaction valid start!"; - private static final String WRONG_CONSENSUS_TIMESTAMP = "Wrong consensus timestamp!"; - private static final String WRONG_TRANSFER_LIST = "Wrong transfer list!"; - private static final String SIMPLE_UPDATE = "SimpleUpdate"; - private static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; - private static final String PAYING_ACCOUNT_TXN = "payingAccountTxn"; - private static final String LUCKY_RECEIVER = "luckyReceiver"; - private static final String SCHEDULE_CREATE_FEE = "scheduleCreateFee"; - private static final String FAILED_XFER = "failedXfer"; - private static final String WEIRDLY_POPULAR_KEY = "weirdlyPopularKey"; - private static final String SENDER_1 = "sender1"; - private static final String SENDER_2 = "sender2"; - private static final String SENDER_3 = "sender3"; - private static final String WEIRDLY_POPULAR_KEY_TXN = "weirdlyPopularKeyTxn"; - private static final String THREE_SIG_XFER = "threeSigXfer"; - private static final String SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL = - "Scheduled transaction should not be successful!"; - private static final String PAYER = "payer"; - public static void main(String... args) { new ScheduleLongTermExecutionSpecs().runSuiteSync(); } @@ -205,7 +197,7 @@ final HapiSpec executionWithCustomPayerWorks() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); Instant triggerTime = Instant.ofEpochSecond( triggeringTx @@ -312,7 +304,7 @@ final HapiSpec executionWithCustomPayerAndAdminKeyWorks() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); Instant triggerTime = Instant.ofEpochSecond( triggeringTx @@ -418,7 +410,7 @@ final HapiSpec executionWithCustomPayerWhoSignsAtCreationAsPayerWorks() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); Instant triggerTime = Instant.ofEpochSecond( triggeringTx @@ -520,7 +512,7 @@ public HapiSpec executionWithDefaultPayerWorks() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); Instant triggerTime = Instant.ofEpochSecond( triggeringTx @@ -627,7 +619,7 @@ public HapiSpec executionWithContractCallWorksAtExpiry() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); Assertions.assertTrue(triggeredTx .getResponseRecord() @@ -677,7 +669,7 @@ public HapiSpec executionWithContractCreateWorksAtExpiry() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); Assertions.assertTrue( triggeredTx.getResponseRecord().getReceipt().hasContractID()); @@ -736,7 +728,7 @@ public HapiSpec executionWithDefaultPayerButNoFundsFails() { Assertions.assertEquals( INSUFFICIENT_PAYER_BALANCE, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL); + ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -808,7 +800,7 @@ public HapiSpec executionWithCustomPayerButNoFundsFails() { Assertions.assertEquals( INSUFFICIENT_PAYER_BALANCE, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL); + ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -895,7 +887,7 @@ public HapiSpec executionWithCustomPayerButAccountDeletedFails() { Assertions.assertEquals( INSUFFICIENT_PAYER_BALANCE, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL); + ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -943,7 +935,7 @@ public HapiSpec executionWithInvalidAccountAmountsFails() { Assertions.assertEquals( INVALID_ACCOUNT_AMOUNTS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL); + ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -988,7 +980,7 @@ public HapiSpec executionWithCryptoInsufficientAccountBalanceFails() { Assertions.assertEquals( INSUFFICIENT_ACCOUNT_BALANCE, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL); + ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -1035,7 +1027,7 @@ public HapiSpec executionWithCryptoSenderDeletedFails() { Assertions.assertEquals( ACCOUNT_DELETED, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_SHOULD_NOT_BE_SUCCESSFUL); + ScheduleUtils.SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -1131,7 +1123,7 @@ final HapiSpec scheduledFreezeWorksAsExpected() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -1211,7 +1203,7 @@ final HapiSpec scheduledPermissionedFileUpdateWorksAsExpected() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -1302,7 +1294,7 @@ final HapiSpec scheduledSystemDeleteWorksAsExpected() { Assertions.assertEquals( SUCCESS, triggeredTx.getResponseRecord().getReceipt().getStatus(), - SCHEDULED_TRANSACTION_BE_SUCCESSFUL); + SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED); })); } @@ -1494,55 +1486,4 @@ final HapiSpec futureThrottlesAreRespected() { protected Logger getResultsLogger() { return LOG; } - - public static HapiSpec enableLongTermScheduledTransactions() { - return defaultHapiSpec("EnableLongTermScheduledTransactions") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, "true"))); - } - - public static HapiSpec disableLongTermScheduledTransactions() { - return defaultHapiSpec("DisableLongTermScheduledTransactions") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, FALSE))); - } - - public static HapiSpec setLongTermScheduledTransactionsToDefault() { - return defaultHapiSpec("SetLongTermScheduledTransactionsToDefault") - .given() - .when() - .then(fileUpdate(APP_PROPERTIES) - .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, DEFAULT_LONG_TERM_ENABLED))); - } - - public static List withAndWithoutLongTermEnabled(Supplier> getSpecs) { - List list = new ArrayList<>(); - list.add(disableLongTermScheduledTransactions()); - list.addAll(getSpecs.get()); - list.add(enableLongTermScheduledTransactions()); - var withEnabled = getSpecs.get(); - withEnabled.forEach(s -> s.appendToName("WithLongTermEnabled")); - list.addAll(withEnabled); - list.add(setLongTermScheduledTransactionsToDefault()); - return list; - } - - public static List withAndWithoutLongTermEnabled(Function> getSpecs) { - List list = new ArrayList<>(); - list.add(disableLongTermScheduledTransactions()); - list.addAll(getSpecs.apply(false)); - list.add(enableLongTermScheduledTransactions()); - var withEnabled = getSpecs.apply(true); - withEnabled.forEach(s -> s.appendToName("WithLongTermEnabled")); - list.addAll(withEnabled); - list.add(setLongTermScheduledTransactionsToDefault()); - return list; - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java index 62b31aeb5f67..42d34cf788d1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleLongTermSignSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,13 +42,35 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.A_SENDER_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BASIC_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BEFORE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATE_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATION; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFERRED_CREATION; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFERRED_FALL; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFERRED_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.EXTRA_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.NEW_SENDER_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER_TXN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SHARED_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.THREE_SIG_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TOKEN_A; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TWO_SIG_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.enableLongTermScheduledTransactions; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.setLongTermScheduledTransactionsToDefault; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_NEW_VALID_SIGNATURES; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SOME_SIGNATURES_WERE_INVALID; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.OverlappingKeyGenerator; import com.hedera.services.bdd.suites.HapiSuite; @@ -63,29 +85,6 @@ public class ScheduleLongTermSignSpecs extends HapiSuite { private static final String suiteWhitelist = "CryptoCreate,ConsensusSubmitMessage,CryptoTransfer"; - public static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; - private static final String defaultWhitelist = - HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST); - private static final String NEW_SKEY = "newSKey"; - private static final String SENDER = "sender"; - private static final String RECEIVER = "receiver"; - private static final String TOKEN_A = "tokenA"; - private static final String SENDER_TXN = "senderTxn"; - private static final String PAYER = "payer"; - private static final String EXTRA_KEY = "extraKey"; - private static final String BASIC_XFER = "basicXfer"; - private static final String ADMIN = "admin"; - private static final String CREATE_TXN = "createTxn"; - private static final String THREE_SIG_XFER = "threeSigXfer"; - private static final String TWO_SIG_XFER = "twoSigXfer"; - private static final String SHARED_KEY = "sharedKey"; - private static final String DEFERRED_CREATION = "deferredCreation"; - private static final String CREATION = "creation"; - private static final String A_SENDER_TXN = "aSenderTxn"; - private static final String BEFORE = "before"; - private static final String DEFERRED_XFER = "deferredXfer"; - private static final String DEFERRED_FALL = "deferredFall"; - public static void main(String... args) { new ScheduleLongTermSignSpecs().runSuiteSync(); } @@ -93,7 +92,7 @@ public static void main(String... args) { @Override public List getSpecsInSuite() { return List.of( - ScheduleLongTermExecutionSpecs.enableLongTermScheduledTransactions(), + enableLongTermScheduledTransactions(), suiteSetup(), triggersUponAdditionalNeededSig(), sharedKeyWorksAsExpected(), @@ -112,7 +111,7 @@ public List getSpecsInSuite() { reductionInSigningReqsAllowsTxnToGoThrough(), reductionInSigningReqsAllowsTxnToGoThroughAtExpiryWithNoWaitForExpiry(), suiteCleanup(), - ScheduleLongTermExecutionSpecs.setLongTermScheduledTransactionsToDefault()); + setLongTermScheduledTransactionsToDefault()); } final HapiSpec suiteCleanup() { @@ -121,7 +120,7 @@ final HapiSpec suiteCleanup() { .when() .then(fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) - .overridingProps(Map.of(SCHEDULING_WHITELIST, defaultWhitelist))); + .overridingProps(Map.of(SCHEDULING_WHITELIST, WHITELIST_DEFAULT))); } final HapiSpec suiteSetup() { @@ -142,7 +141,7 @@ final HapiSpec changeInNestedSigningReqsRespected() { String receiver = "Y"; String schedule = "Z"; String senderKey = "sKey"; - String newSenderKey = NEW_SKEY; + String newSenderKey = NEW_SENDER_KEY; return defaultHapiSpec("ChangeInNestedSigningReqsRespectedAtExpiry") .given( @@ -201,7 +200,7 @@ final HapiSpec reductionInSigningReqsAllowsTxnToGoThrough() { String receiver = "Y"; String schedule = "Z"; String senderKey = "sKey"; - String newSenderKey = NEW_SKEY; + String newSenderKey = NEW_SENDER_KEY; return defaultHapiSpec("ReductionInSigningReqsAllowsTxnToGoThroughAtExpiry") .given( @@ -251,7 +250,7 @@ final HapiSpec reductionInSigningReqsAllowsTxnToGoThroughAtExpiryWithNoWaitForEx String receiver = "Y"; String schedule = "Z"; String senderKey = "sKey"; - String newSenderKey = NEW_SKEY; + String newSenderKey = NEW_SENDER_KEY; return defaultHapiSpec("ReductionInSigningReqsAllowsTxnToGoThroughAtExpiryWithNoWaitForExpiry") .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java index 5671d5defbc8..4c723035768b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleRecordSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Hedera Hashgraph, LLC + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,11 +44,26 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BEGIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.CREATION; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.INSOLVENT_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.OTHER_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIMPLE_UPDATE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIMPLE_XFER_SCHEDULE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.STAKING_FEES_NODE_REWARD_PERCENTAGE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.STAKING_FEES_STAKING_REWARD_PERCENTAGE; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TRIGGER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TWO_SIG_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.UNWILLING_PAYER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_MINIMUM; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.scheduledVersionOf; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_PAYER_BALANCE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; @@ -72,20 +87,6 @@ @HapiTestSuite public class ScheduleRecordSpecs extends HapiSuite { private static final Logger log = LogManager.getLogger(ScheduleRecordSpecs.class); - private static final String TWO_SIG_XFER = "twoSigXfer"; - private static final String BEGIN = "begin"; - private static final String SIMPLE_XFER_SCHEDULE = "simpleXferSchedule"; - private static final String ADMIN = "admin"; - private static final String CREATION = "creation"; - private static final String PAYER = "payer"; - private static final String PAYING_SENDER = "payingSender"; - private static final String OTHER_PAYER = "otherPayer"; - private static final String SIMPLE_UPDATE = "SimpleUpdate"; - private static final String TRIGGER = "trigger"; - private static final String INSOLVENT_PAYER = "insolventPayer"; - private static final String SCHEDULE = "schedule"; - private static final String UNWILLING_PAYER = "unwillingPayer"; - private static final String RECEIVER = "receiver"; public static void main(String... args) { new ScheduleRecordSpecs().runSuiteAsync(); @@ -272,10 +273,6 @@ public HapiSpec canScheduleChunkedMessages() { HapiSpecSetup.getDefaultNodeProps().get(STAKING_FEES_STAKING_REWARD_PERCENTAGE)))); } - static TransactionID scheduledVersionOf(TransactionID txnId) { - return txnId.toBuilder().setScheduled(true).build(); - } - @HapiTest public HapiSpec schedulingTxnIdFieldsNotAllowed() { return defaultHapiSpec("SchedulingTxnIdFieldsNotAllowed") diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java index e7302580c0f4..373568635169 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleSignSpecs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,10 +44,24 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; -import static com.hedera.services.bdd.suites.schedule.ScheduleLongTermExecutionSpecs.withAndWithoutLongTermEnabled; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ADMIN; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.BASIC_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFAULT_TX_EXPIRY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.DEFERRED_XFER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.NEW_SENDER_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RANDOM_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULING_WHITELIST; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SHARED_KEY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SOMEBODY; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TOKEN_A; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.TWO_SIG_XFER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_DEFAULT; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.WHITELIST_MINIMUM; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.withAndWithoutLongTermEnabled; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_NEW_VALID_SIGNATURES; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK; @@ -59,7 +73,6 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestSuite; import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecSetup; import com.hedera.services.bdd.spec.keys.ControlForKey; import com.hedera.services.bdd.spec.keys.OverlappingKeyGenerator; import com.hedera.services.bdd.suites.HapiSuite; @@ -79,23 +92,6 @@ public class ScheduleSignSpecs extends HapiSuite { private static final int SCHEDULE_EXPIRY_TIME_SECS = 10; private static final int SCHEDULE_EXPIRY_TIME_MS = SCHEDULE_EXPIRY_TIME_SECS * 1000; - private static final String defaultTxExpiry = - HapiSpecSetup.getDefaultNodeProps().get(LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS); - private static final String defaultWhitelist = - HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST); - private static final String NEW_SENDER_KEY = "newSKey"; - private static final String DEFERRED_XFER = "deferredXfer"; - private static final String ADMIN = "admin"; - private static final String SHARED_KEY = "sharedKey"; - private static final String TWO_SIG_XFER = "twoSigXfer"; - private static final String PAYER = "payer"; - private static final String SOMEBODY = "somebody"; - private static final String RANDOM_KEY = "randomKey"; - private static final String SENDER = "sender"; - private static final String RECEIVER = "receiver"; - private static final String BASIC_XFER = "basicXfer"; - private static final String TOKEN_A = "tokenA"; - public static void main(String... args) { new ScheduleSignSpecs().runSuiteSync(); } @@ -138,8 +134,8 @@ private HapiSpec suiteCleanup() { .then(fileUpdate(APP_PROPERTIES) .payingWith(ADDRESS_BOOK_CONTROL) .overridingProps(Map.of( - LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS, defaultTxExpiry, - SCHEDULING_WHITELIST, defaultWhitelist))); + LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS, DEFAULT_TX_EXPIRY, + SCHEDULING_WHITELIST, WHITELIST_DEFAULT))); } @HapiTest @@ -808,7 +804,7 @@ public HapiSpec signFailsDueToDeletedExpiration() { .hasPrecheckFrom(OK, INVALID_SCHEDULE_ID) .hasKnownStatusFrom(INVALID_SCHEDULE_ID), getScheduleInfo(TWO_SIG_XFER).hasCostAnswerPrecheck(INVALID_SCHEDULE_ID), - overriding(LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS, "" + defaultTxExpiry)); + overriding(LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS, "" + DEFAULT_TX_EXPIRY)); } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java index f121d10fa819..d40f24e5c10a 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Hedera Hashgraph, LLC + * Copyright (C) 2021-2024 Hedera Hashgraph, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,59 +16,131 @@ package com.hedera.services.bdd.suites.schedule; +import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.poeticUpgradeLoc; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.HapiSpecOperation; +import com.hedera.services.bdd.spec.HapiSpecSetup; +import com.hedera.services.bdd.spec.queries.meta.HapiGetTxnRecord; +import com.hedera.services.bdd.suites.HapiSuite; +import com.hederahashgraph.api.proto.java.AccountAmount; +import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.SchedulableTransactionBody; import com.hederahashgraph.api.proto.java.SchedulableTransactionBody.Builder; import com.hederahashgraph.api.proto.java.TransactionBody; +import com.hederahashgraph.api.proto.java.TransactionID; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; public final class ScheduleUtils { - static final String SENDER_TXN = "senderTxn"; - static final String STAKING_FEES_NODE_REWARD_PERCENTAGE = "staking.fees.nodeRewardPercentage"; - static final String STAKING_FEES_STAKING_REWARD_PERCENTAGE = "staking.fees.stakingRewardPercentage"; - static final String SCHEDULING_MAX_TXN_PER_SECOND = "scheduling.maxTxnPerSecond"; - static final String SCHEDULING_LONG_TERM_ENABLED = "scheduling.longTermEnabled"; + static final String ACCOUNT = "civilian"; + static final String ADMIN = "admin"; + static final String A_SCHEDULE = "validSchedule"; + static final String A_SENDER_TXN = "aSenderTxn"; + static final String A_TOKEN = "token"; + static final String BASIC_XFER = "basicXfer"; + static final String BEFORE = "before"; + static final String BEGIN = "begin"; + static final String CONTINUE = "continue"; + static final String COPYCAT = "copycat"; + static final String CREATE_TX = "createTx"; + static final String CREATE_TXN = "createTx"; + static final String CREATION = "creation"; + static final String DEFERRED_CREATION = "deferredCreation"; + static final String DEFERRED_FALL = "deferredFall"; + static final String DEFERRED_XFER = "deferredXfer"; + static final String DESIGNATING_PAYER = "1.2.3"; + static final String ENTITY_MEMO = "This was Mr. Bleaney's room. He stayed"; + static final String EXTRA_KEY = "extraKey"; + static final String FAILED_XFER = "failedXfer"; + static final String FAILING_TXN = "failingTxn"; + static final String FALSE = "false"; + static final String FIRST_PAYER = "firstPayer"; + static final String INSOLVENT_PAYER = "insolventPayer"; static final String LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS = "ledger.schedule.txExpiryTimeSecs"; - static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; + static final String LEDGER_TOKEN_TRANSFERS_MAX_LEN = "ledger.tokenTransfers.maxLen"; + static final String LEDGER_TRANSFERS_MAX_LEN = "ledger.transfers.maxLen"; + static final String LUCKY_RECEIVER = "luckyReceiver"; + static final String NEVER_TO_BE = "neverToBe"; + static final String NEW_SENDER_KEY = "newSKey"; + static final String ONLY_BODY = "onlyBody"; + static final String ONLY_BODY_AND_ADMIN_KEY = "onlyBodyAndAdminKey"; + static final String ONLY_BODY_AND_MEMO = "onlyBodyAndMemo"; + static final String ONLY_BODY_AND_PAYER = "onlyBodyAndPayer"; + static final String ORIGINAL = "original"; + static final String OTHER_PAYER = "otherPayer"; + static final String PAYER = "payer"; + static final String PAYER_TXN = "payerTxn"; static final String PAYING_ACCOUNT = "payingAccount"; + static final String PAYING_ACCOUNT_2 = "payingAccount2"; + static final String PAYING_ACCOUNT_TXN = "payingAccountTxn"; + static final String PAYING_SENDER = "payingSender"; + static final String RANDOM_KEY = "randomKey"; + static final String RANDOM_MSG = + "Little did they care who danced between / And little she by whom her dance was seen"; static final String RECEIVER = "receiver"; + static final String RECEIVER_A = "receiverA"; + static final String RECEIVER_B = "receiverB"; + static final String RECEIVER_C = "receiverC"; + static final String SCHEDULE = "schedule"; + static final String SCHEDULED_TRANSACTION_MUST_NOT_SUCCEED = "Scheduled transaction must not succeed"; + static final String SCHEDULED_TRANSACTION_MUST_SUCCEED = "Scheduled transaction must succeed"; + static final String SCHEDULE_CREATE_FEE = "scheduleCreateFee"; + static final String SCHEDULE_PAYER = "schedulePayer"; + static final String SCHEDULING_LONG_TERM_ENABLED = "scheduling.longTermEnabled"; + static final String SCHEDULING_MAX_TXN_PER_SECOND = "scheduling.maxTxnPerSecond"; + static final String SCHEDULING_WHITELIST = "scheduling.whitelist"; + static final String SECOND_PAYER = "secondPayer"; static final String SENDER = "sender"; - static final String BASIC_XFER = "basicXfer"; - static final String CREATE_TX = "createTx"; + static final String SENDER_1 = "sender1"; + static final String SENDER_2 = "sender2"; + static final String SENDER_3 = "sender3"; + static final String SENDER_TXN = "senderTxn"; + static final String SHARED_KEY = "sharedKey"; static final String SIGN_TX = "signTx"; + static final String SIGN_TXN = "signTx"; + static final String SIMPLE_UPDATE = "SimpleUpdate"; + static final String SIMPLE_XFER_SCHEDULE = "simpleXferSchedule"; + static final String SOMEBODY = "somebody"; + static final String STAKING_FEES_NODE_REWARD_PERCENTAGE = "staking.fees.nodeRewardPercentage"; + static final String STAKING_FEES_STAKING_REWARD_PERCENTAGE = "staking.fees.stakingRewardPercentage"; + static final String SUCCESS_TXN = "successTxn"; + static final String SUPPLY_KEY = "supplyKey"; + static final String THREE_SIG_XFER = "threeSigXfer"; + static final String TOKENS_NFTS_ARE_ENABLED = "tokens.nfts.areEnabled"; + static final String TOKENS_NFTS_MAX_BATCH_SIZE_MINT = "tokens.nfts.maxBatchSizeMint"; + static final String TOKEN_A = "tokenA"; + static final String TRANSACTION_NOT_SCHEDULED = "Transaction not scheduled!"; + static final String TREASURY = "treasury"; + static final String TRIGGER = "trigger"; static final String TRIGGERING_TXN = "triggeringTxn"; - static final String PAYING_ACCOUNT_2 = "payingAccount2"; - static final String FALSE = "false"; + static final String TWO_SIG_XFER = "twoSigXfer"; + static final String UNWILLING_PAYER = "unwillingPayer"; static final String VALID_SCHEDULE = "validSchedule"; - static final String SUCCESS_TXN = "successTxn"; - static final String PAYER_TXN = "payerTxn"; + static final String VALID_SCHEDULED_TXN = "validScheduledTxn"; + static final String WEIRDLY_POPULAR_KEY = "weirdlyPopularKey"; + static final String WEIRDLY_POPULAR_KEY_TXN = "weirdlyPopularKeyTxn"; + static final String WRONG_CONSENSUS_TIMESTAMP = "Wrong consensus timestamp!"; static final String WRONG_RECORD_ACCOUNT_ID = "Wrong record account ID!"; - static final String TRANSACTION_NOT_SCHEDULED = "Transaction not scheduled!"; static final String WRONG_SCHEDULE_ID = "Wrong schedule ID!"; static final String WRONG_TRANSACTION_VALID_START = "Wrong transaction valid start!"; - static final String WRONG_CONSENSUS_TIMESTAMP = "Wrong consensus timestamp!"; static final String WRONG_TRANSFER_LIST = "Wrong transfer list!"; - static final String SIMPLE_UPDATE = "SimpleUpdate"; - static final String PAYING_ACCOUNT_TXN = "payingAccountTxn"; - static final String LUCKY_RECEIVER = "luckyReceiver"; - static final String SCHEDULE_CREATE_FEE = "scheduleCreateFee"; - static final String FAILED_XFER = "failedXfer"; - static final String WEIRDLY_POPULAR_KEY = "weirdlyPopularKey"; - static final String SENDER_1 = "sender1"; - static final String SENDER_2 = "sender2"; - static final String SENDER_3 = "sender3"; - static final String WEIRDLY_POPULAR_KEY_TXN = "weirdlyPopularKeyTxn"; - static final String THREE_SIG_XFER = "threeSigXfer"; - static final String PAYER = "payer"; + + static final byte[] ORIG_FILE = "SOMETHING".getBytes(); /** * Whitelist containing all of the non-query type transactions so we don't hit whitelist failures @@ -91,11 +163,18 @@ public final class ScheduleUtils { */ static final String WHITELIST_MINIMUM = "ConsensusSubmitMessage,ContractCall,CryptoCreate,CryptoTransfer,FileUpdate,SystemDelete,TokenBurn,TokenMint,Freeze"; - /** - * A whitelist guaranteed to contain every transaction type possible. Useful for specs that need to test scheduling - * a transaction that shouldn't work (e.g. a query). - */ - static final String WHITELIST_ALL = getWhitelistAll(); + + static final String WHITELIST_DEFAULT = HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_WHITELIST); + static final String DEFAULT_LONG_TERM_ENABLED = + HapiSpecSetup.getDefaultNodeProps().get(SCHEDULING_LONG_TERM_ENABLED); + static final String DEFAULT_TX_EXPIRY = + HapiSpecSetup.getDefaultNodeProps().get(LEDGER_SCHEDULE_TX_EXPIRY_TIME_SECS); + static final String DEFAULT_MAX_TRANSFER_LEN = + HapiSpecSetup.getDefaultNodeProps().get(LEDGER_TRANSFERS_MAX_LEN); + static final String DEFAULT_MAX_TOKEN_TRANSFER_LEN = + HapiSpecSetup.getDefaultNodeProps().get(LEDGER_TOKEN_TRANSFERS_MAX_LEN); + static final String DEFAULT_MAX_BATCH_SIZE_MINT = + HapiSpecSetup.getDefaultNodeProps().get(TOKENS_NFTS_MAX_BATCH_SIZE_MINT); private ScheduleUtils() {} @@ -187,7 +266,7 @@ static byte[] getPoeticUpgradeHash() { return poeticUpgradeHash; } - private static String getWhitelistAll() { + static String getWhitelistAll() { final List whitelistNames = new LinkedList<>(); for (final HederaFunctionality enumValue : HederaFunctionality.values()) { if (enumValue != HederaFunctionality.NONE) whitelistNames.add(enumValue.protoName()); @@ -196,4 +275,96 @@ private static String getWhitelistAll() { final String whitelistAll = String.join(",", whitelistNames); return whitelistAll; } + + static HapiSpecOperation addAllToWhitelist() { + final List whitelistNames = new LinkedList<>(); + for (final HederaFunctionality enumValue : HederaFunctionality.values()) { + whitelistNames.add(enumValue.protoName()); + } + final String fullWhitelist = String.join(",", whitelistNames); + return overriding(SCHEDULING_WHITELIST, fullWhitelist); + } + + static HapiSpec enableLongTermScheduledTransactions() { + return defaultHapiSpec("EnableLongTermScheduledTransactions") + .given() + .when() + .then(fileUpdate(HapiSuite.APP_PROPERTIES) + .payingWith(HapiSuite.ADDRESS_BOOK_CONTROL) + .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, "true"))); + } + + static HapiSpec disableLongTermScheduledTransactions() { + return defaultHapiSpec("DisableLongTermScheduledTransactions") + .given() + .when() + .then(fileUpdate(HapiSuite.APP_PROPERTIES) + .payingWith(HapiSuite.ADDRESS_BOOK_CONTROL) + .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, FALSE))); + } + + static HapiSpec setLongTermScheduledTransactionsToDefault() { + return defaultHapiSpec("SetLongTermScheduledTransactionsToDefault") + .given() + .when() + .then(fileUpdate(HapiSuite.APP_PROPERTIES) + .payingWith(HapiSuite.ADDRESS_BOOK_CONTROL) + .overridingProps(Map.of(SCHEDULING_LONG_TERM_ENABLED, DEFAULT_LONG_TERM_ENABLED))); + } + + static List withAndWithoutLongTermEnabled(Supplier> getSpecs) { + List list = new ArrayList<>(); + list.add(disableLongTermScheduledTransactions()); + list.addAll(getSpecs.get()); + list.add(enableLongTermScheduledTransactions()); + var withEnabled = getSpecs.get(); + withEnabled.forEach(s -> s.appendToName("WithLongTermEnabled")); + list.addAll(withEnabled); + list.add(setLongTermScheduledTransactionsToDefault()); + return list; + } + + static List withAndWithoutLongTermEnabled(Function> getSpecs) { + List list = new ArrayList<>(); + list.add(disableLongTermScheduledTransactions()); + list.addAll(getSpecs.apply(false)); + list.add(enableLongTermScheduledTransactions()); + var withEnabled = getSpecs.apply(true); + withEnabled.forEach(s -> s.appendToName("WithLongTermEnabled")); + list.addAll(withEnabled); + list.add(setLongTermScheduledTransactionsToDefault()); + return list; + } + + static boolean transferListCheck( + HapiGetTxnRecord triggered, + AccountID givingAccountID, + AccountID receivingAccountID, + AccountID payingAccountID, + Long amount) { + AccountAmount givingAmount = AccountAmount.newBuilder() + .setAccountID(givingAccountID) + .setAmount(-amount) + .build(); + + AccountAmount receivingAmount = AccountAmount.newBuilder() + .setAccountID(receivingAccountID) + .setAmount(amount) + .build(); + + var accountAmountList = triggered.getResponseRecord().getTransferList().getAccountAmountsList(); + System.out.println("accountAmountList: " + accountAmountList); + boolean payerHasPaid = + accountAmountList.stream().anyMatch(a -> a.getAccountID().equals(payingAccountID) && a.getAmount() < 0); + System.out.println("payerHasPaid: " + payerHasPaid); + boolean amountHasBeenTransferred = + accountAmountList.contains(givingAmount) && accountAmountList.contains(receivingAmount); + System.out.println("amountHasBeenTransferred: " + amountHasBeenTransferred); + + return amountHasBeenTransferred && payerHasPaid; + } + + static TransactionID scheduledVersionOf(TransactionID txnId) { + return txnId.toBuilder().setScheduled(true).build(); + } } From 670823a65875b5ca04c540e51e2b981d17f185e5 Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Wed, 3 Jan 2024 13:26:26 -0600 Subject: [PATCH 80/80] fix: change cron job to once a day (#10570) Signed-off-by: Jeffrey Tang --- .github/workflows/flow-node-performance-tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flow-node-performance-tests.yaml b/.github/workflows/flow-node-performance-tests.yaml index 7ef567a0591d..47feb2a61a61 100644 --- a/.github/workflows/flow-node-performance-tests.yaml +++ b/.github/workflows/flow-node-performance-tests.yaml @@ -1,5 +1,5 @@ ## -# Copyright (C) 2022-2023 Hedera Hashgraph, LLC +# Copyright (C) 2022-2024 Hedera Hashgraph, LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ name: "Node: Performance Tests" on: schedule: - - cron: '0 */16 * * *' + - cron: '0 0 * * *' workflow_dispatch: defaults: