hashgraph / hedera-services

Crypto, token, consensus, file, and smart contract services for the Hedera public ledger
Apache License 2.0
266 stars 118 forks source link

Deserialized copy of a `VirtualMap` must be immutable #13930

Open imalygin opened 3 weeks ago

imalygin commented 3 weeks ago

Description

This issue manifests itself as a failure in VirtualHasher:

2024-06-14 22:30:36.603 12       ERROR EXCEPTION        <<virtual-pipeline: lifecycle #0>> VirtualPipeline: exception on virtual pipeline thread
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 24
    at com.swirlds.virtualmap.internal.hash.VirtualHasher.hash(VirtualHasher.java:401) ~[swirlds-virtualmap-0.51.0.jar:?]
    at com.swirlds.virtualmap.internal.merkle.VirtualRootNode.computeHash(VirtualRootNode.java:1313) ~[swirlds-virtualmap-0.51.0.jar:?]
    at com.swirlds.virtualmap.internal.pipeline.VirtualPipeline.hashCopy(VirtualPipeline.java:402) ~[swirlds-virtualmap-0.51.0.jar:?]
    at com.swirlds.virtualmap.internal.pipeline.VirtualPipeline.merge(VirtualPipeline.java:546) ~[swirlds-virtualmap-0.51.0.jar:?]
    at com.swirlds.virtualmap.internal.pipeline.VirtualPipeline.hashFlushMerge(VirtualPipeline.java:576) ~[swirlds-virtualmap-0.51.0.jar:?]
    at com.swirlds.virtualmap.internal.pipeline.VirtualPipeline.doWork(VirtualPipeline.java:589) ~[swirlds-virtualmap-0.51.0.jar:?]

This issue happens when PCLI attempts to apply an event stream to a state loaded from a snapshot:

#! /bin/sh

STATE_LOC=/mnt/c/tmp/round174012606/SignedState.swh
EVENT_STREAMS_LOC=/mnt/c/tmp/someEventsAfterRound174012606
DDIR=/mnt/c/Users/user/IdeaProjects/hedera-services/hedera-node/data

rm -rf out/ data/apps data/lib hedera-node/data/recordStreams
ln -s $DDIR/apps data/apps
ln -s $DDIR/lib data/lib

./pcli.sh -D event-stream recover \
  --id=0 \
  --main-name=com.hedera.node.app.ServicesMain \
  -L "data/lib" -L "data/apps" \
  -J "-Dhedera.workflows.enabled=true" \
  -J "-Dhedera.recordStream.logDir=hedera-node/data/recordStreams" \
  -J "-Xms36g" \
  $STATE_LOC $EVENT_STREAMS_LOC

Steps to reproduce

  1. Load a snapshot without creating a copy of it
  2. Make an update to an existing entry
  3. Run rehash for the map

Additional context

Updating a deserialized of VirtualMap copy is a violation of its contract. A user must create a copy before use it.

For example, here is how it works in production code:

com.swirlds.platform.state.signed.StartupStateUtils#getInitialState:

     final ReservedSignedState loadedState =
                StartupStateUtils.loadStateFile(platformContext, selfId, mainClassName, swirldName, softwareVersion);

        try (loadedState) {
            if (loadedState.isNotNull()) {
                logger.info(
                        STARTUP.getMarker(),
                        new SavedStateLoadedPayload(
                                loadedState.get().getRound(), loadedState.get().getConsensusTimestamp()));

                return copyInitialSignedState(platformContext, loadedState.get()); // creates a copy of the state (including VitualMaps)
            }
        }

However, nothing in the map prevents that, and this has to be fixed.

Specifically, leafIndexesAreImmutable flag in VirtualNodeCache has to be set to true. By default it's false, and that's what makes the cache copy modifiable.

Hedera network

other

Version

v0.49

Operating system

None