Skip to content

Commit

Permalink
Merge pull request #1303 from hashgraph/handle-repeated-signings
Browse files Browse the repository at this point in the history
Abort ScheduleSignTransitionLogic if schedule is resolved
  • Loading branch information
Michael Tinker authored Apr 30, 2021
2 parents ba21b64 + 6722609 commit 711321f
Show file tree
Hide file tree
Showing 18 changed files with 1,794 additions and 123 deletions.
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

0 comments on commit 711321f

Please sign in to comment.