diff --git a/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskNodeImpl.java b/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskNodeImpl.java index 4a8d4c9c5..f95caf024 100644 --- a/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskNodeImpl.java +++ b/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskNodeImpl.java @@ -86,7 +86,7 @@ protected DiskStore mkStore() { } @Override - public StoreTransaction getTransaction() { + public StoreTransaction getStoreTransaction() { return transaction; } diff --git a/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskStoreTransaction.java b/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskStoreTransaction.java index 4fbef2b1f..b58badf7b 100644 --- a/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskStoreTransaction.java +++ b/io-hotmoka-node-disk/src/main/java/io/hotmoka/node/disk/internal/DiskStoreTransaction.java @@ -43,58 +43,74 @@ public long getNow() { return System.currentTimeMillis(); } + public boolean isJustStore() { + synchronized (getLock()) { + return requests.isEmpty() && responses.isEmpty() && histories.isEmpty(); + } + } + @Override public Optional getResponseUncommitted(TransactionReference reference) { - var uncommittedResponse = responses.get(reference); - if (uncommittedResponse != null) - return Optional.of(uncommittedResponse); - else - return getStore().getResponse(reference); + synchronized (getLock()) { + var uncommittedResponse = responses.get(reference); + if (uncommittedResponse != null) + return Optional.of(uncommittedResponse); + else + return getStore().getResponse(reference); + } } @Override public Stream getHistoryUncommitted(StorageReference object) throws StoreException { - var uncommittedHistory = histories.get(object); - if (uncommittedHistory != null) - return Stream.of(uncommittedHistory); - else - return getStore().getHistory(object); + synchronized (getLock()) { + var uncommittedHistory = histories.get(object); + if (uncommittedHistory != null) + return Stream.of(uncommittedHistory); + else + return getStore().getHistory(object); + } } @Override public Optional getManifestUncommitted() { - var uncommittedManifest = manifest.get(); - if (uncommittedManifest != null) - return Optional.of(uncommittedManifest); - else - return getStore().getManifest(); + synchronized (getLock()) { + var uncommittedManifest = manifest.get(); + if (uncommittedManifest != null) + return Optional.of(uncommittedManifest); + else + return getStore().getManifest(); + } } @Override public DiskStore commit() throws StoreException { var store = getStore(); - // we report all the updates occurred during this transaction into the store - var manifest = this.manifest.get(); - if (manifest != null) - store.setManifest(manifest); + synchronized (getLock()) { + // we report all the updates occurred during this transaction into the store + var manifest = this.manifest.get(); + if (manifest != null) + store.setManifest(manifest); - for (var entry: errors.entrySet()) - store.setError(entry.getKey(), entry.getValue()); + for (var entry: errors.entrySet()) + store.setError(entry.getKey(), entry.getValue()); - for (var entry: histories.entrySet()) - store.setHistory(entry.getKey(), Stream.of(entry.getValue())); + int progressive = 0; + for (var entry: requests.entrySet()) + store.setRequest(progressive++, entry.getKey(), entry.getValue()); - int progressive = 0; - for (var entry: requests.entrySet()) - store.setRequest(progressive++, entry.getKey(), entry.getValue()); + progressive = 0; + for (var entry: responses.entrySet()) + store.setResponse(progressive++, entry.getKey(), entry.getValue()); - progressive = 0; - for (var entry: responses.entrySet()) - store.setResponse(progressive++, entry.getKey(), entry.getValue()); + // it's important to write the histories at the end, so that there are no + // dangling references in the histories (ie, references to responses not yet in store) + for (var entry: histories.entrySet()) + store.setHistory(entry.getKey(), Stream.of(entry.getValue())); - if (progressive > 0) - store.increaseBlockNumber(); + if (progressive > 0) + store.increaseBlockNumber(); + } return store; } diff --git a/io-hotmoka-node-local-api/src/main/java/io/hotmoka/node/local/api/StoreUtility.java b/io-hotmoka-node-local-api/src/main/java/io/hotmoka/node/local/api/StoreUtility.java index 8d869007b..572828f6c 100644 --- a/io-hotmoka-node-local-api/src/main/java/io/hotmoka/node/local/api/StoreUtility.java +++ b/io-hotmoka-node-local-api/src/main/java/io/hotmoka/node/local/api/StoreUtility.java @@ -27,6 +27,7 @@ import io.hotmoka.node.api.updates.Update; import io.hotmoka.node.api.updates.UpdateOfField; import io.hotmoka.node.api.values.StorageReference; +import io.hotmoka.stores.Store; import io.hotmoka.stores.StoreException; /** @@ -38,6 +39,7 @@ */ public interface StoreUtility { + Store getStore(); /** * Determines if the node is initialized, that is, its manifest has been set, * although possibly not yet committed. diff --git a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/AbstractLocalNodeImpl.java b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/AbstractLocalNodeImpl.java index 0df7fc5dc..63f683e44 100644 --- a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/AbstractLocalNodeImpl.java +++ b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/AbstractLocalNodeImpl.java @@ -330,7 +330,7 @@ private AbstractLocalNodeImpl(C config, ConsensusConfig consensus, boolean * * @return the currently executing transaction */ - public abstract StoreTransaction getTransaction(); + public abstract StoreTransaction getStoreTransaction(); /** * Determines if this node has not been closed yet. @@ -693,7 +693,7 @@ public final TransactionResponse deliverTransaction(TransactionRequest reques var reference = TransactionReferences.of(hasher.hash(request)); try { - var transaction = getTransaction(); + var storeTransaction = getStoreTransaction(); try { LOGGER.info(reference + ": delivering start (" + request.getClass().getSimpleName() + ')'); @@ -701,9 +701,9 @@ public final TransactionResponse deliverTransaction(TransactionRequest reques TransactionResponse response; synchronized (deliverTransactionLock) { - ResponseBuilder responseBuilder = responseBuilderFor(reference, request, transaction); + ResponseBuilder responseBuilder = responseBuilderFor(reference, request, storeTransaction); response = responseBuilder.getResponse(); - transaction.push(reference, request, response); + storeTransaction.push(reference, request, response); responseBuilder.replaceReverifiedResponses(); scheduleForNotificationOfEvents(response); takeNoteForNextReward(request, response); @@ -714,18 +714,18 @@ public final TransactionResponse deliverTransaction(TransactionRequest reques return response; } catch (TransactionRejectedException e) { - transaction.push(reference, request, trimmedMessage(e)); + storeTransaction.push(reference, request, trimmedMessage(e)); LOGGER.info(reference + ": delivering failed: " + trimmedMessage(e)); LOGGER.log(Level.INFO, "transaction rejected", e); throw e; } catch (ClassNotFoundException | NodeException | UnknownReferenceException e) { - transaction.push(reference, request, trimmedMessage(e)); + storeTransaction.push(reference, request, trimmedMessage(e)); LOGGER.log(Level.SEVERE, reference + ": delivering failed with unexpected exception", e); throw new RuntimeException(e); } catch (RuntimeException e) { - transaction.push(reference, request, trimmedMessage(e)); + storeTransaction.push(reference, request, trimmedMessage(e)); LOGGER.log(Level.WARNING, reference + ": delivering failed with unexpected exception", e); throw e; } @@ -759,7 +759,7 @@ public final boolean rewardValidators(String behaving, String misbehaving) { return false; try { - Optional manifest = getTransaction().getManifestUncommitted(); + Optional manifest = getStoreTransaction().getManifestUncommitted(); if (manifest.isPresent()) { // we use the manifest as caller, since it is an externally-owned account StorageReference caller = manifest.get(); @@ -796,7 +796,7 @@ else if (minted.signum() < 0) { StorageValues.bigIntegerOf(gasConsumedSinceLastReward), StorageValues.bigIntegerOf(numberOfTransactionsSinceLastReward)); checkTransaction(request); - ResponseBuilder responseBuilder = responseBuilderFor(TransactionReferences.of(hasher.hash(request)), request, getTransaction()); + ResponseBuilder responseBuilder = responseBuilderFor(TransactionReferences.of(hasher.hash(request)), request, getStoreTransaction()); TransactionResponse response = responseBuilder.getResponse(); // if there is only one update, it is the update of the nonce of the manifest: we prefer not to expand // the store with the transaction, so that the state stabilizes, which might give @@ -920,7 +920,7 @@ else if (request instanceof InitializationTransactionRequest itr) throw new TransactionRejectedException("Unexpected transaction request of class " + request.getClass().getName()); } - protected void setStore(Store store) { + public void setStore(Store store) { this.store = (S) store; // TODO } @@ -950,7 +950,7 @@ protected void setStore(Store store) { * @return the response, if any */ Optional getResponseUncommitted(TransactionReference reference) throws StoreException { - var transaction = getTransaction(); + var transaction = getStoreTransaction(); if (transaction != null) return transaction.getResponseUncommitted(reference); else @@ -967,7 +967,7 @@ Optional getResponseUncommitted(TransactionReference refere * @throws StoreException if the store is not able to perform the operation */ Stream getHistoryUncommitted(StorageReference object) throws StoreException { - var transaction = getTransaction(); + var transaction = getStoreTransaction(); if (transaction != null) return transaction.getHistoryUncommitted(object); else @@ -982,7 +982,7 @@ Stream getHistoryUncommitted(StorageReference object) thro * @throws StoreException if the store is not able to complete the operation correctly */ Optional getManifestUncommitted() throws StoreException { - var transaction = getTransaction(); + var transaction = getStoreTransaction(); if (transaction != null) return transaction.getManifestUncommitted(); else @@ -1142,11 +1142,8 @@ private BigInteger addInflation(BigInteger gas) { } private void scheduleForNotificationOfEvents(TransactionResponse response) { - if (response instanceof TransactionResponseWithEvents) { - var responseWithEvents = (TransactionResponseWithEvents) response; - if (responseWithEvents.getEvents().count() > 0L) - scheduleForNotificationOfEvents(responseWithEvents); - } + if (response instanceof TransactionResponseWithEvents responseWithEvents && responseWithEvents.getEvents().count() > 0L) + scheduleForNotificationOfEvents(responseWithEvents); } /** diff --git a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/NonInitialResponseBuilderImpl.java b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/NonInitialResponseBuilderImpl.java index bdb47659d..34c356e29 100644 --- a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/NonInitialResponseBuilderImpl.java +++ b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/NonInitialResponseBuilderImpl.java @@ -239,7 +239,7 @@ private void payerMustBeContract() throws TransactionRejectedException, ClassNot */ private void signatureMustBeValid() throws Exception { // if the node is not initialized yet, the signature is not checked - if (transactionIsSigned() && node.getStoreUtilities().nodeIsInitializedUncommitted() + if (transactionIsSigned() && storeTransaction.nodeIsInitializedUncommitted() && !node.caches.signatureIsValid((SignedTransactionRequest) request, determineSignatureAlgorithm())) throw new TransactionRejectedException("invalid request signature"); } @@ -253,7 +253,7 @@ private void requestMustHaveCorrectChainId() throws TransactionRejectedException // unsigned transactions do not check the chain identifier; // if the node is not initialized yet, the chain id is not checked try { - if (transactionIsSigned() && node.getStoreUtilities().nodeIsInitializedUncommitted()) { + if (transactionIsSigned() && storeTransaction.nodeIsInitializedUncommitted()) { String chainIdOfNode = consensus.getChainId(); String chainId = ((SignedTransactionRequest) request).getChainId(); if (!chainIdOfNode.equals(chainId)) @@ -273,8 +273,7 @@ private void requestMustHaveCorrectChainId() throws TransactionRejectedException private void callerAndRequestMustAgreeOnNonce() throws TransactionRejectedException { // calls to @View methods do not check the nonce if (!isView()) { - BigInteger expected = node.getStoreUtilities().getNonceUncommitted(request.getCaller()); - + BigInteger expected = storeTransaction.getNonceUncommitted(request.getCaller()); if (!expected.equals(request.getNonce())) throw new TransactionRejectedException("Incorrect nonce: the request reports " + request.getNonce() + " but the account " + request.getCaller() + " contains " + expected); @@ -321,7 +320,7 @@ private void gasLimitIsInsideBounds() throws TransactionRejectedException { private void gasPriceIsLargeEnough() throws TransactionRejectedException { // before initialization, the gas price is not yet available try { - if (transactionIsSigned() && node.getStoreUtilities().nodeIsInitializedUncommitted() && !ignoreGasPrice()) { + if (transactionIsSigned() && storeTransaction.nodeIsInitializedUncommitted() && !ignoreGasPrice()) { BigInteger currentGasPrice = node.caches.getGasPrice().get(); if (request.getGasPrice().compareTo(currentGasPrice) < 0) throw new TransactionRejectedException("the gas price of the request is smaller than the current gas price (" + request.getGasPrice() + " < " + currentGasPrice + ")"); @@ -340,7 +339,7 @@ private void gasPriceIsLargeEnough() throws TransactionRejectedException { */ private void payerCanPayForAllPromisedGas() throws TransactionRejectedException { BigInteger cost = costOf(request.getGasLimit()); - BigInteger totalBalance = node.getStoreUtilities().getTotalBalanceUncommitted(getPayerFromRequest()); + BigInteger totalBalance = storeTransaction.getTotalBalanceUncommitted(getPayerFromRequest()); if (totalBalance.subtract(cost).signum() < 0) throw new TransactionRejectedException("the payer has not enough funds to buy " + request.getGasLimit() + " units of gas"); diff --git a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/StoreUtilityImpl.java b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/StoreUtilityImpl.java index 54b5dbc36..9ff085e68 100644 --- a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/StoreUtilityImpl.java +++ b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/StoreUtilityImpl.java @@ -70,7 +70,7 @@ public StoreUtilityImpl(AbstractLocalNodeImpl node) { this.node = node; } - private Store getStore() { + public Store getStore() { return store != null ? store : node.getStore(); } diff --git a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/AbstractResponseBuilder.java b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/AbstractResponseBuilder.java index 5ac57796e..06f378beb 100644 --- a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/AbstractResponseBuilder.java +++ b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/AbstractResponseBuilder.java @@ -65,7 +65,7 @@ public abstract class AbstractResponseBuilder node; - public final StoreTransaction transaction; + public final StoreTransaction storeTransaction; /** * The object that translates storage types into their run-time class tag. @@ -97,13 +97,13 @@ public abstract class AbstractResponseBuilder transaction, AbstractLocalNodeImpl node) throws TransactionRejectedException { + protected AbstractResponseBuilder(TransactionReference reference, Request request, StoreTransaction storeTransaction, AbstractLocalNodeImpl node) throws TransactionRejectedException { try { - this.transaction = transaction; + this.storeTransaction = storeTransaction; this.request = request; this.reference = reference; this.node = node; @@ -128,7 +128,7 @@ public final EngineClassLoader getClassLoader() { @Override public final void replaceReverifiedResponses() throws NoSuchElementException, UnknownReferenceException, NodeException { - ((EngineClassLoaderImpl) classLoader).replaceReverifiedResponses(transaction); + ((EngineClassLoaderImpl) classLoader).replaceReverifiedResponses(storeTransaction); } /** @@ -149,7 +149,7 @@ public final void replaceReverifiedResponses() throws NoSuchElementException, Un * @return the wrapped or original exception */ protected static TransactionRejectedException wrapAsTransactionRejectedException(Throwable t) { - return t instanceof TransactionRejectedException ? (TransactionRejectedException) t : new TransactionRejectedException(t); + return t instanceof TransactionRejectedException tre ? tre : new TransactionRejectedException(t); } /** @@ -184,7 +184,7 @@ protected ResponseCreator() throws TransactionRejectedException { try { this.deserializer = new Deserializer(AbstractResponseBuilder.this, node.getStoreUtilities()); this.updatesExtractor = new UpdatesExtractorFromRAM(AbstractResponseBuilder.this); - this.now = transaction.getNow(); + this.now = storeTransaction.getNow(); } catch (Throwable t) { throw new TransactionRejectedException(t); @@ -282,7 +282,7 @@ public final boolean isSystemCall() { */ public final Object deserializeLastUpdateFor(StorageReference object, FieldSignature field) { try { - UpdateOfField update = node.getStoreUtilities().getLastUpdateToFieldUncommitted(object, field) + UpdateOfField update = storeTransaction.getLastUpdateToFieldUncommitted(object, field) .orElseThrow(() -> new DeserializationError("did not find the last update for " + field + " of " + object)); return deserializer.deserialize(update.getValue()); } @@ -301,7 +301,7 @@ public final Object deserializeLastUpdateFor(StorageReference object, FieldSigna * @return the value of the field */ public final Object deserializeLastUpdateForFinal(StorageReference object, FieldSignature field) { - UpdateOfField update = node.getStoreUtilities().getLastUpdateToFinalFieldUncommitted(object, field) + UpdateOfField update = storeTransaction.getLastUpdateToFinalFieldUncommitted(object, field) .orElseThrow(() -> new DeserializationError("did not find the last update for " + field + " of " + object)); return deserializer.deserialize(update.getValue()); diff --git a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/InstanceMethodCallResponseBuilder.java b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/InstanceMethodCallResponseBuilder.java index 630c807fc..d59a0eca5 100644 --- a/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/InstanceMethodCallResponseBuilder.java +++ b/io-hotmoka-node-local/src/main/java/io/hotmoka/node/local/internal/transactions/InstanceMethodCallResponseBuilder.java @@ -257,7 +257,7 @@ protected void ensureWhiteListingOf(Method executable, Object[] actuals) throws */ private void mintCoinsForRewardToValidators() { try { - Optional manifest = node.getStoreUtilities().getManifestUncommitted(); + Optional manifest = storeTransaction.getManifestUncommitted(); if (isSystemCall() && request.getStaticTarget().equals(MethodSignatures.VALIDATORS_REWARD) && manifest.isPresent() && request.getCaller().equals(manifest.get())) { Optional firstArg = request.actuals().findFirst(); if (firstArg.isPresent()) { @@ -283,7 +283,7 @@ private void mintCoinsForRewardToValidators() { */ private Object[] addExtraActualsForFromContract() { int al = deserializedActuals.length; - Object[] result = new Object[al + 2]; + var result = new Object[al + 2]; System.arraycopy(deserializedActuals, 0, result, 0, al); result[al] = getDeserializedCaller(); result[al + 1] = null; // Dummy is not used diff --git a/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintNodeImpl.java b/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintNodeImpl.java index 574be76a4..2809313e3 100644 --- a/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintNodeImpl.java +++ b/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintNodeImpl.java @@ -509,7 +509,7 @@ private void initWorkingDirectoryOfTendermintProcess(TendermintNodeConfig config } @Override - public StoreTransaction getTransaction() { + public StoreTransaction getStoreTransaction() { return transaction; } } \ No newline at end of file diff --git a/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintStoreTransaction.java b/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintStoreTransaction.java index dbaf91250..792c79a86 100644 --- a/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintStoreTransaction.java +++ b/io-hotmoka-node-tendermint/src/main/java/io/hotmoka/node/tendermint/internal/TendermintStoreTransaction.java @@ -33,4 +33,10 @@ protected void setError(TransactionReference reference, String error) throws Sto public long getNow() { return now; } + + @Override + public boolean isJustStore() { + // TODO Auto-generated method stub + return false; + } } \ No newline at end of file diff --git a/io-hotmoka-stores/src/main/java/io/hotmoka/stores/AbstractStoreTransaction.java b/io-hotmoka-stores/src/main/java/io/hotmoka/stores/AbstractStoreTransaction.java index bfd2d5937..716e91c67 100644 --- a/io-hotmoka-stores/src/main/java/io/hotmoka/stores/AbstractStoreTransaction.java +++ b/io-hotmoka-stores/src/main/java/io/hotmoka/stores/AbstractStoreTransaction.java @@ -16,23 +16,33 @@ package io.hotmoka.stores; +import java.math.BigInteger; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; +import io.hotmoka.exceptions.UncheckFunction; +import io.hotmoka.node.FieldSignatures; import io.hotmoka.node.api.requests.InitializationTransactionRequest; import io.hotmoka.node.api.requests.TransactionRequest; import io.hotmoka.node.api.responses.GameteCreationTransactionResponse; import io.hotmoka.node.api.responses.InitializationTransactionResponse; import io.hotmoka.node.api.responses.TransactionResponse; import io.hotmoka.node.api.responses.TransactionResponseWithUpdates; +import io.hotmoka.node.api.signatures.FieldSignature; import io.hotmoka.node.api.transactions.TransactionReference; +import io.hotmoka.node.api.updates.ClassTag; import io.hotmoka.node.api.updates.Update; +import io.hotmoka.node.api.updates.UpdateOfField; +import io.hotmoka.node.api.values.BigIntegerValue; import io.hotmoka.node.api.values.StorageReference; +import io.hotmoka.node.api.values.StringValue; /** * The store of a node. It keeps information about the state of the objects created @@ -44,51 +54,200 @@ public abstract class AbstractStoreTransaction> implements St private final static Logger LOGGER = Logger.getLogger(AbstractStoreTransaction.class.getName()); private final T store; + /** + * This lock ensures that the various components of this transaction are always aligned. + * For instance, histories refer to responses in store. Subclasses must take care + * of synchronizing on this object when they read components of this transaction, so that + * they are provided in a consistent state. + */ + private final Object lock = new Object(); + protected AbstractStoreTransaction(T store) { this.store = store; } - protected final T getStore() { + public final T getStore() { return store; } + protected final Object getLock() { + return lock; + } + @Override public final void push(TransactionReference reference, TransactionRequest request, TransactionResponse response) throws StoreException { - if (response instanceof TransactionResponseWithUpdates trwu) { - setRequest(reference, request); - setResponse(reference, response); - expandHistory(reference, trwu); + synchronized (lock) { + if (response instanceof TransactionResponseWithUpdates trwu) { + setRequest(reference, request); + setResponse(reference, response); + expandHistory(reference, trwu); - if (response instanceof GameteCreationTransactionResponse gctr) - LOGGER.info(gctr.getGamete() + ": created as gamete"); - } - else if (response instanceof InitializationTransactionResponse) { - if (request instanceof InitializationTransactionRequest itr) { + if (response instanceof GameteCreationTransactionResponse gctr) + LOGGER.info(gctr.getGamete() + ": created as gamete"); + } + else if (response instanceof InitializationTransactionResponse) { + if (request instanceof InitializationTransactionRequest itr) { + setRequest(reference, request); + setResponse(reference, response); + StorageReference manifest = itr.getManifest(); + setManifest(manifest); + LOGGER.info(manifest + ": set as manifest"); + LOGGER.info("the node has been initialized"); + } + else + throw new StoreException("Trying to initialize the node with a request of class " + request.getClass().getSimpleName()); + } + else { setRequest(reference, request); setResponse(reference, response); - StorageReference manifest = itr.getManifest(); - setManifest(manifest); - LOGGER.info(manifest + ": set as manifest"); - LOGGER.info("the node has been initialized"); } - else - throw new StoreException("Trying to initialize the node with a request of class " + request.getClass().getSimpleName()); } - else { + } + + @Override + public final void push(TransactionReference reference, TransactionRequest request, String errorMessage) throws StoreException { + synchronized (lock) { setRequest(reference, request); + setError(reference, errorMessage); + } + } + + @Override + public final void replace(TransactionReference reference, TransactionRequest request, TransactionResponse response) throws StoreException { + synchronized (lock) { setResponse(reference, response); } } @Override - public final void push(TransactionReference reference, TransactionRequest request, String errorMessage) throws StoreException { - setRequest(reference, request); - setError(reference, errorMessage); + public final Optional getTakamakaCodeUncommitted() throws StoreException { + synchronized (lock) { + return getManifestUncommitted() + .map(this::getClassTagUncommitted) + .map(ClassTag::getJar); + } } @Override - public final void replace(TransactionReference reference, TransactionRequest request, TransactionResponse response) throws StoreException { - setResponse(reference, response); + public final boolean nodeIsInitializedUncommitted() throws StoreException { + return getManifestUncommitted().isPresent(); + } + + @Override + public final Optional getGasStationUncommitted() throws StoreException { + synchronized (lock) { + return getManifestUncommitted().map(_manifest -> getReferenceFieldUncommitted(_manifest, FieldSignatures.MANIFEST_GAS_STATION_FIELD)); + } + } + + @Override + public final Optional getValidatorsUncommitted() throws StoreException { + synchronized (lock) { + return getManifestUncommitted().map(_manifest -> getReferenceFieldUncommitted(_manifest, FieldSignatures.MANIFEST_VALIDATORS_FIELD)); + } + } + + @Override + public final Optional getGameteUncommitted() throws StoreException { + synchronized (lock) { + return getManifestUncommitted().map(_manifest -> getReferenceFieldUncommitted(_manifest, FieldSignatures.MANIFEST_GAMETE_FIELD)); + } + } + + @Override + public final Optional getVersionsUncommitted() throws StoreException { + synchronized (lock) { + return getManifestUncommitted().map(_manifest -> getReferenceFieldUncommitted(_manifest, FieldSignatures.MANIFEST_VERSIONS_FIELD)); + } + } + + @Override + public final BigInteger getBalanceUncommitted(StorageReference contract) { + return getBigIntegerFieldUncommitted(contract, FieldSignatures.BALANCE_FIELD); + } + + @Override + public final BigInteger getRedBalanceUncommitted(StorageReference contract) { + return getBigIntegerFieldUncommitted(contract, FieldSignatures.RED_BALANCE_FIELD); + } + + @Override + public final BigInteger getCurrentSupplyUncommitted(StorageReference validators) { + return getBigIntegerFieldUncommitted(validators, FieldSignatures.ABSTRACT_VALIDATORS_CURRENT_SUPPLY_FIELD); + } + + @Override + public final String getPublicKeyUncommitted(StorageReference account) { + return getStringFieldUncommitted(account, FieldSignatures.EOA_PUBLIC_KEY_FIELD); + } + + @Override + public final StorageReference getCreatorUncommitted(StorageReference event) { + return getReferenceFieldUncommitted(event, FieldSignatures.EVENT_CREATOR_FIELD); + } + + @Override + public final BigInteger getNonceUncommitted(StorageReference account) { + return getBigIntegerFieldUncommitted(account, FieldSignatures.EOA_NONCE_FIELD); + } + + @Override + public final BigInteger getTotalBalanceUncommitted(StorageReference contract) { + return getBalanceUncommitted(contract).add(getRedBalanceUncommitted(contract)); + } + + @Override + public final String getClassNameUncommitted(StorageReference reference) { + return getClassTagUncommitted(reference).getClazz().getName(); + } + + @Override + public final ClassTag getClassTagUncommitted(StorageReference reference) throws NoSuchElementException { + // we go straight to the transaction that created the object + try { + synchronized (lock) { + return getResponseUncommitted(reference.getTransaction()) // TODO: cache it? + .filter(response -> response instanceof TransactionResponseWithUpdates) + .flatMap(response -> ((TransactionResponseWithUpdates) response).getUpdates() + .filter(update -> update instanceof ClassTag && update.getObject().equals(reference)) + .map(update -> (ClassTag) update) + .findFirst()) + .orElseThrow(() -> new NoSuchElementException("Object " + reference + " does not exist")); + } + } + catch (StoreException e) { + throw new RuntimeException(e); // TODO + } + } + + @Override + public final Stream getEagerFieldsUncommitted(StorageReference object) throws StoreException { + var fieldsAlreadySeen = new HashSet(); + + synchronized (lock) { + return getHistoryUncommitted(object) + .flatMap(UncheckFunction.uncheck(transaction -> enforceHasUpdates(getResponseUncommitted(transaction).get()).getUpdates())) // TODO: cache it? recheck + .filter(update -> update.isEager() && update instanceof UpdateOfField && update.getObject().equals(object) && + fieldsAlreadySeen.add(((UpdateOfField) update).getField())) + .map(update -> (UpdateOfField) update); + } + } + + @Override + public final Optional getLastUpdateToFieldUncommitted(StorageReference object, FieldSignature field) throws StoreException { + synchronized (lock) { + return getHistoryUncommitted(object) + .map(transaction -> getLastUpdateUncommitted(object, field, transaction)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + } + } + + @Override + public final Optional getLastUpdateToFinalFieldUncommitted(StorageReference object, FieldSignature field) { + // accesses directly the transaction that created the object + return getLastUpdateUncommitted(object, field, object.getTransaction()); } /** @@ -139,6 +298,68 @@ public final void replace(TransactionReference reference, TransactionRequest */ protected abstract void setManifest(StorageReference manifest) throws StoreException; + private static TransactionResponseWithUpdates enforceHasUpdates(TransactionResponse response) { // TODO: remove? + if (response instanceof TransactionResponseWithUpdates trwu) + return trwu; + else + throw new RuntimeException("Transaction " + response + " does not contain updates"); + } + + private StorageReference getReferenceFieldUncommitted(StorageReference object, FieldSignature field) { + try { + return (StorageReference) getLastUpdateToFieldUncommitted(object, field).get().getValue(); + } + catch (StoreException e) { + throw new RuntimeException(e); // TODO + } + } + + private BigInteger getBigIntegerFieldUncommitted(StorageReference object, FieldSignature field) { + try { + return ((BigIntegerValue) getLastUpdateToFieldUncommitted(object, field).get().getValue()).getValue(); + } + catch (StoreException e) { + throw new RuntimeException(e); // TODO + } + } + + private String getStringFieldUncommitted(StorageReference object, FieldSignature field) { + try { + return ((StringValue) getLastUpdateToFieldUncommitted(object, field).get().getValue()).getValue(); + } + catch (StoreException e) { + throw new RuntimeException(e); // TODO + } + } + + /** + * Yields the update to the given field of the object at the given reference, generated during a given transaction. + * + * @param object the reference of the object + * @param field the field of the object + * @param transaction the reference to the transaction + * @return the update, if any. If the field of {@code object} was not modified during + * the {@code transaction}, this method returns an empty optional + */ + private Optional getLastUpdateUncommitted(StorageReference object, FieldSignature field, TransactionReference transaction) { + try { + TransactionResponse response = getResponseUncommitted(transaction) + .orElseThrow(() -> new StoreException("Unknown transaction reference " + transaction)); + + if (response instanceof TransactionResponseWithUpdates trwu) + return trwu.getUpdates() + .filter(update -> update instanceof UpdateOfField) + .map(update -> (UpdateOfField) update) + .filter(update -> update.getObject().equals(object) && update.getField().equals(field)) + .findFirst(); + else + throw new StoreException("Transaction reference " + transaction + " does not contain updates"); + } + catch (StoreException e) { + throw new RuntimeException(e); // TODO + } + } + /** * Process the updates contained in the given response, expanding the history of the affected objects. * diff --git a/io-hotmoka-stores/src/main/java/io/hotmoka/stores/StoreTransaction.java b/io-hotmoka-stores/src/main/java/io/hotmoka/stores/StoreTransaction.java index f46eeeaf4..1289f6e51 100644 --- a/io-hotmoka-stores/src/main/java/io/hotmoka/stores/StoreTransaction.java +++ b/io-hotmoka-stores/src/main/java/io/hotmoka/stores/StoreTransaction.java @@ -16,13 +16,18 @@ package io.hotmoka.stores; +import java.math.BigInteger; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.stream.Stream; import io.hotmoka.annotations.ThreadSafe; import io.hotmoka.node.api.requests.TransactionRequest; import io.hotmoka.node.api.responses.TransactionResponse; +import io.hotmoka.node.api.signatures.FieldSignature; import io.hotmoka.node.api.transactions.TransactionReference; +import io.hotmoka.node.api.updates.ClassTag; +import io.hotmoka.node.api.updates.UpdateOfField; import io.hotmoka.node.api.values.StorageReference; /** @@ -34,13 +39,15 @@ @ThreadSafe public interface StoreTransaction> { + Store getStore(); /** - * Yields the time to use as now for the executions performed inside this transaction. + * Yields the time to use as current time for the requests executed performed inside this transaction. * * @return the time, in milliseconds from the UNIX epoch time */ long getNow(); + boolean isJustStore(); /** * Yields the response of the transaction having the given reference. * This considers also updates inside this transaction, that have not yet been committed. @@ -70,6 +77,42 @@ public interface StoreTransaction> { */ Optional getManifestUncommitted() throws StoreException; + Optional getTakamakaCodeUncommitted() throws StoreException; + + boolean nodeIsInitializedUncommitted() throws StoreException; + + Optional getGasStationUncommitted() throws StoreException; + + Optional getValidatorsUncommitted() throws StoreException; + + Optional getGameteUncommitted() throws StoreException; + + Optional getVersionsUncommitted() throws StoreException; + + BigInteger getBalanceUncommitted(StorageReference contract); + + BigInteger getRedBalanceUncommitted(StorageReference contract); + + BigInteger getCurrentSupplyUncommitted(StorageReference validators); + + String getPublicKeyUncommitted(StorageReference account); + + StorageReference getCreatorUncommitted(StorageReference event); + + BigInteger getNonceUncommitted(StorageReference account); + + BigInteger getTotalBalanceUncommitted(StorageReference contract); + + String getClassNameUncommitted(StorageReference reference); + + ClassTag getClassTagUncommitted(StorageReference reference) throws NoSuchElementException; + + Stream getEagerFieldsUncommitted(StorageReference object) throws StoreException; + + Optional getLastUpdateToFieldUncommitted(StorageReference object, FieldSignature field) throws StoreException; + + Optional getLastUpdateToFinalFieldUncommitted(StorageReference object, FieldSignature field); + /** * Pushes the result of executing a successful Hotmoka request. * This method assumes that the given request was not already present in the store.