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

Abort ScheduleSignTransitionLogic if schedule is resolved #1303

Merged
merged 14 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions hedera-node/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.intellij</groupId>
<artifactId>annotations</artifactId>
<version>12.0</version>
</dependency>
</dependencies>

<repositories>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
import com.hedera.services.txns.network.UncheckedSubmitTransitionLogic;
import com.hedera.services.txns.schedule.ScheduleCreateTransitionLogic;
import com.hedera.services.txns.schedule.ScheduleDeleteTransitionLogic;
import com.hedera.services.txns.schedule.ScheduleExecutor;
import com.hedera.services.txns.schedule.ScheduleSignTransitionLogic;
import com.hedera.services.txns.submission.PlatformSubmissionManager;
import com.hedera.services.txns.submission.TxnHandlerSubmissionFlow;
Expand Down Expand Up @@ -520,6 +521,7 @@ public class ServicesContext {
private TxnAwareSoliditySigsVerifier soliditySigsVerifier;
private ValidatingCallbackInterceptor apiPermissionsReloading;
private ValidatingCallbackInterceptor applicationPropertiesReloading;
private ScheduleExecutor scheduleExecutor;
private Supplier<ServicesRepositoryRoot> newPureRepo;
private Map<TransactionID, TxnIdRecentHistory> txnHistories;
private AtomicReference<FCMap<MerkleEntityId, MerkleTopic>> queryableTopics;
Expand Down Expand Up @@ -691,6 +693,13 @@ public InHandleActivationHelper activationHelper() {
return activationHelper;
}

public ScheduleExecutor scheduleExecutor() {
if (scheduleExecutor == null) {
scheduleExecutor = new ScheduleExecutor();
}
return scheduleExecutor;
}

public IssEventInfo issEventInfo() {
if (issEventInfo == null) {
issEventInfo = new IssEventInfo(properties());
Expand Down Expand Up @@ -1259,9 +1268,14 @@ private Function<HederaFunctionality, List<TransitionLogic>> transitions() {
/* Schedule */
entry(ScheduleCreate,
List.of(new ScheduleCreateTransitionLogic(
scheduleStore(), txnCtx(), activationHelper(), validator()))),
scheduleStore(), txnCtx(), activationHelper(), validator(), scheduleExecutor()))),
entry(ScheduleSign,
List.of(new ScheduleSignTransitionLogic(scheduleStore(), txnCtx(), activationHelper()))),
List.of(new ScheduleSignTransitionLogic(
scheduleStore(),
txnCtx(),
activationHelper(),
scheduleExecutor()
))),
entry(ScheduleDelete,
List.of(new ScheduleDeleteTransitionLogic(scheduleStore(), txnCtx()))),
/* System */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS;

public class ScheduleCreateTransitionLogic extends ScheduleReadyForExecution implements TransitionLogic {
public class ScheduleCreateTransitionLogic implements TransitionLogic {
private static final Logger log = LogManager.getLogger(ScheduleCreateTransitionLogic.class);

private static final EnumSet<ResponseCodeEnum> ACCEPTABLE_SIGNING_OUTCOMES = EnumSet.of(OK,
Expand All @@ -61,19 +61,28 @@ public class ScheduleCreateTransitionLogic extends ScheduleReadyForExecution imp
private final InHandleActivationHelper activationHelper;
private final Function<TransactionBody, ResponseCodeEnum> SYNTAX_CHECK = this::validate;

ExecutionProcessor executor = this::processExecution;
private final ScheduleExecutor executor;
private final ScheduleStore store;
private final TransactionContext txnCtx;

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

public ScheduleCreateTransitionLogic(
ScheduleStore store,
TransactionContext txnCtx,
InHandleActivationHelper activationHelper,
OptionValidator validator
) {
super(store, txnCtx);
OptionValidator validator,
ScheduleExecutor executor) {
this.store = store;
this.txnCtx = txnCtx;
this.activationHelper = activationHelper;
this.validator = validator;
this.executor = executor;
}

public ScheduleExecutor getExecutor() {
return executor;
}

@Override
Expand Down Expand Up @@ -129,7 +138,7 @@ private void transitionFor(byte[] bodyBytes, SignatureMap sigMap) throws Invalid

var finalOutcome = OK;
if (signingOutcome.getRight()) {
finalOutcome = executor.doProcess(scheduleId);
finalOutcome = executor.processExecution(scheduleId, store, txnCtx);
}
completeContextWith(scheduleId, schedule, finalOutcome == OK ? SUCCESS : finalOutcome);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.hedera.services.txns.schedule;

/*-
* ‌
* 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
*
* 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.InvalidProtocolBufferException;
import com.hedera.services.context.TransactionContext;
import com.hedera.services.store.schedule.ScheduleStore;
import com.hedera.services.utils.TriggeredTxnAccessor;
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
import com.hederahashgraph.api.proto.java.ScheduleID;
import org.jetbrains.annotations.NotNull;

import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.OK;

/**
* Defines a final class to handle scheduled transaction execution once the scheduled transaction is signed by the
* required number of parties.
*
* @author Michael Tinker
* @author Abhishek Pandey
*/
public final class ScheduleExecutor {
/**
* Given a {@link ScheduleID}, {@link ScheduleStore}, {@link TransactionContext} it first checks if the underlying
* transaction is already executed/deleted before attempting to execute and then returns response code after
* triggering the underlying transaction. A ResponseEnumCode of OK is returned upon successful trigger of the
* inner transaction. The arguments cannot be null, the return type would always be a proper ResponseEnumCode.
*
* @param id The non null id of the scheduled transaction.
* @param store A non null object to handle scheduled entity type.
* @param context A non null object to handle inner transaction specific context on a node.
* @return the response code {@link ResponseCodeEnum} from executing the inner scheduled transaction
*/
ResponseCodeEnum processExecution(
@NotNull ScheduleID id,
@NotNull ScheduleStore store,
@NotNull TransactionContext context
) throws
InvalidProtocolBufferException, NullPointerException {
final var executionStatus = store.markAsExecuted(id);
if (executionStatus != OK) {
return executionStatus;
}

final var schedule = store.get(id);
final var transaction = schedule.asSignedTxn();
context.trigger(
new TriggeredTxnAccessor(
transaction.toByteArray(),
schedule.effectivePayer().toGrpcAccountId(),
id));
return OK;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,32 @@
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.FAIL_INVALID;
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;

public class ScheduleSignTransitionLogic extends ScheduleReadyForExecution implements TransitionLogic {
public class ScheduleSignTransitionLogic implements TransitionLogic {
private static final Logger log = LogManager.getLogger(ScheduleSignTransitionLogic.class);

private final Function<TransactionBody, ResponseCodeEnum> SYNTAX_CHECK = this::validate;

private final InHandleActivationHelper activationHelper;

ExecutionProcessor executor = this::processExecution;
private ScheduleExecutor executor;
private final ScheduleStore store;
private final TransactionContext txnCtx;

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

public ScheduleSignTransitionLogic(
ScheduleStore store,
TransactionContext txnCtx,
InHandleActivationHelper activationHelper
) {
super(store, txnCtx);
InHandleActivationHelper activationHelper,
ScheduleExecutor executor) {
this.store = store;
this.txnCtx = txnCtx;
this.executor = executor;
this.activationHelper = activationHelper;
}

Expand All @@ -76,20 +83,30 @@ 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 validScheduleKeys = classifier.validScheduleKeys(
List.of(txnCtx.activePayerKey()),
sigMap,
activationHelper.currentSigsFn(),
activationHelper::visitScheduledCryptoSigs);
var scheduleId = op.getScheduleID();
var signingOutcome = replSigningsWitness.observeInScope(scheduleId, store, validScheduleKeys, activationHelper);

var outcome = signingOutcome.getLeft();
if (outcome == OK) {
var updatedSchedule = store.get(scheduleId);
txnCtx.setScheduledTxnId(updatedSchedule.scheduledTransactionId());
if (signingOutcome.getRight()) {
outcome = executor.doProcess(scheduleId);
outcome = executor.processExecution(scheduleId, store, txnCtx);
}
}
txnCtx.setStatus(outcome == OK ? SUCCESS : outcome);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public class ScheduleCreateTransitionLogicTest {
private PlatformTxnAccessor accessor;
private TransactionContext txnCtx;
private SignatoryUtils.ScheduledSigningsWitness replSigningWitness;
private ScheduleReadyForExecution.ExecutionProcessor executor;
private ScheduleExecutor executor;

private boolean adminKeyActuallySkipped = false;
private boolean invalidAdminKeyIsSentinelKeyList = false;
Expand Down Expand Up @@ -123,7 +123,7 @@ private void setup() throws InvalidProtocolBufferException {
accessor = mock(PlatformTxnAccessor.class);
activationHelper = mock(InHandleActivationHelper.class);
replSigningWitness = mock(SignatoryUtils.ScheduledSigningsWitness.class);
executor = mock(ScheduleReadyForExecution.ExecutionProcessor.class);
executor = mock(ScheduleExecutor.class);
merkleSchedule = mock(MerkleSchedule.class);

given(accessor.getTxnBytes()).willReturn(bodyBytes);
Expand All @@ -133,41 +133,18 @@ private void setup() throws InvalidProtocolBufferException {
given(replSigningWitness.observeInScope(schedule, store, validScheduleKeys, activationHelper))
.willReturn(Pair.of(OK, true));

given(executor.doProcess(schedule)).willReturn(OK);
given(executor.processExecution(any(), any(), any())).willReturn(OK);

txnCtx = mock(TransactionContext.class);
given(txnCtx.activePayer()).willReturn(payer);
given(txnCtx.activePayerKey()).willReturn(payerKey);

subject = new ScheduleCreateTransitionLogic(store, txnCtx, activationHelper, validator);
subject = new ScheduleCreateTransitionLogic(store, txnCtx, activationHelper, validator, executor);

subject.signingsWitness = replSigningWitness;
subject.executor = executor;
subject.classifier = classifier;
}

@Test
public void validProcessExecution() throws InvalidProtocolBufferException {
// setup:
givenValidTxnCtx();
// and:
given(merkleSchedule.effectivePayer()).willReturn(EntityId.fromGrpcAccountId(payer));
given(merkleSchedule.asSignedTxn()).willReturn(Transaction.getDefaultInstance());
given(store.get(schedule)).willReturn(merkleSchedule);
// and:
given(store.markAsExecuted(schedule)).willReturn(OK);

// when:
var result = subject.processExecution(schedule);

// then:
verify(store).get(schedule);
verify(txnCtx).trigger(any());
verify(store).markAsExecuted(schedule);
// and:
assertEquals(OK, result);
}

@Test
public void hasCorrectApplicability() {
givenValidTxnCtx();
Expand Down Expand Up @@ -264,7 +241,7 @@ public void rollsBackForAnyNonOkSigning() throws InvalidProtocolBufferException
verify(store).createProvisionally(eq(merkleSchedule), eq(RichInstant.fromJava(now)));
verify(store, never()).commitCreation();
verify(txnCtx).setStatus(SOME_SIGNATURES_WERE_INVALID);
verify(executor, never()).doProcess(any());
verify(executor, never()).processExecution(schedule, store, txnCtx);
}

@Test
Expand Down
Loading