Skip to content

Commit

Permalink
update node store on post upgrade transaction
Browse files Browse the repository at this point in the history
Signed-off-by: Neeharika-Sompalli <[email protected]>
  • Loading branch information
Neeharika-Sompalli committed Nov 8, 2024
1 parent 4d8984f commit 1190d0e
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.addressbook.impl;

import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.state.addressbook.Node;
import com.hedera.hapi.node.state.common.EntityNumber;
import com.swirlds.config.api.Configuration;
import com.swirlds.state.lifecycle.info.NetworkInfo;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Singleton
public class AddressBookHelper {
private static final Logger log = LogManager.getLogger(AddressBookHelper.class);

@Inject
public AddressBookHelper() {}

public void adjustPostUpgradeNodeMetadata(
@NonNull final NetworkInfo networkInfo,
@NonNull final Configuration config,
@NonNull final WritableNodeStore nodeStore) {
requireNonNull(networkInfo);
requireNonNull(config);
final var nodeStoreIds = getNodeIds(nodeStore.keys());
nodeStoreIds.stream().sorted().forEach(nodeId -> {
final var node = requireNonNull(nodeStore.getForModify(nodeId));
if (!networkInfo.containsNode(nodeId) && !node.deleted()) {
nodeStore.put(node.copyBuilder().weight(0).deleted(true).build());
log.info("Marked node{} as deleted since it has been removed from the address book", nodeId);
}
});
// Add new nodes
for (final var nodeInfo : networkInfo.addressBook()) {
final var node = nodeStore.get(nodeInfo.nodeId());
if (node == null) {
final var newNode = Node.newBuilder()
.nodeId(nodeInfo.nodeId())
.weight(nodeInfo.stake())
.accountId(nodeInfo.accountId())
.gossipCaCertificate(nodeInfo.sigCertBytes())
.gossipEndpoint(nodeInfo.gossipEndpoints())
.build();
nodeStore.put(newNode);
}
}
}

private Set<Long> getNodeIds(final Iterator<EntityNumber> nodeStoreIds) {
if (!nodeStoreIds.hasNext()) return Collections.emptySet();

final var nodeIds = new HashSet<Long>();
while (nodeStoreIds.hasNext()) {
nodeIds.add(nodeStoreIds.next().number());
}
return nodeIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
import com.hedera.node.app.fees.ExchangeRateManager;
import com.hedera.node.app.records.BlockRecordManager;
import com.hedera.node.app.records.BlockRecordService;
import com.hedera.node.app.service.addressbook.AddressBookService;
import com.hedera.node.app.service.addressbook.impl.AddressBookHelper;
import com.hedera.node.app.service.addressbook.impl.WritableNodeStore;
import com.hedera.node.app.service.file.FileService;
import com.hedera.node.app.service.schedule.ScheduleService;
import com.hedera.node.app.service.schedule.WritableScheduleStore;
Expand Down Expand Up @@ -133,6 +136,7 @@ public class HandleWorkflow {
private final StakePeriodManager stakePeriodManager;
private final List<StateChanges.Builder> migrationStateChanges;
private final UserTxnFactory userTxnFactory;
private final AddressBookHelper addressBookHelper;

// The last second since the epoch at which the metrics were updated; this does not affect transaction handling
private long lastMetricUpdateSecond;
Expand All @@ -158,7 +162,8 @@ public HandleWorkflow(
@NonNull final ExchangeRateManager exchangeRateManager,
@NonNull final StakePeriodManager stakePeriodManager,
@NonNull final List<StateChanges.Builder> migrationStateChanges,
@NonNull final UserTxnFactory userTxnFactory) {
@NonNull final UserTxnFactory userTxnFactory,
final AddressBookHelper addressBookHelper) {
this.networkInfo = requireNonNull(networkInfo);
this.stakePeriodChanges = requireNonNull(stakePeriodChanges);
this.dispatchProcessor = requireNonNull(dispatchProcessor);
Expand All @@ -182,6 +187,7 @@ public HandleWorkflow(
.getConfiguration()
.getConfigData(BlockStreamConfig.class)
.streamMode();
this.addressBookHelper = requireNonNull(addressBookHelper);
}

/**
Expand Down Expand Up @@ -365,20 +371,29 @@ private HandleOutput execute(@NonNull final UserTxn userTxn) {
final var rosterStore = writableStoreFactory.getStore(WritableRosterStore.class);
rosterStore.putActiveRoster(networkInfo.roster(), 1L);
} else if (userTxn.type() == POST_UPGRADE_TRANSACTION) {
final var writableStoreFactory = new WritableStoreFactory(
userTxn.stack(), AddressBookService.NAME, userTxn.config(), storeMetricsService);
final var nodeStore = writableStoreFactory.getStore(WritableNodeStore.class);
final var writableStakingInfoStore =
new WritableStakingInfoStore(userTxn.stack().getWritableStates(TokenService.NAME));
final var writableNetworkStakingRewardsStore = new WritableNetworkStakingRewardsStore(
userTxn.stack().getWritableStates(TokenService.NAME));
final var streamBuilder = stakeInfoHelper.adjustPostUpgradeStakes(
userTxn.tokenContextImpl(),
networkInfo,
userTxn.config(),
new WritableStakingInfoStore(userTxn.stack().getWritableStates(TokenService.NAME)),
new WritableNetworkStakingRewardsStore(
userTxn.stack().getWritableStates(TokenService.NAME)));
writableStakingInfoStore,
writableNetworkStakingRewardsStore);
addressBookHelper.adjustPostUpgradeNodeMetadata(networkInfo, userTxn.config(), nodeStore);

if (streamMode != RECORDS) {
// Only externalize this if we are streaming blocks
streamBuilder.exchangeRate(exchangeRateManager.exchangeRates());
userTxn.stack().commitTransaction(streamBuilder);
} else {
// Only update this if we are relying on RecordManager state for post-upgrade processing
blockRecordManager.markMigrationRecordsStreamed();
userTxn.stack().commitSystemStateChanges();
}
// C.f. https://github.com/hashgraph/hedera-services/issues/14751,
// here we may need to switch the newly adopted candidate roster
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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.info;

import static com.hedera.node.app.workflows.standalone.TransactionExecutorsTest.getCertBytes;
import static com.hedera.node.app.workflows.standalone.TransactionExecutorsTest.randomX509Certificate;
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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mock.Strictness.LENIENT;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.hedera.hapi.node.state.addressbook.Node;
import com.hedera.hapi.node.state.common.EntityNumber;
import com.hedera.hapi.node.state.roster.Roster;
import com.hedera.hapi.node.state.roster.RosterEntry;
import com.hedera.hapi.platform.state.Address;
import com.hedera.hapi.platform.state.AddressBook;
import com.hedera.hapi.platform.state.NodeId;
import com.hedera.hapi.platform.state.PlatformState;
import com.hedera.node.app.service.addressbook.AddressBookService;
import com.hedera.node.config.ConfigProvider;
import com.hedera.node.config.VersionedConfigImpl;
import com.hedera.node.config.testfixtures.HederaTestConfigBuilder;
import com.swirlds.platform.state.service.PlatformStateService;
import com.swirlds.state.State;
import com.swirlds.state.spi.ReadableKVState;
import com.swirlds.state.spi.ReadableSingletonState;
import com.swirlds.state.spi.ReadableStates;
import java.security.cert.X509Certificate;
import java.util.List;
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)
public class StateNetworkInfoTest {
@Mock(strictness = LENIENT)
private State state;

@Mock
private ConfigProvider configProvider;

@Mock
private ReadableKVState<EntityNumber, Node> nodeState;

@Mock
private ReadableStates readableStates;

@Mock
private ReadableSingletonState<PlatformState> platformReadableState;

@Mock
private PlatformState platformState;

private static final long SELF_ID = 1L;
private final Roster activeRoster = new Roster(List.of(
RosterEntry.newBuilder().nodeId(SELF_ID).weight(10).build(),
RosterEntry.newBuilder().nodeId(3L).weight(20).build()));

private static final X509Certificate CERTIFICATE_2 = randomX509Certificate();
private static final X509Certificate CERTIFICATE_3 = randomX509Certificate();

private StateNetworkInfo networkInfo;

@BeforeEach
public void setUp() {
when(configProvider.getConfiguration())
.thenReturn(new VersionedConfigImpl(HederaTestConfigBuilder.createConfig(), 1));
when(state.getReadableStates(AddressBookService.NAME)).thenReturn(readableStates);
when(readableStates.<EntityNumber, Node>get("NODES")).thenReturn(nodeState);
when(state.getReadableStates(PlatformStateService.NAME)).thenReturn(readableStates);
networkInfo = new StateNetworkInfo(state, activeRoster, SELF_ID, configProvider);
}

@Test
public void testLedgerId() {
assertEquals("00", networkInfo.ledgerId().toHex());
}

@Test
public void testSelfNodeInfo() {
final var selfNode = networkInfo.selfNodeInfo();
assertNotNull(selfNode);
assertEquals(SELF_ID, selfNode.nodeId());
}

@Test
public void testAddressBook() {
final var addressBook = networkInfo.addressBook();
assertNotNull(addressBook);
assertEquals(2, addressBook.size());
}

@Test
public void testNodeInfo() {
final var nodeInfo = networkInfo.nodeInfo(SELF_ID);
assertNotNull(nodeInfo);
assertEquals(SELF_ID, nodeInfo.nodeId());
assertNull(networkInfo.nodeInfo(999L));
}

@Test
public void testContainsNode() {
assertTrue(networkInfo.containsNode(SELF_ID));
assertFalse(networkInfo.containsNode(999L));
}

@Test
public void testUpdateFrom() {
when(nodeState.get(any(EntityNumber.class))).thenReturn(mock(Node.class));
when(readableStates.<PlatformState>getSingleton("PLATFORM_STATE")).thenReturn(platformReadableState);
when(platformReadableState.get()).thenReturn(platformState);
when(platformState.addressBook())
.thenReturn(AddressBook.newBuilder()
.addresses(
Address.newBuilder()
.id(new NodeId(2L))
.weight(111L)
.signingCertificate(getCertBytes(CERTIFICATE_2))
// The agreementCertificate is unused, but required to prevent deserialization
// failure in
// States API.
.agreementCertificate(getCertBytes(CERTIFICATE_2))
.hostnameInternal("10.0.55.66")
.portInternal(222)
.build(),
Address.newBuilder()
.id(new NodeId(3L))
.weight(3L)
.signingCertificate(getCertBytes(CERTIFICATE_3))
// The agreementCertificate is unused, but required to prevent deserialization
// failure in
// States API.
.agreementCertificate(getCertBytes(CERTIFICATE_3))
.hostnameExternal("external3.com")
.portExternal(111)
.build())
.build());

networkInfo.updateFrom(state);
assertEquals(2, networkInfo.addressBook().size());
}

@Test
public void testBuildNodeInfoMapNodeNotFound() {
when(nodeState.get(any(EntityNumber.class))).thenReturn(null);

StateNetworkInfo networkInfo = new StateNetworkInfo(state, activeRoster, SELF_ID, configProvider);
final var nodeInfo = networkInfo.nodeInfo(SELF_ID);

assertNotNull(nodeInfo);
assertEquals(SELF_ID + 3, nodeInfo.accountId().accountNum());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.hedera.node.app.blocks.BlockStreamManager;
import com.hedera.node.app.fees.ExchangeRateManager;
import com.hedera.node.app.records.BlockRecordManager;
import com.hedera.node.app.service.addressbook.impl.AddressBookHelper;
import com.hedera.node.app.service.token.impl.handlers.staking.StakeInfoHelper;
import com.hedera.node.app.service.token.impl.handlers.staking.StakePeriodManager;
import com.hedera.node.app.spi.metrics.StoreMetricsService;
Expand Down Expand Up @@ -205,6 +206,7 @@ private void givenSubjectWith(
exchangeRateManager,
stakePeriodManager,
migrationStateChanges,
userTxnFactory);
userTxnFactory,
new AddressBookHelper());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
* </ol>
*/
@ExtendWith(MockitoExtension.class)
class TransactionExecutorsTest {
public class TransactionExecutorsTest {
private static final long GAS = 100_000L;
private static final long EXPECTED_LUCKY_NUMBER = 42L;
private static final AccountID TREASURY_ID =
Expand Down Expand Up @@ -373,7 +373,7 @@ public static X509Certificate randomX509Certificate() {
}
}

private static Bytes getCertBytes(X509Certificate certificate) {
public static Bytes getCertBytes(X509Certificate certificate) {
try {
return Bytes.wrap(certificate.getEncoded());
} catch (CertificateEncodingException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ public EndOfStakingPeriodUpdater(
final Map<Long, StakingNodeInfo> newNodeInfos = new LinkedHashMap<>();
final var stakingInfoStore = context.writableStore(WritableStakingInfoStore.class);
for (final var nodeId : context.knownNodeIds().stream().sorted().toList()) {
// The node's staking info at the e end of the period, non-final because
// The node's staking info at the end of the period, non-final because
// we iteratively update its reward sum history,
var nodeInfo = requireNonNull(stakingInfoStore.getForModify(nodeId));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ public void withdrawStake(
* Also clears any pending rewards from the {@link NetworkStakingRewards} singleton for nodes that are no
* longer in the address book.
*
* @param context the token context
* @param networkInfo the list of node infos from the address book
* @param config the configuration for the node
* @param infoStore the writable store for the staking info
* @param context the token context
* @param networkInfo the list of node infos from the address book
* @param config the configuration for the node
* @param infoStore the writable store for the staking info
* @param rewardsStore the store for the staking rewards
*/
public StreamBuilder adjustPostUpgradeStakes(
Expand Down

0 comments on commit 1190d0e

Please sign in to comment.