Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring: Scheduled Sign #517

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package com.hedera.services.store.models;


/*-
/*
* -
* ‌
* Hedera Services Node
* ​
* Copyright (C) 2018 - 2021 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
*
* 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.
* ‍
*/

import com.google.protobuf.ByteString;
Expand Down Expand Up @@ -55,10 +50,10 @@
import static com.hedera.services.utils.MiscUtils.asTimestamp;

/**
* Used to capture the state of an {@link com.hedera.services.state.merkle.MerkleSchedule},
* Used to capture the state of an {@link com.hedera.services.state.merkle.MerkleSchedule},
* and mutate it without touching the actual state. Changes done to this model should be persisted in order
* to modify the state.
*
*
* @author Yoan Sredkov
*/
public class Schedule {
Expand Down Expand Up @@ -117,11 +112,11 @@ public void fromCreateOP(Id scheduleId, RichInstant consensusTime, long schedule

/**
* Executes the schedule.
*
*
* @param transactionContext - the context which triggers the scheduled transaction
* @param fallback - a {@link Runnable}, used for destroying
* the schedule if it's the same as the one pending creation.
*
* the schedule if it's the same as the one pending creation.
*
* @return - execution status of the schedule
*/
public ResponseCodeEnum execute(TransactionContext transactionContext, Runnable fallback) {
Expand All @@ -133,13 +128,14 @@ public ResponseCodeEnum execute(TransactionContext transactionContext, Runnable
validateFalse(id == Id.DEFAULT, ResponseCodeEnum.INVALID_SCHEDULE_ID);
validateFalse(isExecuted(), ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED);
validateFalse(isDeleted(), ResponseCodeEnum.SCHEDULE_ALREADY_DELETED);
resolutionTime = RichInstant.fromJava(transactionContext.consensusTime().plusNanos(1L));
executed = true;

final var txn = asSignedTxn();
transactionContext.trigger(new TriggeredTxnAccessor(
txn.toByteArray(),
effectivePayer().toGrpcAccountId(),
id.asGrpcScheduleId()));
resolutionTime = RichInstant.fromJava(transactionContext.consensusTime().plusNanos(1L));
executed = true;
} catch (InvalidProtocolBufferException ignore) {
fallback.run();
return ResponseCodeEnum.FAIL_INVALID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* ‍
*/

import com.hedera.services.records.TransactionRecordService;
import com.hedera.services.state.merkle.MerkleSchedule;
import com.hedera.services.state.submerkle.EntityId;
import com.hedera.services.store.models.Id;
Expand All @@ -40,11 +41,13 @@
import static com.hedera.services.store.schedule.HederaScheduleStore.NO_PENDING_ID;
import static com.hedera.services.utils.EntityIdUtils.readableId;
import static com.hedera.services.utils.EntityNum.fromScheduleId;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_DELETED;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED;

/**
* A store used for interacting with the merkle schedules.
* Encapsulates the logic for loading/persisting schedules from/to the state.
*
*
* @author Yoan Sredkov
*/
@Singleton
Expand All @@ -64,22 +67,24 @@ public ScheduleModelStore(
/**
* Loads the given schedule.
* Please note that the model is not persisted in state, and must be EXPLICITLY persisted when modified.
*
*
* @param scheduleId - the id to look for
* @return - a mutable model of a MerkleSchedule, obtained by the given ID
*/
public Schedule loadSchedule(Id scheduleId) {
final var id = EntityNum.fromScheduleId(scheduleId.asGrpcScheduleId());
final var model = new Schedule(scheduleId);
final var merkle = schedules.get().get(id);

validateUsable(merkle);

ScheduleConversion.merkleToModel(merkle, model);
return model;
}

/**
* Performs a lookup of the given byte array against the state schedules.
*
*
* @param bodyBytes - the byte array used for lookup. It usually represents a gRPC transaction.
* @return Pair of an optional id and a schedule (if found)
*/
Expand All @@ -105,7 +110,7 @@ public Pair<Optional<Id>, Schedule> lookupSchedule(byte[] bodyBytes) {

/**
* Locates and expires the given schedule
*
*
* @param entityId - an entityId to search with
*/
public void expire(EntityId entityId) {
Expand All @@ -121,13 +126,13 @@ public void expire(EntityId entityId) {
extantSchedulesDelegator.performRemoval(schedule);
schedules.get().remove(EntityNum.fromLong(entityId.num()));
}

/* used to replicate the legacy pending creation */

public void setPendingCreation(Pair<Optional<Id>, Schedule> creation) {
this.pendingCreation = creation;
}

public void flushPending() {
throwIfNoCreationPending();
var id = fromScheduleId(pendingCreation.getLeft().get().asGrpcScheduleId());
Expand Down Expand Up @@ -155,12 +160,26 @@ public void resetPendingCreation() {

/**
* Validates the usability of the given merkle schedule
*
*
* @param schedule - the schedule
*/
private void validateUsable(MerkleSchedule schedule) {
validateTrue(schedule != null, ResponseCodeEnum.INVALID_SCHEDULE_ID);
validateFalse(schedule.isDeleted(), ResponseCodeEnum.SCHEDULE_ALREADY_DELETED);
validateFalse(schedule.isExecuted(), ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED);
}

/**
* Persists the given schedule to the Swirlds state, inviting the injected {@link TransactionRecordService}
* to update the {@link com.hedera.services.state.submerkle.ExpirableTxnRecord} of the active transaction
* with these changes.
*
* @param schedule
* the schedule to save
*/
public void persistSchedule(Schedule schedule) {
final var key = EntityNum.fromLong(schedule.getId().getNum());
final var mutableSchedule = schedules.get().getForModify(key);
ScheduleConversion.modelToMerkle(schedule, mutableSchedule);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoTransfer;
import static com.hederahashgraph.api.proto.java.HederaFunctionality.ScheduleCreate;
import static com.hederahashgraph.api.proto.java.HederaFunctionality.ScheduleSign;
import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenAccountWipe;
import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenAssociateToAccount;
import static com.hederahashgraph.api.proto.java.HederaFunctionality.TokenBurn;
Expand Down Expand Up @@ -63,7 +64,8 @@ public class TransitionRunner {
TokenCreate,
TokenFeeScheduleUpdate,
CryptoTransfer,
TokenDelete
TokenDelete,
ScheduleSign
);
/* contains "special" refactored operations - ops which can be successful without an explicit SUCCESS status */
private static final EnumSet<HederaFunctionality> specialRefactoredOps = EnumSet.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@
* ‍
*/

import com.google.protobuf.InvalidProtocolBufferException;
import com.hedera.services.context.TransactionContext;
import com.hedera.services.keys.InHandleActivationHelper;
import com.hedera.services.store.models.Id;
import com.hedera.services.store.schedule.ScheduleModelStore;
import com.hedera.services.store.schedule.ScheduleStore;
import com.hedera.services.txns.TransitionLogic;
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
import com.hederahashgraph.api.proto.java.ScheduleSignTransactionBody;
import com.hederahashgraph.api.proto.java.SignatureMap;
import com.hederahashgraph.api.proto.java.TransactionBody;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -40,12 +37,9 @@
import java.util.function.Function;
import java.util.function.Predicate;

import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FAIL_INVALID;
import static com.hedera.services.exceptions.ValidationUtils.validateTrue;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_DELETED;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SCHEDULE_ALREADY_EXECUTED;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS;

@Singleton
public class ScheduleSignTransitionLogic implements TransitionLogic {
Expand All @@ -54,73 +48,44 @@ public class ScheduleSignTransitionLogic implements TransitionLogic {
private final Function<TransactionBody, ResponseCodeEnum> SEMANTIC_CHECK = this::validate;

private final InHandleActivationHelper activationHelper;

private final ScheduleExecutor executor;
private final ScheduleStore store;
private final ScheduleModelStore modelStore;
private final ScheduleModelStore store;
private final TransactionContext txnCtx;

SigMapScheduleClassifier classifier = new SigMapScheduleClassifier();
SignatoryUtils.ScheduledSigningsWitness replSigningsWitness = SignatoryUtils::witnessScoped;

@Inject
public ScheduleSignTransitionLogic(
ScheduleStore store,
ScheduleModelStore modelStore,
ScheduleModelStore store,
TransactionContext txnCtx,
InHandleActivationHelper activationHelper,
ScheduleExecutor executor
InHandleActivationHelper activationHelper
) {
this.store = store;
this.txnCtx = txnCtx;
this.executor = executor;
this.activationHelper = activationHelper;
this.modelStore = modelStore;
this.store = store;
}

@Override
public void doStateTransition() {
try {
var accessor = txnCtx.accessor();
transitionFor(accessor.getSigMap(), accessor.getTxn().getScheduleSign());
} catch (Exception e) {
log.warn("Unhandled error while processing :: {}!", txnCtx.accessor().getSignedTxnWrapper(), e);
txnCtx.setStatus(FAIL_INVALID);
}
}
var accessor = txnCtx.accessor();
var scheduleID = accessor.getTxn().getScheduleSign().getScheduleID();
var schedule = store.loadSchedule(Id.fromGrpcSchedule(scheduleID));

private void transitionFor(
SignatureMap sigMap,
ScheduleSignTransactionBody op
) throws InvalidProtocolBufferException {
var scheduleId = op.getScheduleID();
var origSchedule = store.get(scheduleId);
if (origSchedule.isExecuted()) {
txnCtx.setStatus(SCHEDULE_ALREADY_EXECUTED);
return;
}
if (origSchedule.isDeleted()) {
txnCtx.setStatus(SCHEDULE_ALREADY_DELETED);
return;
}

var schedule = modelStore.loadSchedule(Id.fromGrpcSchedule(scheduleId));
var validScheduleKeys = classifier.validScheduleKeys(
List.of(txnCtx.activePayerKey()),
sigMap,
accessor.getSigMap(),
activationHelper.currentSigsFn(),
activationHelper::visitScheduledCryptoSigs);

var signingOutcome = replSigningsWitness.observeInScope(schedule, validScheduleKeys, activationHelper);

var outcome = signingOutcome.getLeft();
if (outcome == OK) {
var updatedSchedule = store.get(scheduleId);
txnCtx.setScheduledTxnId(updatedSchedule.scheduledTransactionId());
if (signingOutcome.getRight()) {
outcome = executor.processExecution(scheduleId, store, txnCtx);
}
validateTrue(outcome == OK, outcome);

txnCtx.setScheduledTxnId(schedule.scheduledTransactionId());
var executionReady = signingOutcome.getRight();
if (executionReady) {
schedule.execute(txnCtx, store::resetPendingCreation);
store.persistSchedule(schedule);
}
txnCtx.setStatus(outcome == OK ? SUCCESS : outcome);
}

@Override
Expand All @@ -139,4 +104,3 @@ public ResponseCodeEnum validate(TransactionBody txnBody) {
return (op.hasScheduleID()) ? OK : INVALID_SCHEDULE_ID;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
* new Ed25519 keys witnessed to have signed the schedule.
*/
public class SignatoryUtils {
@FunctionalInterface
interface ScheduledSigningsWitness {
Pair<ResponseCodeEnum, Boolean> observeInScope(
Schedule schedule,
Optional<List<JKey>> validScheduleKeys,
InHandleActivationHelper activationHelper);
}

/**
* Attempts to update a stored {@code MerkleSchedule} with the Ed25519 keys
* witnessed to sign the schedule in the active transaction. The result of
Expand Down Expand Up @@ -109,12 +117,4 @@ private static boolean witnessAnyNew(Schedule schedule, List<byte[]> signatories

return witnessedNew.get();
}

@FunctionalInterface
interface ScheduledSigningsWitness {
Pair<ResponseCodeEnum, Boolean> observeInScope(
Schedule schedule,
Optional<List<JKey>> validScheduleKeys,
InHandleActivationHelper activationHelper);
}
}