Skip to content
Open
Show file tree
Hide file tree
Changes from 80 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
e88d62a
initial highest node id changes
derektriley Oct 24, 2025
e12a1b2
unit tests
derektriley Oct 24, 2025
a67c57a
updates
derektriley Oct 24, 2025
387ed61
Update proto
derektriley Oct 24, 2025
1c6f23a
move schmema migration
derektriley Oct 24, 2025
35f386d
wip
derektriley Oct 24, 2025
bb69099
wip
derektriley Oct 25, 2025
cc9539a
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 3, 2025
f7d8124
extract migration logic to v69 schema classes
JivkoKelchev Nov 4, 2025
fe97d3b
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 4, 2025
9f5986d
fix tests
JivkoKelchev Nov 4, 2025
a23c8c0
Replace deprecated EntityNumber with NodeId. Fix state changes valida…
JivkoKelchev Nov 4, 2025
5dd1be7
Fix tests
JivkoKelchev Nov 4, 2025
7740ca8
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 4, 2025
5b6179d
Fix unit tests
JivkoKelchev Nov 5, 2025
3b6757e
Fix more unit tests
JivkoKelchev Nov 5, 2025
89d0f8d
Update migration logic
JivkoKelchev Nov 5, 2025
4a563c5
fix module info
JivkoKelchev Nov 5, 2025
a4aeadf
fix module info
JivkoKelchev Nov 6, 2025
9b963a7
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 6, 2025
63aa4d8
Fix restart and iss tests. Remove V069AddressBookSchema.java
JivkoKelchev Nov 6, 2025
500749a
Spotless
JivkoKelchev Nov 6, 2025
8bdbed4
Fix test
JivkoKelchev Nov 7, 2025
072b575
Remove deleted nodes on upgrade
JivkoKelchev Nov 11, 2025
ea6dc7d
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 11, 2025
ebcddaf
Revert test
JivkoKelchev Nov 11, 2025
4b7a1f7
Fix SystemTransactions node create
JivkoKelchev Nov 12, 2025
f7b3458
Fix SystemTransactions node create
JivkoKelchev Nov 13, 2025
4864c8b
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 13, 2025
8787c0c
Fix unit tests
JivkoKelchev Nov 13, 2025
9911c89
revert unwanted changes
JivkoKelchev Nov 14, 2025
e14b984
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 14, 2025
15e2b1b
add java doc
JivkoKelchev Nov 14, 2025
249301e
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 18, 2025
1e35c36
Rename the state
JivkoKelchev Nov 19, 2025
581ba93
Remove redundant ISS exclusion in subProcess task
JivkoKelchev Nov 19, 2025
f59b08b
Remove duplicate code
JivkoKelchev Nov 19, 2025
e41faf5
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 19, 2025
a7a57e3
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 20, 2025
c130f79
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 24, 2025
6ca2a70
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 25, 2025
f3d1c43
Fix unit test
JivkoKelchev Nov 25, 2025
cc862b6
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 26, 2025
210bf0d
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Nov 27, 2025
3a9e90d
Fix
JivkoKelchev Nov 28, 2025
1261983
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 1, 2025
477a123
Fix
JivkoKelchev Dec 1, 2025
bffa0f9
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 2, 2025
bc4ff3c
Update staking info store to use the highest node id
JivkoKelchev Dec 2, 2025
c62071c
Fix unit test
JivkoKelchev Dec 2, 2025
e68784c
Fix unit tests
JivkoKelchev Dec 3, 2025
dee6150
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 10, 2025
25f71bf
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 10, 2025
21cb8f6
Set the schema version to 70
JivkoKelchev Dec 10, 2025
542ce5c
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 16, 2025
8f40855
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 18, 2025
f830ef7
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Dec 19, 2025
2715819
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Jan 5, 2026
67ea14c
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Jan 6, 2026
3a125f8
Fix unit tests
JivkoKelchev Jan 6, 2026
d499abe
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Jan 9, 2026
f8cfcce
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 16, 2026
3a01ea2
Fix Merge conflicts. Update schema version.
JivkoKelchev Feb 17, 2026
2ddf2fa
Fix StandaloneFeeCalculatorTest unit tests
JivkoKelchev Feb 17, 2026
6a8fa1a
rename the state id
JivkoKelchev Feb 17, 2026
ae5a6fd
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 17, 2026
92e9479
fix: Wrong start of block state hash
JivkoKelchev Feb 17, 2026
98eb6b8
init highest node id state with -1
JivkoKelchev Feb 18, 2026
01c5c95
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 18, 2026
30d5869
Fix merge conflicts
JivkoKelchev Feb 18, 2026
79dad8a
fix: Wrong start of block state hash
JivkoKelchev Feb 18, 2026
95878a4
update protobufs
JivkoKelchev Feb 18, 2026
b552a8c
update protobufs
JivkoKelchev Feb 18, 2026
98f4584
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 19, 2026
28186dd
Add unit tests
JivkoKelchev Feb 19, 2026
113e71e
Add unit tests
JivkoKelchev Feb 19, 2026
9c83ec3
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 19, 2026
11aaba1
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 20, 2026
e695c2e
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 24, 2026
96fbeb0
Address comments
JivkoKelchev Feb 24, 2026
17b2217
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Feb 26, 2026
70252b5
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Mar 4, 2026
d9fcf64
Update the schema version
JivkoKelchev Mar 4, 2026
9a913c3
Add schema unit tests
JivkoKelchev Mar 4, 2026
df321c0
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Mar 4, 2026
d0a255a
Update to NodeCreateHandler logic
derektriley Mar 5, 2026
b646f90
Merge branch 'main' into 18855-highest-nodeId
JivkoKelchev Mar 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,11 @@ enum StateIdentifier {
*/
STATE_ID_REGISTERED_NODES = 56;

/**
* A state identifier for the highest node identifier allocated so far.
*/
STATE_ID_HIGHEST_NODE_ID = 57;

/**
* A state identifier for the round receipts queue. Queue state.
*/
Expand Down Expand Up @@ -736,6 +741,11 @@ message SingletonUpdateChange {
* Node payments SHALL be updated for every non-empty block.
*/
com.hedera.hapi.node.state.token.NodePayments node_payments_value = 19;

/**
* A change to the highest node id singleton.
*/
com.hedera.hapi.platform.state.NodeId node_id_value = 20;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,11 @@ message StateValue {
*/
com.hedera.hapi.node.state.token.NodePayments TokenService_I_NODE_PAYMENTS = 55;

/**
* A state identifier for the next node Identifier. Singleton state.
*/
com.hedera.hapi.platform.state.NodeId EntityIdService_I_NODE_ID = 57;

/**
* Metadata of the round receipts queue.
*/
Expand Down Expand Up @@ -806,6 +811,11 @@ enum SingletonType {
*/
TokenService_I_NODE_PAYMENTS = 55;

/**
* A state identifier for the highest node identifier allocated so far. Singleton state.
*/
EntityIdService_I_NODE_ID = 57;

// Queue metadata

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
case STATE_ID_ROSTER_STATE -> "RosterService.ROSTER_STATE";
case STATE_ID_ROSTERS -> "RosterService.ROSTERS";
case STATE_ID_ENTITY_COUNTS -> "EntityIdService.ENTITY_COUNTS";
case STATE_ID_HIGHEST_NODE_ID -> "EntityIdService.HIGHEST_NODE_ID";

Check warning on line 59 in hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/blocks/BlockStreamUtils.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/blocks/BlockStreamUtils.java#L59

Added line #L59 was not covered by tests
case STATE_ID_TRANSACTION_RECEIPTS -> "RecordCache.TRANSACTION_RECEIPTS";
case STATE_ID_SCHEDULES_BY_EQUALITY -> "ScheduleService.SCHEDULES_BY_EQUALITY";
case STATE_ID_SCHEDULES_BY_EXPIRY_SEC -> "ScheduleService.SCHEDULES_BY_EXPIRY_SEC";
Expand Down Expand Up @@ -117,6 +118,7 @@
case HISTORY_PROOF_CONSTRUCTION_VALUE -> singletonUpdateChange.historyProofConstructionValueOrThrow();
case CRS_STATE_VALUE -> singletonUpdateChange.crsStateValueOrThrow();
case NODE_PAYMENTS_VALUE -> singletonUpdateChange.nodePaymentsValueOrThrow();
case NODE_ID_VALUE -> singletonUpdateChange.nodeIdValueOrThrow();

Check warning on line 121 in hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/blocks/BlockStreamUtils.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/blocks/BlockStreamUtils.java#L121

Added line #L121 was not covered by tests
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import com.hedera.hapi.node.state.roster.RosterEntry;
import com.hedera.node.app.hapi.utils.EntityType;
import com.hedera.node.app.service.addressbook.ReadableNodeStore;
import com.hedera.node.app.service.entityid.ReadableEntityCounters;
import com.hedera.node.app.service.entityid.ReadableEntityIdStore;
import com.swirlds.state.spi.ReadableKVState;
import com.swirlds.state.spi.ReadableStates;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -32,17 +32,17 @@ public class ReadableNodeStoreImpl implements ReadableNodeStore {
*/
private final ReadableKVState<EntityNumber, Node> nodesState;

private final ReadableEntityCounters entityCounters;
protected final ReadableEntityIdStore entityIdStore;

/**
* Create a new {@link ReadableNodeStoreImpl} instance.
*
* @param states The state to use.
*/
public ReadableNodeStoreImpl(
@NonNull final ReadableStates states, @NonNull final ReadableEntityCounters entityCounters) {
@NonNull final ReadableStates states, @NonNull final ReadableEntityIdStore entityIdStore) {
requireNonNull(states);
this.entityCounters = requireNonNull(entityCounters);
this.entityIdStore = requireNonNull(entityIdStore);
this.nodesState = states.get(NODES_STATE_ID);
}

Expand All @@ -65,12 +65,12 @@ public Node get(final long nodeId) {
}

/**
* Returns the number of topics in the state.
* Returns the number of nodes in the state.
*
* @return the number of topics in the state
* @return the number of nodes in the state
*/
public long sizeOfState() {
return entityCounters.getCounterFor(EntityType.NODE);
return entityIdStore.getCounterFor(EntityType.NODE);
}

protected <T extends ReadableKVState<EntityNumber, Node>> T nodesState() {
Expand All @@ -79,9 +79,9 @@ protected <T extends ReadableKVState<EntityNumber, Node>> T nodesState() {

@NonNull
public List<EntityNumber> keys() {
final var size = sizeOfState();
final var keys = new ArrayList<EntityNumber>();
for (int i = 0; i < size; i++) {
final long highestExclusive = entityIdStore.peekAtNextNodeId();
for (long i = 0; i < highestExclusive; i++) {
final var key = new EntityNumber(i);
final var node = nodesState.get(key);
if (node != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.hedera.hapi.node.state.addressbook.Node;
import com.hedera.hapi.node.state.common.EntityNumber;
import com.hedera.node.app.hapi.utils.EntityType;
import com.hedera.node.app.service.entityid.WritableEntityCounters;
import com.hedera.node.app.service.entityid.WritableEntityIdStore;
import com.swirlds.state.spi.WritableKVState;
import com.swirlds.state.spi.WritableStates;
import edu.umd.cs.findbugs.annotations.NonNull;
Expand All @@ -20,16 +20,15 @@
* This class is not complete, it will be extended with other methods like remove, update etc.,
*/
public class WritableNodeStore extends ReadableNodeStoreImpl {
private final WritableEntityCounters entityCounters;
private final WritableEntityIdStore writableEntityIdStore;
/**
* Create a new {@link WritableNodeStore} instance.
*
* @param states The state to use.
*/
public WritableNodeStore(
@NonNull final WritableStates states, @NonNull final WritableEntityCounters entityCounters) {
super(states, entityCounters);
this.entityCounters = entityCounters;
public WritableNodeStore(@NonNull final WritableStates states, @NonNull final WritableEntityIdStore entityIdStore) {
super(states, entityIdStore);
this.writableEntityIdStore = entityIdStore;
}

@Override
Expand All @@ -56,7 +55,16 @@ public void put(@NonNull final Node node) {
*/
public void putAndIncrementCount(@NonNull final Node node) {
put(node);
entityCounters.incrementEntityTypeCount(EntityType.NODE);
writableEntityIdStore.incrementEntityTypeCount(EntityType.NODE);
}

/**
* Removes the node with the given id from state and decrements the node entity count.
* @param nodeId the node id to remove
*/
public void remove(final long nodeId) {
nodesState().remove(EntityNumber.newBuilder().number(nodeId).build());
writableEntityIdStore.decrementEntityTypeCounter(EntityType.NODE);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_NODES_CREATED;
import static com.hedera.node.app.service.addressbook.AddressBookHelper.checkDABEnabled;
import static com.hedera.node.app.service.addressbook.impl.validators.AddressBookValidator.validateX509Certificate;
import static com.hedera.node.app.spi.workflows.HandleException.validateFalse;
import static com.hedera.node.app.spi.workflows.HandleContext.DispatchMetadata.Type.SYSTEM_TXN_CREATION_ENTITY_NUM;
import static com.hedera.node.app.spi.workflows.HandleException.validateTrue;
import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck;
import static java.util.Objects.requireNonNull;
Expand All @@ -18,10 +18,12 @@
import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.SubType;
import com.hedera.hapi.node.state.addressbook.Node;
import com.hedera.node.app.service.addressbook.ReadableNodeStore;
import com.hedera.node.app.service.addressbook.impl.WritableAccountNodeRelStore;
import com.hedera.node.app.service.addressbook.impl.WritableNodeStore;
import com.hedera.node.app.service.addressbook.impl.records.NodeCreateStreamBuilder;
import com.hedera.node.app.service.addressbook.impl.validators.AddressBookValidator;
import com.hedera.node.app.service.entityid.NodeIdGenerator;
import com.hedera.node.app.service.token.ReadableAccountStore;
import com.hedera.node.app.spi.fees.FeeContext;
import com.hedera.node.app.spi.fees.Fees;
Expand Down Expand Up @@ -90,8 +92,13 @@ public void handle(@NonNull final HandleContext handleContext) {
final var accountNodeRelStore = storeFactory.writableStore(WritableAccountNodeRelStore.class);
final var accountStore = storeFactory.readableStore(ReadableAccountStore.class);
final var accountId = op.accountIdOrElse(AccountID.DEFAULT);

validateFalse(nodeStore.sizeOfState() >= nodeConfig.maxNumber(), MAX_NODES_CREATED);
final var maybeSystemTxnDispatchEntityNum =
handleContext.dispatchMetadata().getMetadata(SYSTEM_TXN_CREATION_ENTITY_NUM, Long.class);
final var maybeNodeIsInStateForSystemTxn =
isNodeInStateForSystemTxn(handleContext.dispatchMetadata(), handleContext.nodeIdGenerator(), nodeStore);
validateTrue(
maybeNodeIsInStateForSystemTxn || (nodeStore.sizeOfState() < nodeConfig.maxNumber()),
MAX_NODES_CREATED);
addressBookValidator.validateAccount(
accountId, accountStore, accountNodeRelStore, handleContext.expiryValidator());
addressBookValidator.validateDescription(op.description(), nodeConfig);
Expand All @@ -116,12 +123,24 @@ public void handle(@NonNull final HandleContext handleContext) {
nodeBuilder.grpcProxyEndpoint(op.grpcProxyEndpoint());
}

// Since nodes won't be removed from state, we can set the nodeId to the next available id
// in the state based on the size of the state.
final var node = nodeBuilder.nodeId(nodeStore.sizeOfState()).build();
long nextNodeId;
Node node;

// If a system-dispatched transplant transaction for nodes in override network (non-prod environments)
// attempts to create a node that already exists in the state (even if marked as deleted),
// neither the highest node ID nor the entity count should be incremented.
if (maybeNodeIsInStateForSystemTxn) {
// Assign node id using the one provided by the system dispatch metadata
nextNodeId = maybeSystemTxnDispatchEntityNum.get();
node = nodeBuilder.nodeId(nextNodeId).build();
nodeStore.put(node);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we know if the transplant didnt add any new nodes. (total nodes > current network)

} else {
// Assign node id using a dedicated generator to avoid reuse
nextNodeId = handleContext.nodeIdGenerator().newNodeId();
node = nodeBuilder.nodeId(nextNodeId).build();
nodeStore.putAndIncrementCount(node);
}

nodeStore.putAndIncrementCount(node);
// add account id relation
accountNodeRelStore.put(op.accountIdOrThrow(), node.nodeId());

final var recordBuilder = handleContext.savepointStack().getBaseBuilder(NodeCreateStreamBuilder.class);
Expand All @@ -141,4 +160,25 @@ public Fees calculateFees(@NonNull final FeeContext feeContext) {
calculator.addVerificationsPerTransaction(Math.max(0, feeContext.numTxnSignatures() - 1));
return calculator.calculate();
}

/**
* Determines if a system-dispatched node creation transaction targets a node ID
* that already exists in the current state.
*
* <p>If the dispatch metadata provides a node ID (as in system transactions), this method checks
* if that node ID is already present in the node store. If not, it uses the next node ID from the generator.
*
* @param metadata the dispatch metadata containing optional system transaction node ID
* @param nodeIdGenerator the generator for new node IDs
* @param nodeStore the store containing current node state
* @return {@code true} if the node ID (from metadata or generator) already exists in the state; {@code false} otherwise
*/
private boolean isNodeInStateForSystemTxn(
final HandleContext.DispatchMetadata metadata,
final NodeIdGenerator nodeIdGenerator,
final ReadableNodeStore nodeStore) {
final var systemTxnCreationNum = metadata.getMetadataIfPresent(SYSTEM_TXN_CREATION_ENTITY_NUM, Long.class);
final var nextNodeId = systemTxnCreationNum != null ? systemTxnCreationNum : nodeIdGenerator.peekAtNewNodeId();
return nodeStore.get(nextNodeId) != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void registersExpectedSchema() {
verify(schemaRegistry, times(2)).register(schemaCaptor.capture());
final var schemas = schemaCaptor.getAllValues();
assertThat(schemas).hasSize(2);
assertThat(schemas.getFirst()).isInstanceOf(V053AddressBookSchema.class);
assertThat(schemas.getLast()).isInstanceOf(V068AddressBookSchema.class);
assertThat(schemas.get(0)).isInstanceOf(V053AddressBookSchema.class);
assertThat(schemas.get(1)).isInstanceOf(V068AddressBookSchema.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ void keysWorks() {
.value(new EntityNumber(2), mock(Node.class))
.value(new EntityNumber(3), mock(Node.class))
.value(new EntityNumber(0), mock(Node.class));
givenEntityCounters(3);
readableNodeState = stateBuilder.build();
given(readableStates.<EntityNumber, Node>get(NODES_STATE_ID)).willReturn(readableNodeState);
subject = new ReadableNodeStoreImpl(readableStates, writableEntityCounters);
writableEntityCounters.adjustEntityCount(EntityType.NODE, 3L);
final var keys = subject.keys();
assertFalse(keys.isEmpty());
assertEquals(keys, List.of(new EntityNumber(0), new EntityNumber(1), new EntityNumber(2), new EntityNumber(3)));
Expand All @@ -107,8 +107,8 @@ void snapshotOfFutureRosterIncludesAllUndeletedDefinitions() {
EntityNumber.newBuilder().number(4).build(),
Node.newBuilder().nodeId(4).weight(40).deleted(true).build())
.build();
givenEntityCounters(3);
given(readableStates.<EntityNumber, Node>get(anyInt())).willReturn(nodesState);
writableEntityCounters.adjustEntityCount(EntityType.NODE, 3L);

subject = new ReadableNodeStoreImpl(readableStates, writableEntityCounters);
final var result = subject.snapshotOfFutureRoster(nodeId ->
Expand Down
Loading
Loading