From 205b2021f1be51669646398cd3213627eb1b9b9a Mon Sep 17 00:00:00 2001 From: Deyan Dimitrov Date: Tue, 13 Feb 2024 21:42:58 +0200 Subject: [PATCH] feat: scheduled txs mono signed state dumper (#11390) Signed-off-by: dikel --- .../DumpScheduledTransactionsSubcommand.java | 265 ++++++++++++++++++ .../cli/signedstate/DumpStateCommand.java | 21 ++ .../cli/signedstate/DumpTokensSubcommand.java | 51 +--- .../cli/signedstate/DumpTopicsSubcommand.java | 7 +- .../DumpUniqueTokensSubcommand.java | 24 +- .../cli/signedstate/SignedStateHolder.java | 9 + .../services/cli/utils/FieldBuilder.java | 41 +++ .../hedera/services/cli/utils/Formatters.java | 49 ++++ .../schedule/ScheduleVirtualValue.java | 4 + .../src/main/java/module-info.java | 1 + 10 files changed, 397 insertions(+), 75 deletions(-) create mode 100644 hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpScheduledTransactionsSubcommand.java create mode 100644 hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/FieldBuilder.java create mode 100644 hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/Formatters.java diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpScheduledTransactionsSubcommand.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpScheduledTransactionsSubcommand.java new file mode 100644 index 000000000000..80b1ea2dde1a --- /dev/null +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpScheduledTransactionsSubcommand.java @@ -0,0 +1,265 @@ +/* + * 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.services.cli.signedstate; + +import static com.hedera.services.cli.utils.Formatters.getListFormatter; +import static com.hedera.services.cli.utils.Formatters.getNullableFormatter; +import static com.hedera.services.cli.utils.Formatters.getOptionalFormatter; +import static com.hedera.services.cli.utils.ThingsToStrings.quoteForCsv; +import static java.util.Objects.requireNonNull; + +import com.google.protobuf.ByteString; +import com.hedera.node.app.service.mono.legacy.core.jproto.JKey; +import com.hedera.node.app.service.mono.state.adapters.MerkleMapLike; +import com.hedera.node.app.service.mono.state.submerkle.EntityId; +import com.hedera.node.app.service.mono.state.submerkle.RichInstant; +import com.hedera.node.app.service.mono.state.virtual.EntityNumVirtualKey; +import com.hedera.node.app.service.mono.state.virtual.schedule.ScheduleVirtualValue; +import com.hedera.services.cli.utils.FieldBuilder; +import com.hedera.services.cli.utils.ThingsToStrings; +import com.hedera.services.cli.utils.Writer; +import com.hederahashgraph.api.proto.java.Key; +import com.hederahashgraph.api.proto.java.SchedulableTransactionBody; +import com.hederahashgraph.api.proto.java.TransactionBody; +import com.swirlds.base.utility.Pair; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** Dump all scheduled transactions from a signed state file to a text file in a deterministic order */ +public class DumpScheduledTransactionsSubcommand { + static void doit( + @NonNull final SignedStateHolder state, + @NonNull final Path scheduledTxsPath, + @NonNull final DumpStateCommand.EmitSummary emitSummary, + @NonNull final SignedStateCommand.Verbosity verbosity) { + new DumpScheduledTransactionsSubcommand(state, scheduledTxsPath, emitSummary, verbosity).doit(); + } + + @NonNull + final SignedStateHolder state; + + @NonNull + final Path scheduledTxsPath; + + @NonNull + final DumpStateCommand.EmitSummary emitSummary; + + @NonNull + final SignedStateCommand.Verbosity verbosity; + + DumpScheduledTransactionsSubcommand( + @NonNull final SignedStateHolder state, + @NonNull final Path scheduledTxsPath, + @NonNull final DumpStateCommand.EmitSummary emitSummary, + @NonNull final SignedStateCommand.Verbosity verbosity) { + requireNonNull(state, "state"); + requireNonNull(scheduledTxsPath, "signedTxsPath"); + requireNonNull(emitSummary, "emitSummary"); + requireNonNull(verbosity, "verbosity"); + + this.state = state; + this.scheduledTxsPath = scheduledTxsPath; + this.emitSummary = emitSummary; + this.verbosity = verbosity; + } + + void doit() { + final var scheduledTransactions = state.getScheduledTransactions().byId(); + System.out.printf("=== %d scheduled transactions ===%n", scheduledTransactions.size()); + + final var allScheduledTxs = gatherScheduledTransactions(scheduledTransactions); + + int reportSize; + try (@NonNull final var writer = new Writer(scheduledTxsPath)) { + if (emitSummary == DumpStateCommand.EmitSummary.YES) reportSummary(writer, allScheduledTxs); + reportOnScheduledTransactions(writer, allScheduledTxs); + reportSize = writer.getSize(); + } + + System.out.printf("=== scheduled transactions report is %d bytes%n", reportSize); + } + + @SuppressWarnings( + "java:S6218") // "Equals/hashcode method should be overridden in records containing array fields" - this + record ScheduledTransaction( + long number, + @Nullable Key grpcAdminKey, + @NonNull Optional adminKey, + @Nullable String memo, + boolean deleted, + boolean executed, + boolean calculatedWaitForExpiry, + boolean waitForExpiryProvided, + @Nullable EntityId payer, + @NonNull EntityId schedulingAccount, + @NonNull RichInstant schedulingTXValidStart, + @Nullable RichInstant expirationTimeProvided, + @Nullable RichInstant calculatedExpirationTime, + @Nullable RichInstant resolutionTime, + byte[] bodyBytes, + @NonNull TransactionBody ordinaryScheduledTxn, + @NonNull SchedulableTransactionBody scheduledTxn, + @NonNull List signatories, + @NonNull List notary) { + + ScheduledTransaction(@NonNull final ScheduleVirtualValue scheduledTx) { + this( + scheduledTx.getKey().getKeyAsLong(), + scheduledTx.grpcAdminKey(), + scheduledTx.adminKey(), + scheduledTx.memo().orElse(""), + scheduledTx.isDeleted(), + scheduledTx.isExecuted(), + scheduledTx.calculatedWaitForExpiry(), + scheduledTx.waitForExpiryProvided(), + scheduledTx.payer(), + scheduledTx.schedulingAccount(), + scheduledTx.schedulingTXValidStart(), + scheduledTx.expirationTimeProvided(), + scheduledTx.calculatedExpirationTime(), + scheduledTx.getResolutionTime(), + scheduledTx.bodyBytes(), + scheduledTx.ordinaryViewOfScheduledTxn(), + scheduledTx.scheduledTxn(), + scheduledTx.signatories(), + scheduledTx.notary().stream().map(ByteString::toByteArray).toList()); + Objects.requireNonNull(adminKey, "adminKey"); + Objects.requireNonNull(schedulingAccount, "schedulingAccount"); + Objects.requireNonNull(schedulingTXValidStart, "schedulingTXValidStart"); + Objects.requireNonNull(ordinaryScheduledTxn, "ordinaryScheduledTxn"); + Objects.requireNonNull(scheduledTxn, "scheduledTxn"); + Objects.requireNonNull(signatories, "signatories"); + Objects.requireNonNull(notary, "notary"); + } + } + + @NonNull + Map gatherScheduledTransactions( + @NonNull final MerkleMapLike scheduledTxsStore) { + final var allScheduledTransactions = new TreeMap(); + scheduledTxsStore.forEachNode( + (en, mt) -> allScheduledTransactions.put(en.getKeyAsLong(), new ScheduledTransaction(mt))); + return allScheduledTransactions; + } + + void reportSummary(@NonNull Writer writer, @NonNull Map scheduledTxs) { + writer.writeln("=== %7d: scheduled transactions".formatted(scheduledTxs.size())); + writer.writeln(""); + } + + void reportOnScheduledTransactions(@NonNull Writer writer, @NonNull Map scheduledTxs) { + writer.writeln(formatHeader()); + scheduledTxs.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> formatScheduledTransaction(writer, e.getValue())); + writer.writeln(""); + } + + void formatScheduledTransaction(@NonNull final Writer writer, @NonNull final ScheduledTransaction scheduledTx) { + final var fb = new FieldBuilder(FIELD_SEPARATOR); + fieldFormatters.stream().map(Pair::right).forEach(ff -> ff.accept(fb, scheduledTx)); + writer.writeln(fb); + } + + @NonNull + String formatHeader() { + return fieldFormatters.stream().map(Pair::left).collect(Collectors.joining(FIELD_SEPARATOR)); + } + + static final String FIELD_SEPARATOR = ";"; + static final String SUBFIELD_SEPARATOR = ","; + static Function booleanFormatter = b -> b ? "T" : ""; + static Function csvQuote = s -> quoteForCsv(FIELD_SEPARATOR, (s == null) ? "" : s.toString()); + + @NonNull + static List>> fieldFormatters = List.of( + Pair.of("number", getFieldFormatter(ScheduledTransaction::number, Object::toString)), + Pair.of("grpcAdminKey", getFieldFormatter(ScheduledTransaction::grpcAdminKey, csvQuote)), + Pair.of( + "adminKey", + getFieldFormatter( + ScheduledTransaction::adminKey, getOptionalFormatter(ThingsToStrings::toStringOfJKey))), + Pair.of("memo", getFieldFormatter(ScheduledTransaction::memo, csvQuote)), + Pair.of("isDeleted", getFieldFormatter(ScheduledTransaction::deleted, booleanFormatter)), + Pair.of("isExecuted", getFieldFormatter(ScheduledTransaction::executed, booleanFormatter)), + Pair.of( + "calculatedWaitForExpiry", + getFieldFormatter(ScheduledTransaction::calculatedWaitForExpiry, booleanFormatter)), + Pair.of( + "waitForExpiryProvided", + getFieldFormatter(ScheduledTransaction::waitForExpiryProvided, booleanFormatter)), + Pair.of("payer", getFieldFormatter(ScheduledTransaction::payer, ThingsToStrings::toStringOfEntityId)), + Pair.of( + "schedulingAccount", + getFieldFormatter(ScheduledTransaction::schedulingAccount, ThingsToStrings::toStringOfEntityId)), + Pair.of( + "schedulingTXValidStart", + getFieldFormatter( + ScheduledTransaction::schedulingTXValidStart, ThingsToStrings::toStringOfRichInstant)), + Pair.of( + "expirationTimeProvided", + getFieldFormatter( + ScheduledTransaction::expirationTimeProvided, + getNullableFormatter(ThingsToStrings::toStringOfRichInstant))), + Pair.of( + "calculatedExpirationTime", + getFieldFormatter( + ScheduledTransaction::calculatedExpirationTime, + getNullableFormatter(ThingsToStrings::toStringOfRichInstant))), + Pair.of( + "resolutionTime", + getFieldFormatter( + ScheduledTransaction::resolutionTime, + getNullableFormatter(ThingsToStrings::toStringOfRichInstant))), + Pair.of( + "bodyBytes", + getFieldFormatter(ScheduledTransaction::bodyBytes, ThingsToStrings::toStringOfByteArray)), + Pair.of("ordinaryScheduledTxn", getFieldFormatter(ScheduledTransaction::ordinaryScheduledTxn, csvQuote)), + Pair.of("scheduledTxn", getFieldFormatter(ScheduledTransaction::scheduledTxn, csvQuote)), + Pair.of( + "signatories", + getFieldFormatter( + ScheduledTransaction::signatories, + getListFormatter(ThingsToStrings::toStringOfByteArray, SUBFIELD_SEPARATOR))), + Pair.of( + "notary", + getFieldFormatter( + ScheduledTransaction::notary, + getListFormatter(ThingsToStrings::toStringOfByteArray, SUBFIELD_SEPARATOR)))); + + static BiConsumer getFieldFormatter( + @NonNull final Function fun, @NonNull final Function formatter) { + return (fb, t) -> formatField(fb, t, fun, formatter); + } + + static void formatField( + @NonNull final FieldBuilder fb, + @NonNull final ScheduledTransaction scheduledTransaction, + @NonNull final Function fun, + @NonNull final Function formatter) { + fb.append(formatter.apply(fun.apply(scheduledTransaction))); + } +} diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpStateCommand.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpStateCommand.java index ad6253aafed9..769d49ee3f87 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpStateCommand.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpStateCommand.java @@ -355,6 +355,27 @@ void topics( finish(); } + @Command(name = "scheduled-transactions", description = "Dump scheduled transactions") + void scheduledTransactions( + @Option( + names = {"--scheduled-transaction"}, + required = true, + arity = "1", + description = "Output file for scheduled transactions dump") + @NonNull + final Path scheduledTxsPath, + @Option( + names = {"-s", "--summary"}, + description = "Emit summary information") + final boolean emitSummary) { + Objects.requireNonNull(scheduledTxsPath); + init(); + System.out.println("=== scheduled transactions ==="); + DumpScheduledTransactionsSubcommand.doit( + parent.signedState, scheduledTxsPath, emitSummary ? EmitSummary.YES : EmitSummary.NO, parent.verbosity); + finish(); + } + /** Setup to run a dump subcommand: If _first_ subcommand being run then open signed state file */ void init() { if (thisSubcommandsNumber == null) { diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTokensSubcommand.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTokensSubcommand.java index 6fae43d87651..39aa4589fa0c 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTokensSubcommand.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTokensSubcommand.java @@ -16,6 +16,9 @@ package com.hedera.services.cli.signedstate; +import static com.hedera.services.cli.utils.Formatters.getListFormatter; +import static com.hedera.services.cli.utils.Formatters.getNullableFormatter; +import static com.hedera.services.cli.utils.Formatters.getOptionalFormatter; import static com.hedera.services.cli.utils.ThingsToStrings.quoteForCsv; import static com.hedera.services.cli.utils.ThingsToStrings.toStructureSummaryOfJKey; @@ -31,6 +34,7 @@ import com.hedera.services.cli.signedstate.DumpStateCommand.KeyDetails; import com.hedera.services.cli.signedstate.DumpStateCommand.WithFeeSummary; import com.hedera.services.cli.signedstate.SignedStateCommand.Verbosity; +import com.hedera.services.cli.utils.FieldBuilder; import com.hedera.services.cli.utils.ThingsToStrings; import com.hedera.services.cli.utils.Writer; import com.swirlds.base.utility.Pair; @@ -281,28 +285,6 @@ void reportSummary(@NonNull Writer writer, @NonNull EnumMap fieldSeparator.length()) sb.setLength(sb.length() - fieldSeparator.length()); - return sb.toString(); - } - } - static Function booleanFormatter = b -> b ? "T" : ""; static Function csvQuote = s -> quoteForCsv(FIELD_SEPARATOR, s); @@ -352,31 +334,6 @@ static void formatField( fb.append(formatter.apply(fun.apply(token))); } - static Function, String> getOptionalFormatter(@NonNull final Function formatter) { - return ot -> ot.isPresent() ? formatter.apply(ot.get()) : ""; - } - - static Function getNullableFormatter(@NonNull final Function formatter) { - return t -> null != t ? formatter.apply(t) : ""; - } - - static Function, String> getListFormatter( - @NonNull final Function formatter, @NonNull final String subfieldSeparator) { - return lt -> { - if (!lt.isEmpty()) { - final var sb = new StringBuilder(); - for (@NonNull final var e : lt) { - final var v = formatter.apply(e); - sb.append(v); - sb.append(subfieldSeparator); - } - // Remove last subfield separator - if (sb.length() >= subfieldSeparator.length()) sb.setLength(sb.length() - subfieldSeparator.length()); - return sb.toString(); - } else return ""; - }; - } - void formatToken(@NonNull final Writer writer, @NonNull final Token token) { final var fb = new FieldBuilder(FIELD_SEPARATOR); fieldFormatters.stream().map(Pair::right).forEach(ff -> ff.accept(fb, token)); diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTopicsSubcommand.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTopicsSubcommand.java index 555911647e8a..a41ffa53b0e1 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTopicsSubcommand.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpTopicsSubcommand.java @@ -16,6 +16,7 @@ package com.hedera.services.cli.signedstate; +import static com.hedera.services.cli.utils.Formatters.getNullableFormatter; import static com.hedera.services.cli.utils.ThingsToStrings.getMaybeStringifyByteString; import static com.hedera.services.cli.utils.ThingsToStrings.quoteForCsv; import static java.util.Objects.requireNonNull; @@ -27,8 +28,8 @@ import com.hedera.node.app.service.mono.state.submerkle.RichInstant; import com.hedera.node.app.service.mono.utils.EntityNum; import com.hedera.services.cli.signedstate.DumpStateCommand.EmitSummary; -import com.hedera.services.cli.signedstate.DumpTokensSubcommand.FieldBuilder; import com.hedera.services.cli.signedstate.SignedStateCommand.Verbosity; +import com.hedera.services.cli.utils.FieldBuilder; import com.hedera.services.cli.utils.ThingsToStrings; import com.hedera.services.cli.utils.Writer; import com.swirlds.base.utility.Pair; @@ -190,10 +191,6 @@ static BiConsumer getFieldFormatter( return (fb, t) -> formatField(fb, t, fun, formatter); } - static Function getNullableFormatter(@NonNull final Function formatter) { - return t -> null != t ? formatter.apply(t) : ""; - } - static void formatField( @NonNull final FieldBuilder fb, @NonNull final Topic topic, diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpUniqueTokensSubcommand.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpUniqueTokensSubcommand.java index 05442e1b4d7f..eb628c5d570c 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpUniqueTokensSubcommand.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/DumpUniqueTokensSubcommand.java @@ -30,6 +30,7 @@ import com.hedera.node.app.service.mono.utils.NftNumPair; import com.hedera.services.cli.signedstate.DumpStateCommand.EmitSummary; import com.hedera.services.cli.signedstate.SignedStateCommand.Verbosity; +import com.hedera.services.cli.utils.FieldBuilder; import com.hedera.services.cli.utils.ThingsToStrings; import com.hedera.services.cli.utils.Writer; import com.swirlds.base.utility.Pair; @@ -182,29 +183,6 @@ static RelatedEntities countRelatedEntities(@NonNull final Map fieldSeparator.length()) sb.setLength(sb.length() - fieldSeparator.length()); - return sb.toString(); - } - } - void reportOnUniques(@NonNull final Writer writer, @NonNull final Map uniques) { writer.writeln(formatHeader()); uniques.entrySet().stream() diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java index d840f4328cd2..05188838b408 100644 --- a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/signedstate/SignedStateHolder.java @@ -19,6 +19,7 @@ import com.hedera.node.app.service.mono.ServicesState; import com.hedera.node.app.service.mono.state.adapters.MerkleMapLike; import com.hedera.node.app.service.mono.state.adapters.VirtualMapLike; +import com.hedera.node.app.service.mono.state.merkle.MerkleScheduledTransactions; import com.hedera.node.app.service.mono.state.merkle.MerkleSpecialFiles; import com.hedera.node.app.service.mono.state.merkle.MerkleToken; import com.hedera.node.app.service.mono.state.merkle.MerkleTopic; @@ -305,6 +306,14 @@ public VirtualMapLike getRawContractStorage( return rawContractStorage; } + /** Get all scheduled transactions */ + @NonNull + public MerkleScheduledTransactions getScheduledTransactions() { + final var scheduledTransactions = servicesState.scheduleTxs(); + assertSignedStateComponentExists(scheduledTransactions, "scheduledTransactions"); + return scheduledTransactions; + } + /** Deserialize the signed state file into an in-memory data structure. */ @NonNull private Pair dehydrate(@NonNull final List configurationPaths) { diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/FieldBuilder.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/FieldBuilder.java new file mode 100644 index 000000000000..5ec74fee0367 --- /dev/null +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/FieldBuilder.java @@ -0,0 +1,41 @@ +/* + * 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.services.cli.utils; + +import edu.umd.cs.findbugs.annotations.NonNull; + +public class FieldBuilder { + final StringBuilder sb; + final String fieldSeparator; + + public FieldBuilder(@NonNull final String fieldSeparator) { + this.sb = new StringBuilder(); + this.fieldSeparator = fieldSeparator; + } + + public void append(@NonNull final String v) { + sb.append(v); + sb.append(fieldSeparator); + } + + @Override + @NonNull + public String toString() { + if (sb.length() > fieldSeparator.length()) sb.setLength(sb.length() - fieldSeparator.length()); + return sb.toString(); + } +} diff --git a/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/Formatters.java b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/Formatters.java new file mode 100644 index 000000000000..26fef34dcc48 --- /dev/null +++ b/hedera-node/cli-clients/src/main/java/com/hedera/services/cli/utils/Formatters.java @@ -0,0 +1,49 @@ +/* + * 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.services.cli.utils; + +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +public class Formatters { + public static Function, String> getListFormatter( + @NonNull final Function formatter, @NonNull final String subfieldSeparator) { + return lt -> { + if (!lt.isEmpty()) { + final var sb = new StringBuilder(); + for (@NonNull final var e : lt) { + final var v = formatter.apply(e); + sb.append(v); + sb.append(subfieldSeparator); + } + // Remove last subfield separator + if (sb.length() >= subfieldSeparator.length()) sb.setLength(sb.length() - subfieldSeparator.length()); + return sb.toString(); + } else return ""; + }; + } + + public static Function, String> getOptionalFormatter(@NonNull final Function formatter) { + return ot -> ot.isPresent() ? formatter.apply(ot.get()) : ""; + } + + public static Function getNullableFormatter(@NonNull final Function formatter) { + return t -> null != t ? formatter.apply(t) : ""; + } +} diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleVirtualValue.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleVirtualValue.java index 680df8873f9c..889017273601 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleVirtualValue.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/state/virtual/schedule/ScheduleVirtualValue.java @@ -499,6 +499,10 @@ public RichInstant calculatedExpirationTime() { return calculatedExpirationTime; } + public Set notary() { + return notary; + } + public void setCalculatedExpirationTime(final RichInstant calculatedExpirationTime) { throwIfImmutable("Cannot change this schedule's payer if it's immutable."); this.calculatedExpirationTime = calculatedExpirationTime; diff --git a/hedera-node/hedera-mono-service/src/main/java/module-info.java b/hedera-node/hedera-mono-service/src/main/java/module-info.java index eb560a409744..73e046de80c9 100644 --- a/hedera-node/hedera-mono-service/src/main/java/module-info.java +++ b/hedera-node/hedera-mono-service/src/main/java/module-info.java @@ -62,6 +62,7 @@ com.hedera.node.app, com.hedera.node.app.service.file.impl; exports com.hedera.node.app.service.mono.state.virtual.schedule to + com.hedera.node.services.cli, com.hedera.node.app.service.mono.test.fixtures, com.hedera.node.app.service.schedule.impl, com.hedera.node.app;