Skip to content

Commit

Permalink
feat: Differential testing: Enhance contract bytecode dumper to handl…
Browse files Browse the repository at this point in the history
…e modular representation (#11523)

Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com>
  • Loading branch information
vtronkov committed Feb 29, 2024
1 parent de7b0eb commit b58eb1a
Show file tree
Hide file tree
Showing 11 changed files with 539 additions and 4 deletions.
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

0 comments on commit b58eb1a

Please sign in to comment.