Skip to content

Commit

Permalink
feat: Use Stake value at the end of the day as weight in `StakingNo…
Browse files Browse the repository at this point in the history
…deInfo` (#17285)

Signed-off-by: Neeharika-Sompalli <[email protected]>
  • Loading branch information
Neeharika-Sompalli authored Jan 10, 2025
1 parent 500015a commit f7b6f69
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 88 deletions.
5 changes: 4 additions & 1 deletion hapi/hedera-protobufs/services/state/addressbook/node.proto
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,11 @@ message Node {
* of HBAR staked to that node.<br/>
* Consensus SHALL be calculated based on agreement of greater than `2/3`
* of the total `weight` value of all nodes on the network.
* <p>
* This field is deprecated and SHALL NOT be used when RosterLifecycle
* is enabled.
*/
uint64 weight = 8;
uint64 weight = 8 [deprecated = true];

/**
* A flag indicating this node is deleted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ message StakingNodeInfo {
* This is recomputed based on the `stake` of this node at midnight UTC of
* each day. If the `stake` of this node at that time is less than
* `min_stake`, then the weight SHALL be 0.<br/>
* The sum of all weights of nodes in the network SHALL be less than 500.
* <p>
* Given the following:
* <ul>
Expand All @@ -143,10 +142,11 @@ message StakingNodeInfo {
* <li>The `effective network stake` SHALL be calculated as ∑(`effective
* stake` of each node) for all nodes in the network address book.</li>
* </ul>
* The actual consensus weight for this node SHALL be calculated as
* __(500 * (`effective stake`/`effective network stake`))__
* <p>
* This field is deprecated and SHALL NOT be used when RosterLifecycle
* is enabled. The weight SHALL be same as the `effective_stake` described above.
*/
int32 weight = 10;
int32 weight = 10 [deprecated = true];

/**
* The total staking rewards in tinybars that MAY be collected by all
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -32,6 +32,7 @@
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;

/**
* Provides read-only methods for interacting with the underlying data storage mechanisms for
Expand All @@ -55,8 +56,8 @@ public ReadableNodeStoreImpl(@NonNull final ReadableStates states) {
}

@Override
public Roster snapshotOfFutureRoster() {
return constructFromNodesState(nodesState());
public Roster snapshotOfFutureRoster(Function<Long, Long> weightFunction) {
return constructFromNodesStateWithStakingInfoWeight(nodesState(), weightFunction);
}

/**
Expand Down Expand Up @@ -88,7 +89,9 @@ public Iterator<EntityNumber> keys() {
return nodesState().keys();
}

private Roster constructFromNodesState(@NonNull final ReadableKVState<EntityNumber, Node> nodesState) {
private Roster constructFromNodesStateWithStakingInfoWeight(
@NonNull final ReadableKVState<EntityNumber, Node> nodesState,
@NonNull final Function<Long, Long> weightProvider) {
final var rosterEntries = new ArrayList<RosterEntry>();
for (final var it = nodesState.keys(); it.hasNext(); ) {
final var nodeNumber = it.next();
Expand All @@ -102,7 +105,7 @@ private Roster constructFromNodesState(@NonNull final ReadableKVState<EntityNumb
if (!node.deleted()) {
final var entry = RosterEntry.newBuilder()
.nodeId(node.nodeId())
.weight(node.weight())
.weight(weightProvider.apply(node.nodeId()))
.gossipCaCertificate(node.gossipCaCertificate())
.gossipEndpoint(nodeEndpoints)
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -126,7 +126,8 @@ void snapshotOfFutureRosterIncludesAllUndeletedDefinitions() {
given(readableStates.<EntityNumber, Node>get(anyString())).willReturn(nodesState);

subject = new ReadableNodeStoreImpl(readableStates);
final var result = subject.snapshotOfFutureRoster();
final var result = subject.snapshotOfFutureRoster(nodeId ->
nodesState.get(EntityNumber.newBuilder().number(nodeId).build()).weight());
org.assertj.core.api.Assertions.assertThat(result.rosterEntries())
.containsExactlyInAnyOrder(ROSTER_NODE_1, ROSTER_NODE_2, ROSTER_NODE_3);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -22,6 +22,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.util.Iterator;
import java.util.function.Function;

/**
* Provides read-only methods for interacting with the underlying data storage mechanisms for
Expand All @@ -35,9 +36,11 @@ public interface ReadableNodeStore {
* Constructs a new {@link Roster} object using the current info for each node defined in state.
* Accordingly, be warned that <b>this method iterates over all nodes.</b>
*
* @param weightFunction the function to use to determine the weight of each node
* from stakingNodeInfo
* @return a new roster, representing the most current node configurations available
*/
Roster snapshotOfFutureRoster();
Roster snapshotOfFutureRoster(Function<Long, Long> weightFunction);

/**
* Returns the node needed. If the node doesn't exist returns failureReason. If the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import com.hedera.node.app.service.addressbook.AddressBookService;
import com.hedera.node.app.service.addressbook.impl.ReadableNodeStoreImpl;
import com.hedera.node.app.service.networkadmin.FreezeService;
import com.hedera.node.app.service.token.TokenService;
import com.hedera.node.app.service.token.impl.ReadableStakingInfoStoreImpl;
import com.hedera.node.config.data.NetworkAdminConfig;
import com.swirlds.config.api.Configuration;
import com.swirlds.platform.config.AddressBookConfig;
Expand All @@ -44,6 +46,7 @@
import java.util.List;
import java.util.Spliterators;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
import javax.inject.Singleton;
Expand Down Expand Up @@ -71,7 +74,7 @@ public PlatformStateUpdates(@NonNull final BiConsumer<Roster, Path> rosterExport
* Checks whether the given transaction body is a freeze transaction and eventually
* notifies the registered facility.
*
* @param state the current state
* @param state the current state
* @param txBody the transaction body
* @param config the configuration
*/
Expand Down Expand Up @@ -114,17 +117,32 @@ public void handleTxBody(
final var nodeStore =
new ReadableNodeStoreImpl(state.getReadableStates(AddressBookService.NAME));
final var rosterStore = new WritableRosterStore(state.getWritableStates(RosterService.NAME));
final var candidateRoster = nodeStore.snapshotOfFutureRoster();
logger.info("Candidate roster is {}", candidateRoster);
final var stakingInfoStore =
new ReadableStakingInfoStoreImpl(state.getReadableStates(TokenService.NAME));

// update the candidate roster weights with weights from stakingNodeInfo map
final Function<Long, Long> weightFunction = nodeId -> {
final var stakingInfo = stakingInfoStore.get(nodeId);
if (stakingInfo != null && !stakingInfo.deleted()) {
return stakingInfo.stake();
}
// Default weight if no staking info is found or the node is deleted
return 0L;
};
final var candidateRoster = nodeStore.snapshotOfFutureRoster(weightFunction);
logger.info("Candidate roster with updated weights is {}", candidateRoster);
boolean rosterAccepted = false;
try {
rosterStore.putCandidateRoster(candidateRoster);
rosterAccepted = true;
} catch (Exception e) {
logger.warn("Candidate roster was rejected", e);
}
if (rosterAccepted && networkAdminConfig.exportCandidateRoster()) {
doExport(candidateRoster, networkAdminConfig);
if (rosterAccepted) {
// If the candidate roster needs to be exported, export the file
if (networkAdminConfig.exportCandidateRoster()) {
doExport(candidateRoster, networkAdminConfig);
}
}
} else if (networkAdminConfig.exportCandidateRoster()) {
// Having the option to export candidate-roster.json even before using the roster
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.hedera.node.config.data.StakingConfig;
import com.hedera.node.config.types.StreamMode;
import com.swirlds.config.api.Configuration;
import com.swirlds.platform.config.AddressBookConfig;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.time.Instant;
import java.time.LocalDate;
Expand Down Expand Up @@ -111,6 +112,8 @@ public boolean process(
stack.rollbackFullStack();
}
final var config = tokenContext.configuration();
final var useRosterLifecycle =
config.getConfigData(AddressBookConfig.class).useRosterLifecycle();
try {
final var nodeStore = newWritableNodeStore(stack, config);
final BiConsumer<Long, Integer> weightUpdates = (nodeId, weight) -> nodeStore.put(nodeStore
Expand All @@ -119,7 +122,7 @@ public boolean process(
.weight(weight)
.build());
final var streamBuilder = endOfStakingPeriodUpdater.updateNodes(
tokenContext, exchangeRateManager.exchangeRates(), weightUpdates);
tokenContext, exchangeRateManager.exchangeRates(), weightUpdates, useRosterLifecycle);
if (streamBuilder != null) {
stack.commitTransaction(streamBuilder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ void processUpdateCalledForGenesisTxn() {

subject.process(dispatch, stack, context, RECORDS, true, Instant.EPOCH);

verify(stakingPeriodCalculator).updateNodes(eq(context), eq(ExchangeRateSet.DEFAULT), any(BiConsumer.class));
verify(stakingPeriodCalculator)
.updateNodes(eq(context), eq(ExchangeRateSet.DEFAULT), any(BiConsumer.class), eq(false));
verify(exchangeRateManager).updateMidnightRates(stack);
}

Expand Down Expand Up @@ -167,7 +168,8 @@ void processUpdateCalledForNextPeriodWithRecordsStreamMode() {
.updateNodes(
argThat(stakingContext -> currentConsensusTime.equals(stakingContext.consensusTime())),
eq(ExchangeRateSet.DEFAULT),
any(BiConsumer.class));
any(BiConsumer.class),
eq(false));
verify(exchangeRateManager).updateMidnightRates(stack);
}

Expand All @@ -193,7 +195,8 @@ void processUpdateCalledForNextPeriodWithBlocksStreamMode() {
.updateNodes(
argThat(stakingContext -> currentConsensusTime.equals(stakingContext.consensusTime())),
eq(ExchangeRateSet.DEFAULT),
any(BiConsumer.class));
any(BiConsumer.class),
eq(false));
verify(exchangeRateManager).updateMidnightRates(stack);
}

Expand All @@ -203,7 +206,7 @@ void processUpdateExceptionIsCaught() {
given(exchangeRateManager.exchangeRates()).willReturn(ExchangeRateSet.DEFAULT);
doThrow(new RuntimeException("test exception"))
.when(stakingPeriodCalculator)
.updateNodes(any(), eq(ExchangeRateSet.DEFAULT), any(BiConsumer.class));
.updateNodes(any(), eq(ExchangeRateSet.DEFAULT), any(BiConsumer.class), eq(false));
given(blockStore.getLastBlockInfo())
.willReturn(BlockInfo.newBuilder()
.consTimeOfLastHandledTxn(new Timestamp(CONSENSUS_TIME_1234567.getEpochSecond(), 0))
Expand All @@ -215,7 +218,8 @@ void processUpdateExceptionIsCaught() {

Assertions.assertThatNoException()
.isThrownBy(() -> subject.process(dispatch, stack, context, RECORDS, false, Instant.EPOCH));
verify(stakingPeriodCalculator).updateNodes(eq(context), eq(ExchangeRateSet.DEFAULT), any(BiConsumer.class));
verify(stakingPeriodCalculator)
.updateNodes(eq(context), eq(ExchangeRateSet.DEFAULT), any(BiConsumer.class), eq(false));
verify(exchangeRateManager).updateMidnightRates(stack);
}

Expand Down
Loading

0 comments on commit f7b6f69

Please sign in to comment.