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

feat: Differential testing: Enhance contract bytecode dumper to handle modular representation #11523

Merged
merged 20 commits into from
Feb 29, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static com.hedera.node.app.bbm.accounts.AccountDumpUtils.dumpMonoAccounts;
import static com.hedera.node.app.bbm.associations.TokenAssociationsDumpUtils.dumpModTokenRelations;
import static com.hedera.node.app.bbm.associations.TokenAssociationsDumpUtils.dumpMonoTokenRelations;
import static com.hedera.node.app.bbm.contracts.ContractBytecodesDumpUtils.dumpModContractBytecodes;
import static com.hedera.node.app.bbm.files.FilesDumpUtils.dumpModFiles;
import static com.hedera.node.app.bbm.files.FilesDumpUtils.dumpMonoFiles;
import static com.hedera.node.app.bbm.nfts.UniqueTokenDumpUtils.dumpModUniqueTokens;
Expand All @@ -36,14 +37,19 @@
import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.ContractID;
import com.hedera.hapi.node.base.FileID;
import com.hedera.hapi.node.base.NftID;
import com.hedera.hapi.node.base.TokenAssociation;
import com.hedera.hapi.node.state.blockrecords.BlockInfo;
import com.hedera.hapi.node.state.contract.Bytecode;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.hapi.node.state.token.Nft;
import com.hedera.hapi.node.state.token.TokenRelation;
import com.hedera.node.app.bbm.contracts.ContractBytecodesDumpUtils;
import com.hedera.node.app.records.BlockRecordService;
import com.hedera.node.app.service.contract.ContractService;
import com.hedera.node.app.service.contract.impl.state.InitialModServiceContractSchema;
import com.hedera.node.app.service.file.FileService;
import com.hedera.node.app.service.file.impl.FileServiceImpl;
import com.hedera.node.app.service.mono.state.merkle.MerkleNetworkContext;
Expand All @@ -68,6 +74,7 @@ public class StateDumper {
private static final String SEMANTIC_TOKEN_RELATIONS = "tokenRelations.txt";
private static final String SEMANTIC_FILES = "files.txt";
private static final String SEMANTIC_ACCOUNTS = "accounts.txt";
private static final String SEMANTIC_CONTRACT_BYTECODES = "contractBytecodes.txt";

public static void dumpMonoChildrenFrom(
@NonNull final MerkleHederaState state, @NonNull final DumpCheckpoint checkpoint) {
Expand All @@ -78,6 +85,12 @@ public static void dumpMonoChildrenFrom(
Paths.get(dumpLoc, SEMANTIC_TOKEN_RELATIONS), state.getChild(TOKEN_ASSOCIATIONS), checkpoint);
dumpMonoFiles(Paths.get(dumpLoc, SEMANTIC_FILES), state.getChild(STORAGE), checkpoint);
dumpMonoAccounts(Paths.get(dumpLoc, SEMANTIC_TOKEN_RELATIONS), state.getChild(ACCOUNTS), checkpoint);

ContractBytecodesDumpUtils.dumpMonoContractBytecodes(
Paths.get(dumpLoc, SEMANTIC_CONTRACT_BYTECODES),
state.getChild(ACCOUNTS),
state.getChild(STORAGE),
checkpoint);
}

public static void dumpModChildrenFrom(
Expand Down Expand Up @@ -106,6 +119,10 @@ public static void dumpModChildrenFrom(
final VirtualMap<OnDiskKey<AccountID>, OnDiskValue<Account>> accounts =
requireNonNull(state.getChild(state.findNodeIndex(TokenService.NAME, ACCOUNTS_KEY)));
dumpModAccounts(Paths.get(dumpLoc, SEMANTIC_ACCOUNTS), accounts, checkpoint);

final VirtualMap<OnDiskKey<ContractID>, OnDiskValue<Bytecode>> contracts = requireNonNull(state.getChild(
state.findNodeIndex(ContractService.NAME, InitialModServiceContractSchema.BYTECODE_KEY)));
dumpModContractBytecodes(Paths.get(dumpLoc, SEMANTIC_CONTRACT_BYTECODES), contracts, checkpoint);
}

private static String getExtantDumpLoc(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public static void dumpModAccounts(
}

@NonNull
private static <K extends VirtualKey, V extends VirtualValue> HederaAccount[] gatherAccounts(
public static <K extends VirtualKey, V extends VirtualValue> HederaAccount[] gatherAccounts(
@NonNull VirtualMap<K, V> accounts, @NonNull Function<V, HederaAccount> mapper) {
final var accountsToReturn = new ConcurrentLinkedQueue<HederaAccount>();
final var threadCount = 8;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.node.app.bbm.contracts;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Arrays;

/** Implements equality-of-content on a byte array so it can be used as a map key */
public record ByteArrayAsKey(@NonNull byte[] array) {

@Override
public boolean equals(final Object obj) {
return obj instanceof ByteArrayAsKey other && Arrays.equals(array, other.array);
}

@Override
public int hashCode() {
return Arrays.hashCode(array);
}

@Override
public String toString() {
return "ByteArrayAsKey{" + "array=" + Arrays.toString(array) + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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.node.app.bbm.contracts;

import com.hedera.hapi.node.base.ContractID;
import com.hedera.hapi.node.state.contract.Bytecode;
import com.hedera.node.app.state.merkle.disk.OnDiskKey;
import com.hedera.node.app.state.merkle.disk.OnDiskValue;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Arrays;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

/**
* A contract - some bytecode associated with its contract id(s)
*
* @param ids - direct from the signed state file there's one contract id for each bytecode, but
* there are duplicates which can be coalesced and then there's a set of ids for the single
* contract; kept in sorted order by the container `TreeSet` so it's easy to get the canonical
* id for the contract, and also you can't forget to process them in a deterministic order
* @param bytecode - bytecode of the contract
* @param validity - whether the contract is valid or note, aka active or deleted
*/
public record Contract(
@NonNull TreeSet</*@NonNull*/ Integer> ids, @NonNull byte[] bytecode, @NonNull Validity validity) {

public static Contract fromMod(OnDiskKey<ContractID> id, OnDiskValue<Bytecode> bytecode) {
final var c = new Contract(new TreeSet<>(), bytecode.getValue().code().toByteArray(), Validity.ACTIVE);
if (id.getKey().contractNum() != null) {
c.ids().add(id.getKey().contractNum().intValue());
}
return c;
}

// For any set of contract ids with the same bytecode, the lowest contract id is used as the "canonical"
// id for that bytecode (useful for ordering contracts deterministically)
public int canonicalId() {
return ids.first();
}

@Override
public boolean equals(final Object o) {
if (o == null) {
return false;
}
if (o == this) {
return true;
}
return o instanceof Contract other
&& new EqualsBuilder()
.append(ids, other.ids)
.append(bytecode, other.bytecode)
.append(validity, other.validity)
.isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(ids)
.append(bytecode)
.append(validity)
.toHashCode();
}

@Override
public String toString() {
var csvIds = ids.stream().map(Object::toString).collect(Collectors.joining(","));
return "Contract{ids=(%s), %s, bytecode=%s}".formatted(csvIds, validity, Arrays.toString(bytecode));
}
}
Loading
Loading