Skip to content

Commit 82f791e

Browse files
tinker-michaeljnetopyriwsimon
authored
fix: cherry pick 12512 (#12513)
Signed-off-by: Michael Tinker <[email protected]> Signed-off-by: Michael Heinrichs <[email protected]> Co-authored-by: Michael Heinrichs <[email protected]> Co-authored-by: Iris Simon <[email protected]>
1 parent a4c50d9 commit 82f791e

File tree

5 files changed

+116
-31
lines changed

5 files changed

+116
-31
lines changed

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetBytecodeHandler.java

+22-7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID;
2121
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
2222
import static com.hedera.hapi.node.base.ResponseType.ANSWER_ONLY;
23+
import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbjResponseType;
2324
import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck;
2425
import static java.util.Objects.requireNonNull;
2526

@@ -31,6 +32,7 @@
3132
import com.hedera.hapi.node.state.token.Account;
3233
import com.hedera.hapi.node.transaction.Query;
3334
import com.hedera.hapi.node.transaction.Response;
35+
import com.hedera.node.app.hapi.utils.fee.SmartContractFeeBuilder;
3436
import com.hedera.node.app.service.contract.impl.state.ContractStateStore;
3537
import com.hedera.node.app.service.token.ReadableAccountStore;
3638
import com.hedera.node.app.spi.fees.Fees;
@@ -48,6 +50,8 @@
4850
*/
4951
@Singleton
5052
public class ContractGetBytecodeHandler extends PaidQueryHandler {
53+
private final SmartContractFeeBuilder feeBuilder = new SmartContractFeeBuilder();
54+
5155
@Inject
5256
public ContractGetBytecodeHandler() {
5357
// Exists for injection
@@ -93,6 +97,23 @@ public Response findResponse(@NonNull final QueryContext context, @NonNull final
9397
.build();
9498
}
9599

100+
@NonNull
101+
@Override
102+
public Fees computeFees(@NonNull final QueryContext context) {
103+
final Bytes effectiveBytecode;
104+
final var contract = contractFrom(context);
105+
if (contract == null || contract.deleted()) {
106+
effectiveBytecode = Bytes.EMPTY;
107+
} else {
108+
effectiveBytecode = bytecodeFrom(context, contract);
109+
}
110+
final var op = context.query().contractGetBytecodeOrThrow();
111+
final var responseType = op.headerOrElse(QueryHeader.DEFAULT).responseType();
112+
final var usage = feeBuilder.getContractByteCodeQueryFeeMatrices(
113+
(int) effectiveBytecode.length(), fromPbjResponseType(responseType));
114+
return context.feeCalculator().legacyCalculate(sigValueObj -> usage);
115+
}
116+
96117
private @Nullable Account contractFrom(@NonNull final QueryContext context) {
97118
final var accountsStore = context.createStore(ReadableAccountStore.class);
98119
final var contractId = context.query().contractGetBytecodeOrThrow().contractIDOrElse(ContractID.DEFAULT);
@@ -102,15 +123,9 @@ public Response findResponse(@NonNull final QueryContext context, @NonNull final
102123

103124
private Bytes bytecodeFrom(@NonNull final QueryContext context, @NonNull Account contract) {
104125
final var store = context.createStore(ContractStateStore.class);
105-
var contractNumber = contract.accountId().accountNum();
126+
var contractNumber = contract.accountIdOrThrow().accountNumOrThrow();
106127
var contractId = ContractID.newBuilder().contractNum(contractNumber).build();
107128
final var bytecode = store.getBytecode(contractId);
108129
return bytecode.code();
109130
}
110-
111-
@NonNull
112-
@Override
113-
public Fees computeFees(@NonNull final QueryContext context) {
114-
return context.feeCalculator().calculate();
115-
}
116131
}

hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/handlers/ContractGetInfoHandler.java

+20-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID;
2020
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
2121
import static com.hedera.hapi.node.base.ResponseType.ANSWER_ONLY;
22+
import static com.hedera.node.app.service.mono.pbj.PbjConverter.fromPbj;
2223
import static com.hedera.node.app.service.token.api.AccountSummariesApi.hexedEvmAddressOf;
2324
import static com.hedera.node.app.service.token.api.AccountSummariesApi.summarizeStakingInfo;
2425
import static com.hedera.node.app.service.token.api.AccountSummariesApi.tokenRelationshipsOf;
@@ -36,6 +37,7 @@
3637
import com.hedera.hapi.node.state.token.Account;
3738
import com.hedera.hapi.node.transaction.Query;
3839
import com.hedera.hapi.node.transaction.Response;
40+
import com.hedera.node.app.hapi.fees.usage.contract.ContractGetInfoUsage;
3941
import com.hedera.node.app.service.token.ReadableAccountStore;
4042
import com.hedera.node.app.service.token.ReadableNetworkStakingRewardsStore;
4143
import com.hedera.node.app.service.token.ReadableStakingInfoStore;
@@ -48,6 +50,7 @@
4850
import com.hedera.node.config.data.LedgerConfig;
4951
import com.hedera.node.config.data.StakingConfig;
5052
import com.hedera.node.config.data.TokensConfig;
53+
import com.hederahashgraph.api.proto.java.FeeData;
5154
import edu.umd.cs.findbugs.annotations.NonNull;
5255
import edu.umd.cs.findbugs.annotations.Nullable;
5356
import javax.inject.Inject;
@@ -105,6 +108,23 @@ public Response findResponse(@NonNull final QueryContext context, @NonNull final
105108
return Response.newBuilder().contractGetInfo(contractGetInfo).build();
106109
}
107110

111+
@NonNull
112+
@Override
113+
public Fees computeFees(@NonNull final QueryContext context) {
114+
return context.feeCalculator().legacyCalculate(sigValueObj -> {
115+
final var contract = contractFrom(context);
116+
if (contract == null) {
117+
return FeeData.getDefaultInstance();
118+
} else {
119+
return ContractGetInfoUsage.newEstimate(fromPbj(context.query()))
120+
.givenCurrentKey(fromPbj(contract.keyOrThrow()))
121+
.givenCurrentMemo(contract.memo())
122+
.givenCurrentTokenAssocs(contract.numberAssociations())
123+
.get();
124+
}
125+
});
126+
}
127+
108128
private ContractInfo infoFor(
109129
@NonNull final Account contract,
110130
@NonNull final TokensConfig tokensConfig,
@@ -149,10 +169,4 @@ private ContractInfo infoFor(
149169
final var contract = accountsStore.getContractById(contractId);
150170
return (contract == null || !contract.smartContract()) ? null : contract;
151171
}
152-
153-
@NonNull
154-
@Override
155-
public Fees computeFees(@NonNull final QueryContext context) {
156-
return context.feeCalculator().calculate();
157-
}
158172
}

hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/handlers/ContractGetBytecodeHandlerTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ void findResponsePositiveTest() {
139139
given(contract.smartContract()).willReturn(true);
140140

141141
given(context.createStore(ContractStateStore.class)).willReturn(contractStore);
142-
given(contract.accountId()).willReturn(accountID);
142+
given(contract.accountIdOrThrow()).willReturn(accountID);
143143
final var expectedResult = Bytes.wrap(new byte[] {1, 2, 3, 4, 5});
144144
final var bytecode = Bytecode.newBuilder().code(expectedResult).build();
145145
given(contractStore.getBytecode(any())).willReturn(bytecode);

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetBytecodeSuite.java

+46-13
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@
1818

1919
import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT;
2020
import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec;
21+
import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.approxChangeFromSnapshot;
22+
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance;
2123
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractBytecode;
2224
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate;
25+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate;
2326
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode;
2427
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor;
28+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot;
29+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor;
30+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing;
2531
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext;
2632
import static com.hedera.services.bdd.suites.contract.Utils.getResourcePath;
2733

@@ -35,6 +41,7 @@
3541
import java.io.File;
3642
import java.util.Arrays;
3743
import java.util.List;
44+
import java.util.concurrent.atomic.AtomicLong;
3845
import org.apache.logging.log4j.LogManager;
3946
import org.apache.logging.log4j.Logger;
4047
import org.bouncycastle.util.encoders.Hex;
@@ -71,21 +78,47 @@ public boolean canRunConcurrent() {
7178
@HapiTest
7279
final HapiSpec getByteCodeWorks() {
7380
final var contract = "EmptyConstructor";
81+
final var canonicalUsdFee = 0.05;
82+
final var canonicalQueryFeeAtActiveRate = new AtomicLong();
7483
return HapiSpec.defaultHapiSpec("GetByteCodeWorks")
75-
.given(uploadInitCode(contract), contractCreate(contract))
76-
.when()
77-
.then(withOpContext((spec, opLog) -> {
78-
final var getBytecode = getContractBytecode(contract).saveResultTo("contractByteCode");
79-
allRunFor(spec, getBytecode);
84+
.given(
85+
cryptoCreate(CIVILIAN_PAYER).balance(ONE_HUNDRED_HBARS),
86+
uploadInitCode(contract),
87+
contractCreate(contract))
88+
.when(balanceSnapshot("beforeQuery", CIVILIAN_PAYER))
89+
.then(
90+
withOpContext((spec, opLog) -> {
91+
final var getBytecode = getContractBytecode(contract)
92+
.payingWith(CIVILIAN_PAYER)
93+
.saveResultTo("contractByteCode")
94+
.exposingBytecodeTo(bytes -> {
95+
canonicalQueryFeeAtActiveRate.set(
96+
spec.ratesProvider().toTbWithActiveRates((long)
97+
(canonicalUsdFee * 100 * TINY_PARTS_PER_WHOLE)));
98+
log.info(
99+
"Canoncal tinybar cost at active rate: {}",
100+
canonicalQueryFeeAtActiveRate.get());
101+
});
102+
allRunFor(spec, getBytecode);
80103

81-
@SuppressWarnings("UnstableApiUsage")
82-
final var originalBytecode =
83-
Hex.decode(Files.toByteArray(new File(getResourcePath(contract, ".bin"))));
84-
final var actualBytecode = spec.registry().getBytes("contractByteCode");
85-
// The original bytecode is modified on deployment
86-
final var expectedBytecode = Arrays.copyOfRange(originalBytecode, 29, originalBytecode.length);
87-
Assertions.assertArrayEquals(expectedBytecode, actualBytecode);
88-
}));
104+
@SuppressWarnings("UnstableApiUsage")
105+
final var originalBytecode =
106+
Hex.decode(Files.toByteArray(new File(getResourcePath(contract, ".bin"))));
107+
final var actualBytecode = spec.registry().getBytes("contractByteCode");
108+
// The original bytecode is modified on deployment
109+
final var expectedBytecode =
110+
Arrays.copyOfRange(originalBytecode, 29, originalBytecode.length);
111+
Assertions.assertArrayEquals(expectedBytecode, actualBytecode);
112+
}),
113+
// Wait for the query payment transaction to be handled
114+
sleepFor(5_000),
115+
sourcing(() -> getAccountBalance(CIVILIAN_PAYER)
116+
.hasTinyBars(
117+
// Just sanity-check a fee within 50% of the canonical fee to be safe
118+
approxChangeFromSnapshot(
119+
"beforeQuery",
120+
-canonicalQueryFeeAtActiveRate.get(),
121+
canonicalQueryFeeAtActiveRate.get() / 2))));
89122
}
90123

91124
@HapiTest

hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractGetInfoSuite.java

+27-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@
1818

1919
import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT;
2020
import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec;
21+
import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.approxChangeFromSnapshot;
2122
import static com.hedera.services.bdd.spec.assertions.ContractInfoAsserts.contractWith;
23+
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance;
2224
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo;
2325
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate;
26+
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate;
2427
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode;
28+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.balanceSnapshot;
2529
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
30+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor;
31+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing;
32+
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext;
2633
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withTargetLedgerId;
2734
import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES;
2835

@@ -33,6 +40,7 @@
3340
import com.hedera.services.bdd.suites.HapiSuite;
3441
import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
3542
import java.util.List;
43+
import java.util.concurrent.atomic.AtomicLong;
3644
import org.apache.logging.log4j.LogManager;
3745
import org.apache.logging.log4j.Logger;
3846
import org.junit.jupiter.api.Tag;
@@ -64,19 +72,34 @@ public boolean canRunConcurrent() {
6472
final HapiSpec getInfoWorks() {
6573
final var contract = "Multipurpose";
6674
final var MEMO = "This is a test.";
75+
final var canonicalUsdPrice = 0.0001;
76+
final var canonicalQueryFeeAtActiveRate = new AtomicLong();
6777
return defaultHapiSpec("GetInfoWorks", NONDETERMINISTIC_TRANSACTION_FEES)
6878
.given(
6979
newKeyNamed("adminKey"),
80+
cryptoCreate(CIVILIAN_PAYER).balance(ONE_HUNDRED_HBARS),
81+
balanceSnapshot("beforeQuery", CIVILIAN_PAYER),
7082
uploadInitCode(contract),
7183
contractCreate(contract)
7284
.adminKey("adminKey")
7385
.entityMemo(MEMO)
74-
.autoRenewSecs(6999999L))
75-
.when()
76-
.then(withTargetLedgerId(ledgerId -> getContractInfo(contract)
86+
.autoRenewSecs(6999999L),
87+
withOpContext((spec, opLog) -> canonicalQueryFeeAtActiveRate.set(spec.ratesProvider()
88+
.toTbWithActiveRates((long) (canonicalUsdPrice * 100 * TINY_PARTS_PER_WHOLE)))))
89+
.when(withTargetLedgerId(ledgerId -> getContractInfo(contract)
90+
.payingWith(CIVILIAN_PAYER)
7791
.hasEncodedLedgerId(ledgerId)
7892
.hasExpectedInfo()
79-
.has(contractWith().memo(MEMO).adminKey("adminKey"))));
93+
.has(contractWith().memo(MEMO).adminKey("adminKey"))))
94+
.then(
95+
// Wait for the query payment transaction to be handled
96+
sleepFor(5_000), sourcing(() -> getAccountBalance(CIVILIAN_PAYER)
97+
.hasTinyBars(
98+
// Just sanity-check a fee within 50% of the canonical fee to be safe
99+
approxChangeFromSnapshot(
100+
"beforeQuery",
101+
-canonicalQueryFeeAtActiveRate.get(),
102+
canonicalQueryFeeAtActiveRate.get() / 2))));
80103
}
81104

82105
@HapiTest

0 commit comments

Comments
 (0)