Skip to content

Commit

Permalink
Create genesis records from node startup (#7983)
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Hess <[email protected]>
  • Loading branch information
mhess-swl authored Sep 19, 2023
1 parent a4ee62a commit 80ebd58
Show file tree
Hide file tree
Showing 32 changed files with 1,148 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.hedera.node.app.spi.state;

import com.hedera.node.app.spi.info.NetworkInfo;
import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder;
import com.swirlds.config.api.Configuration;
import edu.umd.cs.findbugs.annotations.NonNull;

Expand Down Expand Up @@ -61,4 +62,15 @@ public interface MigrationContext {
* @return The {@link NetworkInfo} of the network at the time of migration.
*/
NetworkInfo networkInfo();

/**
* Provides a class to store any entities created during genesis. The data saved in this class
* during genesis will then enable record generation from said data once a consensus timestamp is
* available.
* <p>
* It's possible that this method could be expanded to cover records for any migration (genesis
* or otherwise) in the future.
*/
@NonNull
GenesisRecordsBuilder genesisRecordsBuilder();
}
Original file line number Diff line number Diff line change
@@ -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.spi.workflows.record;

import com.hedera.hapi.node.state.token.Account;
import com.hedera.hapi.node.token.CryptoCreateTransactionBody;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Map;

/**
* A class that stores entities created during node startup, for the purpose of creating synthetic
* records after startup
*/
public interface GenesisRecordsBuilder {
/**
* Tracks the system accounts created during node startup
*/
void systemAccounts(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts);

/**
* Tracks the staking accounts created during node startup
*/
void stakingAccounts(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts);

/**
* Tracks miscellaneous accounts created during node startup. These accounts are typically used for testing
*/
void miscAccounts(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts);

/**
* Tracks the treasury clones created during node startup
*/
void treasuryClones(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts);
}
Original file line number Diff line number Diff line change
@@ -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.spi.fixtures.state;

/*
* 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.
*/

import com.hedera.hapi.node.state.token.Account;
import com.hedera.hapi.node.token.CryptoCreateTransactionBody;
import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Map;

public class NoOpGenesisRecordsBuilder implements GenesisRecordsBuilder {
@Override
public void systemAccounts(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts) {
// Intentional no-op
}

@Override
public void stakingAccounts(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts) {
// Intentional no-op
}

@Override
public void miscAccounts(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts) {
// Intentional no-op
}

@Override
public void treasuryClones(@NonNull final Map<Account, CryptoCreateTransactionBody.Builder> accounts) {
// Intentional no-op
}
}
3 changes: 2 additions & 1 deletion hedera-node/hedera-app/src/itest/java/grpc/GrpcTestBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.hedera.node.app.services.ServicesRegistry;
import com.hedera.node.app.spi.Service;
import com.hedera.node.app.spi.fixtures.TestBase;
import com.hedera.node.app.spi.fixtures.state.NoOpGenesisRecordsBuilder;
import com.hedera.node.app.state.merkle.MerkleSchemaRegistry;
import com.hedera.node.app.workflows.ingest.IngestWorkflow;
import com.hedera.node.app.workflows.query.QueryWorkflow;
Expand Down Expand Up @@ -175,7 +176,7 @@ public String basePath() {
};

final var cr = ConstructableRegistry.getInstance();
final var registry = new MerkleSchemaRegistry(cr, "TestService");
final var registry = new MerkleSchemaRegistry(cr, "TestService", new NoOpGenesisRecordsBuilder());
final var registration = new ServicesRegistry.Registration(testService, registry);
final var config = createConfig(new TestSource());
this.grpcServer = new NettyGrpcServerManager(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.hedera.node.app.service.util.impl.UtilServiceImpl;
import com.hedera.node.app.services.ServicesRegistryImpl;
import com.hedera.node.app.spi.HapiUtils;
import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder;
import com.hedera.node.app.state.HederaState;
import com.hedera.node.app.state.merkle.MerkleHederaState;
import com.hedera.node.app.state.merkle.MerkleSchemaRegistry;
Expand All @@ -54,6 +55,7 @@
import com.hedera.node.app.version.HederaSoftwareVersion;
import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory;
import com.hedera.node.app.workflows.handle.SystemFileUpdateFacility;
import com.hedera.node.app.workflows.handle.record.GenesisRecordsConsensusHook;
import com.hedera.node.config.ConfigProvider;
import com.hedera.node.config.Utils;
import com.hedera.node.config.data.FilesConfig;
Expand Down Expand Up @@ -134,6 +136,8 @@ public final class Hedera implements SwirldMain {
private ThrottleManager throttleManager;
/** The exchange rate manager */
private ExchangeRateManager exchangeRateManager;
/** The class responsible for remembering objects created in genesis cases */
private final GenesisRecordsBuilder genesisRecordsBuilder;
/**
* Dependencies managed by Dagger. Set during state initialization. The mono-service requires this object, but none
* of the rest of the system (and particularly the modular implementation) uses it directly. Rather, it is created
Expand Down Expand Up @@ -197,10 +201,13 @@ public Hedera(@NonNull final ConstructableRegistry constructableRegistry) {
() -> HapiUtils.toString(version.getHapiVersion()),
() -> HapiUtils.toString(version.getServicesVersion()));

// Create a record builder for any genesis records that need to be created
this.genesisRecordsBuilder = new GenesisRecordsConsensusHook();

// Create all the service implementations
logger.info("Registering services");
// FUTURE: Use the service loader framework to load these services!
this.servicesRegistry = new ServicesRegistryImpl(constructableRegistry);
this.servicesRegistry = new ServicesRegistryImpl(constructableRegistry, genesisRecordsBuilder);
Set.of(
new ConsensusServiceImpl(),
CONTRACT_SERVICE,
Expand Down Expand Up @@ -714,6 +721,7 @@ private void initializeDagger(@NonNull final MerkleHederaState state, @NonNull f
.servicesRegistry(servicesRegistry)
.bootstrapProps(new BootstrapProperties(false)) // TBD REMOVE
.instantSource(InstantSource.system())
.genesisRecordsConsensusHook((GenesisRecordsConsensusHook) genesisRecordsBuilder)
.build();

daggerApp.workingStateAccessor().setHederaState(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.hedera.node.app.workflows.handle.DualStateUpdateFacility;
import com.hedera.node.app.workflows.handle.HandleWorkflow;
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;
import com.hedera.node.config.ConfigProvider;
import com.swirlds.common.crypto.Cryptography;
Expand Down Expand Up @@ -119,6 +120,8 @@ public interface HederaInjectionComponent {

DualStateUpdateFacility dualStateUpdateFacility();

GenesisRecordsConsensusHook genesisRecordsConsensusHook();

@Component.Builder
interface Builder {

Expand Down Expand Up @@ -161,6 +164,9 @@ interface Builder {
@BindsInstance
Builder throttleManager(ThrottleManager throttleManager);

@BindsInstance
Builder genesisRecordsConsensusHook(GenesisRecordsConsensusHook genesisRecordsBuilder);

HederaInjectionComponent build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;

import com.hedera.node.app.spi.Service;
import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder;
import com.hedera.node.app.state.merkle.MerkleSchemaRegistry;
import com.swirlds.common.constructable.ConstructableRegistry;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -41,11 +42,16 @@ public final class ServicesRegistryImpl implements ServicesRegistry {
/** The set of registered services */
private final SortedSet<Registration> entries;

private final GenesisRecordsBuilder genesisRecords;

/**
* Creates a new registry.
*/
public ServicesRegistryImpl(@NonNull final ConstructableRegistry constructableRegistry) {
public ServicesRegistryImpl(
@NonNull final ConstructableRegistry constructableRegistry,
@NonNull final GenesisRecordsBuilder genesisRecords) {
this.constructableRegistry = requireNonNull(constructableRegistry);
this.genesisRecords = requireNonNull(genesisRecords);
this.entries = new TreeSet<>(Comparator.comparing(r -> r.service().getServiceName()));
}

Expand All @@ -58,7 +64,7 @@ public void register(@NonNull final Service service) {
final var serviceName = service.getServiceName();

logger.debug("Registering schemas for service {}", serviceName);
final var registry = new MerkleSchemaRegistry(constructableRegistry, serviceName);
final var registry = new MerkleSchemaRegistry(constructableRegistry, serviceName, genesisRecords);
service.registerSchemas(registry);

entries.add(new Registration(service, registry));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.hedera.node.app.spi.state.Schema;
import com.hedera.node.app.spi.state.SchemaRegistry;
import com.hedera.node.app.spi.state.StateDefinition;
import com.hedera.node.app.spi.workflows.record.GenesisRecordsBuilder;
import com.hedera.node.app.state.merkle.disk.OnDiskKey;
import com.hedera.node.app.state.merkle.disk.OnDiskKeySerializer;
import com.hedera.node.app.state.merkle.disk.OnDiskValue;
Expand All @@ -39,6 +40,7 @@
import com.hedera.node.app.state.merkle.singleton.SingletonNode;
import com.hedera.node.app.state.merkle.singleton.StringLeaf;
import com.hedera.node.app.state.merkle.singleton.ValueLeaf;
import com.hedera.node.app.workflows.handle.record.MigrationContextImpl;
import com.swirlds.common.constructable.ClassConstructorPair;
import com.swirlds.common.constructable.ConstructableRegistry;
import com.swirlds.common.constructable.ConstructableRegistryException;
Expand Down Expand Up @@ -82,18 +84,24 @@ public class MerkleSchemaRegistry implements SchemaRegistry {
private final ConstructableRegistry constructableRegistry;
/** The ordered set of all schemas registered by the service */
private final Set<Schema> schemas = new TreeSet<>();
/** Stores system entities created during genesis until the node can build synthetic records */
private final GenesisRecordsBuilder genesisRecordsBuilder;

/**
* Create a new instance.
*
* @param constructableRegistry The {@link ConstructableRegistry} to register states with for
* deserialization
* @param serviceName The name of the service using this registry.
* deserialization
* @param serviceName The name of the service using this registry.
* @param genesisRecordsBuilder class used to store entities created at genesis
*/
public MerkleSchemaRegistry(
@NonNull final ConstructableRegistry constructableRegistry, @NonNull final String serviceName) {
@NonNull final ConstructableRegistry constructableRegistry,
@NonNull final String serviceName,
@NonNull final GenesisRecordsBuilder genesisRecordsBuilder) {
this.constructableRegistry = requireNonNull(constructableRegistry);
this.serviceName = StateUtils.validateStateKey(requireNonNull(serviceName));
this.genesisRecordsBuilder = requireNonNull(genesisRecordsBuilder);
}

/**
Expand Down Expand Up @@ -208,7 +216,8 @@ public void migrate(
remainingStates.removeAll(statesToRemove);
final var newStates = new FilteredWritableStates(writeableStates, remainingStates);

final var migrationContext = new MigrationContextImpl(previousStates, newStates, config, networkInfo);
final var migrationContext =
new MigrationContextImpl(previousStates, newStates, config, networkInfo, genesisRecordsBuilder);
if (updateInsteadOfMigrate) {
schema.restart(migrationContext);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package com.hedera.node.app.workflows.handle;

import com.hedera.node.app.service.token.records.StakingContext;
import com.hedera.node.app.service.token.records.TokenContext;
import edu.umd.cs.findbugs.annotations.NonNull;

/**
Expand All @@ -32,5 +32,5 @@ public interface ConsensusTimeHook {
*
* @param context the {@code StakingContext} context of the transaction being processed
*/
void process(@NonNull final StakingContext context);
void process(@NonNull final TokenContext context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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.handle.record.GenesisRecordsConsensusHook;
import com.hedera.node.app.workflows.handle.record.RecordListBuilder;
import com.hedera.node.app.workflows.handle.record.SingleTransactionRecordBuilderImpl;
import com.hedera.node.app.workflows.handle.stack.SavepointStackImpl;
Expand Down Expand Up @@ -111,6 +112,7 @@ public class HandleWorkflow {
private final ServiceScopeLookup serviceScopeLookup;
private final ConfigProvider configProvider;
private final HederaRecordCache recordCache;
private final GenesisRecordsConsensusHook genesisRecordsTimeHook;
private final StakingPeriodTimeHook stakingPeriodTimeHook;
private final FeeManager feeManager;
private final ExchangeRateManager exchangeRateManager;
Expand All @@ -132,6 +134,7 @@ public HandleWorkflow(
@NonNull final ServiceScopeLookup serviceScopeLookup,
@NonNull final ConfigProvider configProvider,
@NonNull final HederaRecordCache recordCache,
@NonNull final GenesisRecordsConsensusHook genesisRecordsTimeHook,
@NonNull final StakingPeriodTimeHook stakingPeriodTimeHook,
@NonNull final FeeManager feeManager,
@NonNull final ExchangeRateManager exchangeRateManager,
Expand All @@ -150,6 +153,7 @@ public HandleWorkflow(
this.serviceScopeLookup = requireNonNull(serviceScopeLookup, "serviceScopeLookup must not be null");
this.configProvider = requireNonNull(configProvider, "configProvider must not be null");
this.recordCache = requireNonNull(recordCache, "recordCache must not be null");
this.genesisRecordsTimeHook = requireNonNull(genesisRecordsTimeHook, "genesisRecordsTimeHook must not be null");
this.stakingPeriodTimeHook = requireNonNull(stakingPeriodTimeHook, "stakingPeriodTimeHook must not be null");
this.feeManager = requireNonNull(feeManager, "feeManager must not be null");
this.exchangeRateManager = requireNonNull(exchangeRateManager, "exchangeRateManager must not be null");
Expand Down Expand Up @@ -256,7 +260,10 @@ private void handleUserTransaction(
final var readableStoreFactory = new ReadableStoreFactory(stack);
final var feeAccumulator = createFeeAccumulator(stack, configuration, recordBuilder);

final var tokenServiceContext = new TokenServiceContextImpl(configuration, stack, recordListBuilder);
final var tokenServiceContext = new TokenContextImpl(configuration, stack, recordListBuilder);
// It's awful that we have to check this every time a transaction is handled, especially since this mostly
// applies to non-production cases. Let's find a way to 💥💥 remove this 💥💥
genesisRecordsTimeHook.process(tokenServiceContext);
try {
// If this is the first user transaction after midnight, then handle staking updates prior to handling the
// transaction itself.
Expand Down
Loading

0 comments on commit 80ebd58

Please sign in to comment.