From c3cfc872b6f91e1069e6e030f10ed3525be0dd8f Mon Sep 17 00:00:00 2001 From: Matt Hess Date: Fri, 24 Jan 2025 13:40:07 -0700 Subject: [PATCH] chore: Reapply "FileService address book and node details" (#17543) Signed-off-by: Matt Hess --- .../workflows/handle/record/SystemSetup.java | 5 +- .../standalone/TransactionExecutorsTest.java | 13 +- .../service/file/impl/FileServiceImpl.java | 9 +- .../impl/handlers/FileGetContentsHandler.java | 29 ++--- .../file/impl/schemas/V0490FileSchema.java | 45 +------ .../src/main/java/module-info.java | 5 +- .../handlers/FileGetContentsHandlerTest.java | 8 +- .../spec/utilops/grouping/GroupingVerbs.java | 17 ++- .../suites/hip993/SystemFileExportsTest.java | 114 ++++++++++++++++-- 9 files changed, 164 insertions(+), 81 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java index 10280e33d89e..7d783c6413a1 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SystemSetup.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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. @@ -133,7 +133,8 @@ public SystemSetup( */ public void doGenesisSetup(@NonNull final Dispatch dispatch) { final var systemContext = systemContextFor(dispatch); - fileService.createSystemEntities(systemContext); + final var nodeStore = dispatch.handleContext().storeFactory().readableStore(ReadableNodeStore.class); + fileService.createSystemEntities(systemContext, nodeStore); } /** diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java index 6456994f07a7..5b3c3264546d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java @@ -53,7 +53,10 @@ import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.info.NodeInfoImpl; import com.hedera.node.app.records.BlockRecordService; +import com.hedera.node.app.service.addressbook.AddressBookService; +import com.hedera.node.app.service.addressbook.ReadableNodeStore; import com.hedera.node.app.service.addressbook.impl.AddressBookServiceImpl; +import com.hedera.node.app.service.addressbook.impl.ReadableNodeStoreImpl; import com.hedera.node.app.service.consensus.impl.ConsensusServiceImpl; import com.hedera.node.app.service.contract.impl.ContractServiceImpl; import com.hedera.node.app.service.file.FileService; @@ -391,8 +394,10 @@ private State genesisState(@NonNull final Map overrides) { NO_OP_METRICS, startupNetworks); final var writableStates = state.getWritableStates(FileService.NAME); + final var readableStates = state.getReadableStates(AddressBookService.NAME); + final var nodeStore = new ReadableNodeStoreImpl(readableStates); final var files = writableStates.get(V0490FileSchema.BLOBS_KEY); - genesisContentProviders(networkInfo, config).forEach((fileNum, provider) -> { + genesisContentProviders(nodeStore, config).forEach((fileNum, provider) -> { final var fileId = createFileID(fileNum, config); files.put( fileId, @@ -407,12 +412,12 @@ private State genesisState(@NonNull final Map overrides) { } private Map> genesisContentProviders( - @NonNull final NetworkInfo networkInfo, @NonNull final Configuration config) { + @NonNull final ReadableNodeStore nodeStore, @NonNull final Configuration config) { final var genesisSchema = new V0490FileSchema(); final var filesConfig = config.getConfigData(FilesConfig.class); return Map.of( - filesConfig.addressBook(), ignore -> genesisSchema.genesisAddressBook(networkInfo), - filesConfig.nodeDetails(), ignore -> genesisSchema.genesisNodeDetails(networkInfo), + filesConfig.addressBook(), ignore -> genesisSchema.nodeStoreAddressBook(nodeStore), + filesConfig.nodeDetails(), ignore -> genesisSchema.nodeStoreNodeDetails(nodeStore), filesConfig.feeSchedules(), genesisSchema::genesisFeeSchedules, filesConfig.exchangeRates(), genesisSchema::genesisExchangeRates, filesConfig.networkProperties(), genesisSchema::genesisNetworkProperties, diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java index 7c89fb23587a..cc5e766298f8 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/FileServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * Copyright (C) 2020-2025 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,12 +49,13 @@ public void registerSchemas(@NonNull final SchemaRegistry registry) { } /** - * Creates the system files in the given genesis context. + * Creates the system files in the given genesis context and the nodeStore data. * * @param context the genesis context + * @param nodeStore the ReadableNodeStore */ - public void createSystemEntities(@NonNull final SystemContext context) { - fileSchema.createGenesisAddressBookAndNodeDetails(context); + public void createSystemEntities(@NonNull final SystemContext context, @NonNull final ReadableNodeStore nodeStore) { + fileSchema.createGenesisAddressBookAndNodeDetails(context, nodeStore); fileSchema.createGenesisFeeSchedule(context); fileSchema.createGenesisExchangeRate(context); fileSchema.createGenesisNetworkProperties(context); diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java index ea0fc1852af1..edaff860f3b7 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/handlers/FileGetContentsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * Copyright (C) 2022-2025 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. @@ -34,6 +34,7 @@ import com.hedera.hapi.node.transaction.Response; import com.hedera.node.app.hapi.utils.CommonPbjConverters; import com.hedera.node.app.hapi.utils.fee.FileFeeBuilder; +import com.hedera.node.app.service.addressbook.ReadableNodeStore; import com.hedera.node.app.service.file.ReadableFileStore; import com.hedera.node.app.service.file.impl.base.FileQueryBase; import com.hedera.node.app.service.file.impl.schemas.V0490FileSchema; @@ -45,7 +46,6 @@ import com.hederahashgraph.api.proto.java.FeeData; import com.hederahashgraph.api.proto.java.ResponseType; import com.swirlds.config.api.Configuration; -import com.swirlds.state.lifecycle.info.NetworkInfo; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; @@ -60,7 +60,6 @@ public class FileGetContentsHandler extends FileQueryBase { private final FileFeeBuilder usageEstimator; private final V0490FileSchema genesisSchema; - private final NetworkInfo networkInfo; /** * Constructs a {@link FileGetContentsHandler} with the given {@link FileFeeBuilder}. @@ -68,12 +67,9 @@ public class FileGetContentsHandler extends FileQueryBase { */ @Inject public FileGetContentsHandler( - @NonNull final FileFeeBuilder usageEstimator, - @NonNull final V0490FileSchema genesisSchema, - @NonNull final NetworkInfo networkInfo) { + @NonNull final FileFeeBuilder usageEstimator, @NonNull final V0490FileSchema genesisSchema) { this.usageEstimator = requireNonNull(usageEstimator); this.genesisSchema = requireNonNull(genesisSchema); - this.networkInfo = networkInfo; } @Override @@ -102,10 +98,11 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti public @NonNull Fees computeFees(@NonNull QueryContext queryContext) { final var query = queryContext.query(); final var fileStore = queryContext.createStore(ReadableFileStore.class); + final var nodeStore = queryContext.createStore(ReadableNodeStore.class); final var op = query.fileGetContentsOrThrow(); final var fileId = op.fileIDOrElse(FileID.DEFAULT); final var responseType = op.headerOrElse(QueryHeader.DEFAULT).responseType(); - final FileContents fileContents = contentFile(fileId, fileStore, queryContext.configuration()); + final FileContents fileContents = contentFile(fileId, fileStore, queryContext.configuration(), nodeStore); return queryContext .feeCalculator() .legacyCalculate(sigValueObj -> @@ -117,6 +114,7 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti requireNonNull(header); final var query = context.query(); final var fileStore = context.createStore(ReadableFileStore.class); + final var nodeStore = context.createStore(ReadableNodeStore.class); final var op = query.fileGetContentsOrThrow(); final var responseBuilder = FileGetContentsResponse.newBuilder(); final var fileId = op.fileIDOrThrow(); @@ -124,7 +122,7 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti final var responseType = op.headerOrElse(QueryHeader.DEFAULT).responseType(); responseBuilder.header(header); if (header.nodeTransactionPrecheckCode() == OK && responseType != COST_ANSWER) { - final var content = contentFile(fileId, fileStore, context.configuration()); + final var content = contentFile(fileId, fileStore, context.configuration(), nodeStore); if (content == null) { responseBuilder.header(header.copyBuilder() .nodeTransactionPrecheckCode(INVALID_FILE_ID) @@ -143,18 +141,20 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti * @param fileID the file to get information about * @param fileStore the file store * @param config the configuration + * @param nodeStore the ReadableNodeStore * @return the content about the file */ private @Nullable FileContents contentFile( @NonNull final FileID fileID, @NonNull final ReadableFileStore fileStore, - @NonNull final Configuration config) { + @NonNull final Configuration config, + @NonNull final ReadableNodeStore nodeStore) { final var meta = fileStore.getFileMetadata(fileID); if (meta == null) { if (notGenesisCreation(fileID, config)) { return null; } else { - final var genesisContent = genesisContentProviders(config) + final var genesisContent = genesisContentProviders(config, nodeStore) .getOrDefault(fileID.fileNum(), ignore -> EMPTY) .apply(config); return new FileContents(fileID, genesisContent); @@ -167,11 +167,12 @@ public void validate(@NonNull final QueryContext context) throws PreCheckExcepti } } - private Map> genesisContentProviders(@NonNull final Configuration config) { + private Map> genesisContentProviders( + @NonNull final Configuration config, @NonNull final ReadableNodeStore nodeStore) { final var filesConfig = config.getConfigData(FilesConfig.class); return Map.of( - filesConfig.addressBook(), ignore -> genesisSchema.genesisAddressBook(networkInfo), - filesConfig.nodeDetails(), ignore -> genesisSchema.genesisNodeDetails(networkInfo), + filesConfig.addressBook(), ignore -> genesisSchema.nodeStoreAddressBook(nodeStore), + filesConfig.nodeDetails(), ignore -> genesisSchema.nodeStoreNodeDetails(nodeStore), filesConfig.feeSchedules(), genesisSchema::genesisFeeSchedules, filesConfig.exchangeRates(), genesisSchema::genesisExchangeRates, filesConfig.networkProperties(), genesisSchema::genesisNetworkProperties, diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java index 79c8eb03a354..b0cdd7bdc6d7 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java @@ -51,7 +51,6 @@ import com.hedera.hapi.node.transaction.ThrottleDefinitions; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.addressbook.ReadableNodeStore; -import com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema; import com.hedera.node.app.spi.workflows.SystemContext; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.BootstrapConfig; @@ -64,8 +63,6 @@ import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.StateDefinition; -import com.swirlds.state.lifecycle.info.NetworkInfo; -import com.swirlds.state.lifecycle.info.NodeInfo; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.ByteArrayInputStream; @@ -166,9 +163,9 @@ public void migrate(@NonNull final MigrationContext ctx) { // ================================================================================================================ // Creates and loads the Address Book into state - public void createGenesisAddressBookAndNodeDetails(@NonNull final SystemContext systemContext) { + public void createGenesisAddressBookAndNodeDetails( + @NonNull final SystemContext systemContext, @NonNull final ReadableNodeStore nodeStore) { requireNonNull(systemContext); - final var networkInfo = systemContext.networkInfo(); final var filesConfig = systemContext.configuration().getConfigData(FilesConfig.class); final var bootstrapConfig = systemContext.configuration().getConfigData(BootstrapConfig.class); @@ -184,7 +181,7 @@ public void createGenesisAddressBookAndNodeDetails(@NonNull final SystemContext systemContext.dispatchCreation( TransactionBody.newBuilder() .fileCreate(FileCreateTransactionBody.newBuilder() - .contents(genesisAddressBook(networkInfo)) + .contents(nodeStoreAddressBook(nodeStore)) .keys(masterKey) .expirationTime(maxLifetimeExpiry(systemContext)) .build()) @@ -195,7 +192,7 @@ public void createGenesisAddressBookAndNodeDetails(@NonNull final SystemContext systemContext.dispatchCreation( TransactionBody.newBuilder() .fileCreate(FileCreateTransactionBody.newBuilder() - .contents(genesisNodeDetails(networkInfo)) + .contents(nodeStoreNodeDetails(nodeStore)) .keys(masterKey) .expirationTime(maxLifetimeExpiry(systemContext)) .build()) @@ -203,36 +200,6 @@ public void createGenesisAddressBookAndNodeDetails(@NonNull final SystemContext nodeInfoFileNum); } - public Bytes genesisAddressBook(@NonNull final NetworkInfo networkInfo) { - final var nodeAddresses = networkInfo.addressBook().stream() - .sorted(Comparator.comparingLong(NodeInfo::nodeId)) - .map(nodeInfo -> NodeAddress.newBuilder() - .nodeId(nodeInfo.nodeId()) - .nodeAccountId(nodeInfo.accountId()) - .rsaPubKey(nodeInfo.hexEncodedPublicKey()) - .serviceEndpoint(V053AddressBookSchema.endpointFor("1.0.0.0", 1)) - .build()) - .toList(); - return NodeAddressBook.PROTOBUF.toBytes( - NodeAddressBook.newBuilder().nodeAddress(nodeAddresses).build()); - } - - public Bytes genesisNodeDetails(@NonNull final NetworkInfo networkInfo) { - final var nodeDetails = networkInfo.addressBook().stream() - .sorted(Comparator.comparingLong(NodeInfo::nodeId)) - .map(nodeInfo -> NodeAddress.newBuilder() - .stake(nodeInfo.weight()) - .nodeAccountId(nodeInfo.accountId()) - .nodeId(nodeInfo.nodeId()) - .rsaPubKey(nodeInfo.hexEncodedPublicKey()) - .serviceEndpoint(V053AddressBookSchema.endpointFor("1.0.0.0", 1)) - .build()) - .toList(); - - return NodeAddressBook.PROTOBUF.toBytes( - NodeAddressBook.newBuilder().nodeAddress(nodeDetails).build()); - } - public void updateAddressBookAndNodeDetailsAfterFreeze( @NonNull final SystemContext systemContext, @NonNull final ReadableNodeStore nodeStore) { requireNonNull(systemContext); @@ -262,7 +229,7 @@ public static void dispatchSynthFileUpdate( .build())); } - private Bytes nodeStoreNodeDetails(@NonNull final ReadableNodeStore nodeStore) { + public Bytes nodeStoreNodeDetails(@NonNull final ReadableNodeStore nodeStore) { final var nodeDetails = new ArrayList(); StreamSupport.stream(Spliterators.spliterator(nodeStore.keys(), nodeStore.sizeOfState(), DISTINCT), false) .mapToLong(EntityNumber::number) @@ -288,7 +255,7 @@ private Bytes getHexStringBytesFromBytes(final Bytes rawBytes) { return Bytes.wrap(Normalizer.normalize(hexString, Normalizer.Form.NFD).getBytes(UTF_8)); } - private Bytes nodeStoreAddressBook(@NonNull final ReadableNodeStore nodeStore) { + public Bytes nodeStoreAddressBook(@NonNull final ReadableNodeStore nodeStore) { final var nodeAddresses = new ArrayList(); StreamSupport.stream(Spliterators.spliterator(nodeStore.keys(), nodeStore.sizeOfState(), DISTINCT), false) .mapToLong(EntityNumber::number) diff --git a/hedera-node/hedera-file-service-impl/src/main/java/module-info.java b/hedera-node/hedera-file-service-impl/src/main/java/module-info.java index 1e22d589f4c9..31058e857c4f 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/module-info.java @@ -6,13 +6,12 @@ requires transitive com.hedera.node.app.spi; requires transitive com.hedera.node.config; requires transitive com.hedera.node.hapi; + requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; requires transitive com.swirlds.state.api; - requires transitive com.hedera.pbj.runtime; requires transitive dagger; - requires transitive static java.compiler; // javax.annotation.processing.Generated + requires transitive java.compiler; // javax.annotation.processing.Generated requires transitive javax.inject; - requires com.hedera.node.app.service.addressbook.impl; requires com.swirlds.common; requires com.fasterxml.jackson.databind; requires com.google.protobuf; diff --git a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetContentsHandlerTest.java b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetContentsHandlerTest.java index 707e200a912b..a43e449a588f 100644 --- a/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetContentsHandlerTest.java +++ b/hedera-node/hedera-file-service-impl/src/test/java/com/hedera/node/app/service/file/impl/test/handlers/FileGetContentsHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * Copyright (C) 2023-2025 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,6 @@ import com.hedera.node.app.spi.workflows.QueryContext; import com.hedera.node.config.data.FilesConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.state.lifecycle.info.NetworkInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -68,14 +67,11 @@ class FileGetContentsHandlerTest extends FileTestBase { @Mock private V0490FileSchema genesisSchema; - @Mock - private NetworkInfo networkInfo; - private FileGetContentsHandler subject; @BeforeEach void setUp() { - subject = new FileGetContentsHandler(usageEstimator, genesisSchema, networkInfo); + subject = new FileGetContentsHandler(usageEstimator, genesisSchema); } @Test diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/grouping/GroupingVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/grouping/GroupingVerbs.java index 69110fcc27bf..abf871cd98d0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/grouping/GroupingVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/grouping/GroupingVerbs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Hedera Hashgraph, LLC + * Copyright (C) 2024-2025 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,4 +44,19 @@ private GroupingVerbs() { public static SysFileLookups getSystemFiles(@NonNull final Consumer> observer) { return new SysFileLookups(fileNum -> true, observer); } + + /** + * Returns a utility operation to retrieve the contents of specific system files and pass them to an observer. + * + * @param sysfileNub the system file number + * @param observer the consumer of the system file contents + * @return the utility operation + */ + public static SysFileLookups getSystemFiles(final long sysfileNub, @NonNull final Consumer observer) { + final Consumer> temp = map -> { + final Bytes contents = map.get(new FileID(0, 0, sysfileNub)); + observer.accept(contents); + }; + return new SysFileLookups(fileNum -> fileNum == sysfileNub, temp); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java index 222e3f7fd42d..dc7d55c39d19 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java @@ -175,6 +175,40 @@ final Stream syntheticAddressBookUpdateHappensAtUpgradeBoundary() { cryptoCreate("secondUser").via("addressBookExport")); } + @GenesisHapiTest + final Stream syntheticAddressBookCreatedAtGenesis() { + final AtomicReference addressBookContent = new AtomicReference<>(); + return hapiTest( + recordStreamMustIncludeNoFailuresFrom(visibleItems( + validatorSpecificSysFileFor(addressBookContent, "files.addressBook", "genesisTxn"), + "genesisTxn")), + sourcingContextual(spec -> + getSystemFiles(spec.startupProperties().getLong("files.addressBook"), addressBookContent::set)), + cryptoCreate("firstUser").via("genesisTxn"), + // Assert the first created entity still has the expected number + withOpContext((spec, opLog) -> assertEquals( + spec.startupProperties().getLong("hedera.firstUserEntity"), + spec.registry().getAccountID("firstUser").getAccountNum(), + "First user entity num doesn't match config"))); + } + + @GenesisHapiTest + final Stream syntheticNodeDetailsCreatedAtGenesis() { + final AtomicReference addressBookContent = new AtomicReference<>(); + return hapiTest( + recordStreamMustIncludeNoFailuresFrom(visibleItems( + validatorSpecificSysFileFor(addressBookContent, "files.nodeDetails", "genesisTxn"), + "genesisTxn")), + sourcingContextual(spec -> + getSystemFiles(spec.startupProperties().getLong("files.nodeDetails"), addressBookContent::set)), + cryptoCreate("firstUser").via("genesisTxn"), + // Assert the first created entity still has the expected number + withOpContext((spec, opLog) -> assertEquals( + spec.startupProperties().getLong("hedera.firstUserEntity"), + spec.registry().getAccountID("firstUser").getAccountNum(), + "First user entity num doesn't match config"))); + } + @GenesisHapiTest final Stream syntheticFeeSchedulesUpdateHappensAtUpgradeBoundary() throws InvalidProtocolBufferException { @@ -569,21 +603,85 @@ private static void validateSystemFileExports( assertEquals(Map.of(SUCCESS, 1), histogram.get(NodeStakeUpdate)); final var postGenesisContents = SysFileLookups.getSystemFileContents(spec, fileNum -> true); items.entries().stream().filter(item -> item.function() == FileCreate).forEach(item -> { + final var fileId = item.createdFileId(); final var preContents = requireNonNull( - preGenesisContents.get(item.createdFileId()), - "No pre-genesis contents for " + item.createdFileId()); + preGenesisContents.get(item.createdFileId()), "No pre-genesis contents for " + fileId); final var postContents = requireNonNull( - postGenesisContents.get(item.createdFileId()), - "No post-genesis contents for " + item.createdFileId()); + postGenesisContents.get(item.createdFileId()), "No post-genesis contents for " + fileId); final var exportedContents = fromByteString(item.body().getFileCreate().getContents()); - assertEquals( - exportedContents, preContents, item.createdFileId() + " contents don't match pre-genesis query"); - assertEquals( - exportedContents, postContents, item.createdFileId() + " contents don't match post-genesis query"); + if (fileId.fileNum() + != 102) { // for nodedetail, the node's weight changed between preContent and exportedContents + assertEquals(exportedContents, preContents, fileId + " contents don't match pre-genesis query"); + } + assertEquals(exportedContents, postContents, fileId + " contents don't match post-genesis query"); }); } + private static VisibleItemsValidator validatorSpecificSysFileFor( + @NonNull final AtomicReference fileContent, + @NonNull final String fileNumProperty, + @NonNull final String specTxnIds) { + return (spec, records) -> + specificSysFileValidator(spec, records, fileContent.get(), fileNumProperty, specTxnIds); + } + + private static void specificSysFileValidator( + @NonNull final HapiSpec spec, + @NonNull final Map genesisRecords, + @NonNull final Bytes fileContent, + @NonNull final String fileNumProperty, + @NonNull final String specTxnIds) { + final var items = requireNonNull(genesisRecords.get(specTxnIds)); + final long fileNumb = spec.startupProperties().getLong(fileNumProperty); + final var histogram = statusHistograms(items.entries()); + final var systemFileNums = + SysFileLookups.allSystemFileNums(spec).boxed().toList(); + assertEquals(Map.of(SUCCESS, systemFileNums.size()), histogram.get(FileCreate)); + // Also check we export a node stake update at genesis + assertEquals(Map.of(SUCCESS, 1), histogram.get(NodeStakeUpdate)); + final var fileItem = items.entries().stream() + .filter(item -> item.function() == FileCreate) + .filter(item -> item.createdFileId().equals(new FileID(0, 0, fileNumb))) + .findFirst() + .orElse(null); + + assertNotNull(fileItem, "No create item for " + fileNumProperty + " found in " + specTxnIds + " txn"); + final var fileCreateContents = fileItem.body().getFileCreate().getContents(); + assertNotNull( + fileCreateContents, "No create content for " + fileNumProperty + " found in " + specTxnIds + " txn"); + if (fileNumProperty.equals("files.nodeDetails")) { + try { + final var addressBook = NodeAddressBook.PROTOBUF.parse(fileContent); + final var updatedAddressBook = + NodeAddressBook.PROTOBUF.parse(Bytes.wrap(fileCreateContents.toByteArray())); + assertEquals( + addressBook.nodeAddress().size(), + updatedAddressBook.nodeAddress().size(), + "address book size mismatch"); + + for (int i = 0; + i < addressBook.nodeAddress().size(); + i++) { // only stake not matching because of recalculating + final var address = updatedAddressBook.nodeAddress().get(i); + final var updatedAddress = updatedAddressBook.nodeAddress().get(i); + assertEquals(address.nodeId(), updatedAddress.nodeId(), "nodeId mismatch"); + assertEquals(address.nodeAccountId(), updatedAddress.nodeAccountId(), "nodeAccountId mismatch"); + assertEquals(address.nodeCertHash(), updatedAddress.nodeCertHash(), "nodeCertHash mismatch"); + assertEquals(address.description(), updatedAddress.description(), "description mismatch"); + assertEquals(address.rsaPubKey(), updatedAddress.rsaPubKey(), "rsaPubKey mismatch"); + assertEquals( + address.serviceEndpoint(), updatedAddress.serviceEndpoint(), "serviceEndpoint mismatch"); + } + } catch (ParseException e) { + Assertions.fail("Update contents was not protobuf " + e.getMessage()); + } + } else { + assertEquals( + fileContent, fromByteString(fileCreateContents), fileNumb + " contents don't match genesis query"); + } + } + private static Map generateCertificates(final int n) { final var randomAddressBook = RandomAddressBookBuilder.create(new Random()) .withSize(n)