Skip to content

Commit

Permalink
Support HIP-329 CREATE2 opcode (#3217)
Browse files Browse the repository at this point in the history
* Add evm_address to contract and contract_history tables
* Add an EntityIdService service to centralize alias/evm_address lookups
* Make RecordItem HAPI version aware and skip persisting contracts from createdContractIDs if HAPI >= 0.23.0
* Populate evm_address for newly created contracts
* Rename solidity_address to evm_adress in REST API response

Signed-off-by: Xin Li <xin.li@hedera.com>
Signed-off-by: Steven Sheehy <steven.sheehy@hedera.com>
Co-authored-by: Steven Sheehy <steven.sheehy@hedera.com>
Signed-off-by: Matheus DallRosa <matheus.dallrosa@swirlds.com>
  • Loading branch information
2 people authored and matheus-dallrosa committed Feb 21, 2022
1 parent 8b79cf0 commit b3b616b
Show file tree
Hide file tree
Showing 75 changed files with 1,864 additions and 316 deletions.
5 changes: 3 additions & 2 deletions docs/design/smart-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ create table if not exists contract
auto_renew_period bigint null,
created_timestamp bigint null,
deleted boolean null,
evm_address bytea null,
expiration_timestamp bigint null,
file_id bigint null,
id bigint not null,
Expand Down Expand Up @@ -192,11 +193,11 @@ create table if not exists contract_state_change
"_type": "ProtobufEncoded",
"key": "7b2233222c2233222c2233227d"
},
"address": "0x0000000000000000000000000000000000001001",
"auto_renew_period": 7776000,
"contract_id": "0.0.10001",
"created_timestamp": "1633466568.31556926",
"deleted": false,
"evm_address": "0x0000000000000000000000000000000000001001",
"expiration_timestamp": null,
"file_id": 1000,
"memo": "First contract",
Expand Down Expand Up @@ -230,12 +231,12 @@ Optional filters
"_type": "ProtobufEncoded",
"key": "7b2233222c2233222c2233227d"
},
"address": "0x0000000000000000000000000000000000001001",
"auto_renew_period": 7776000,
"bytecode": "0xc896c66db6d98784cc03807640f3dfd41ac3a48c",
"contract_id": "0.0.10001",
"created_timestamp": "1633466229.96874612",
"deleted": false,
"evm_address": "0x0000000000000000000000000000000000001001",
"expiration_timestamp": null,
"file_id": "0.0.1000",
"memo": "First contract",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.hedera.mirror.common.domain;

/*-
* ‌
* Hedera Mirror Node
* ​
* Copyright (C) 2019 - 2022 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.hedera.mirror.common.domain.entity.EntityId;
import com.hedera.mirror.common.domain.entity.EntityType;

public interface Aliasable {

byte[] getAlias();

Boolean getDeleted();

EntityType getType();

EntityId toEntityId();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,40 @@
* ‍
*/

import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.Column;
import javax.persistence.Convert;

import com.hedera.mirror.common.domain.entity.AbstractEntity;
import com.hedera.mirror.common.domain.entity.EntityId;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

import com.hedera.mirror.common.converter.FileIdConverter;
import com.hedera.mirror.common.converter.UnknownIdConverter;
import com.hedera.mirror.common.domain.Aliasable;
import com.hedera.mirror.common.domain.entity.AbstractEntity;
import com.hedera.mirror.common.domain.entity.EntityId;

@Data
@javax.persistence.Entity
@NoArgsConstructor
@SuperBuilder
public class Contract extends AbstractEntity {
public class Contract extends AbstractEntity implements Aliasable {

@Column(updatable = false)
@ToString.Exclude
private byte[] evmAddress;

@Column(updatable = false)
@Convert(converter = FileIdConverter.class)
private EntityId fileId;

@Convert(converter = UnknownIdConverter.class)
private EntityId obtainerId;

@JsonIgnore
@Override
public byte[] getAlias() {
return evmAddress;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
import lombok.experimental.SuperBuilder;

import com.hedera.mirror.common.converter.AccountIdConverter;
import com.hedera.mirror.common.domain.Aliasable;

@Data
@javax.persistence.Entity
@NoArgsConstructor
@SuperBuilder
public class Entity extends AbstractEntity {
public class Entity extends AbstractEntity implements Aliasable {

@Column(updatable = false)
@ToString.Exclude
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ public static EntityId of(AccountID accountID) {
return of(accountID.getShardNum(), accountID.getRealmNum(), accountID.getAccountNum(), EntityType.ACCOUNT);
}

/**
* @deprecated in favor of using EntityIdService.lookup where applicable
*/
@Deprecated(since = "v0.50.0")
public static EntityId of(ContractID contractID) {
return of(contractID.getShardNum(), contractID.getRealmNum(), contractID.getContractNum(),
EntityType.CONTRACT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Transient;

import com.hedera.mirror.common.domain.StreamItem;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.util.Version;
import reactor.core.publisher.Flux;

import com.hedera.mirror.common.converter.AccountIdConverter;
Expand All @@ -50,6 +49,13 @@
@NoArgsConstructor
public class RecordFile implements StreamFile<RecordItem> {

public static final Version HAPI_VERSION_NOT_SET = new Version(0, 0, 0);
public static final Version HAPI_VERSION_0_23_0 = new Version(0, 23, 0);

@Getter(lazy = true)
@Transient
private final Version hapiVersion = hapiVersion();

@ToString.Exclude
private byte[] bytes;

Expand Down Expand Up @@ -105,4 +111,12 @@ public class RecordFile implements StreamFile<RecordItem> {
public StreamType getType() {
return StreamType.RECORD;
}

private Version hapiVersion() {
if (hapiVersionMajor == null || hapiVersionMinor == null || hapiVersionPatch == null) {
return HAPI_VERSION_NOT_SET;
}

return new Version(hapiVersionMajor, hapiVersionMinor, hapiVersionPatch);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;

import com.hedera.mirror.common.exception.ProtobufException;

import com.hederahashgraph.api.proto.java.ResponseCodeEnum;
import com.hederahashgraph.api.proto.java.SignatureMap;
import com.hederahashgraph.api.proto.java.SignedTransaction;
Expand All @@ -36,9 +33,11 @@
import lombok.Getter;
import lombok.Value;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.util.Version;

import com.hedera.mirror.common.domain.StreamItem;
import com.hedera.mirror.common.domain.entity.EntityId;
import com.hedera.mirror.common.exception.ProtobufException;
import com.hedera.mirror.common.util.DomainUtils;

@Log4j2
Expand All @@ -48,6 +47,7 @@ public class RecordItem implements StreamItem {
static final String BAD_RECORD_BYTES_MESSAGE = "Failed to parse record bytes";
static final String BAD_TRANSACTION_BODY_BYTES_MESSAGE = "Error parsing transactionBody from transaction";

private final Version hapiVersion;
private final Transaction transaction;
private final TransactionBodyAndSignatureMap transactionBodyAndSignatureMap;
private final TransactionRecord record;
Expand All @@ -67,7 +67,7 @@ public class RecordItem implements StreamItem {
/**
* Constructs RecordItem from serialized transactionBytes and recordBytes.
*/
public RecordItem(byte[] transactionBytes, byte[] recordBytes) {
public RecordItem(Version hapiVersion, byte[] transactionBytes, byte[] recordBytes) {
try {
transaction = Transaction.parseFrom(transactionBytes);
} catch (InvalidProtocolBufferException e) {
Expand All @@ -80,22 +80,31 @@ record = TransactionRecord.parseFrom(recordBytes);
}
transactionBodyAndSignatureMap = parseTransactionBodyAndSignatureMap(transaction);
transactionType = getTransactionType(transactionBodyAndSignatureMap.getTransactionBody());

this.hapiVersion = hapiVersion;
this.transactionBytes = transactionBytes;
this.recordBytes = recordBytes;
}

// Used only in tests
// There are many brittle RecordItemParser*Tests which rely on bytes being null. Those tests need to be fixed,
// then this function can be removed.
public RecordItem(Transaction transaction, TransactionRecord record) {
public RecordItem(Version hapiVersion, Transaction transaction, TransactionRecord record) {
Objects.requireNonNull(transaction, "transaction is required");
Objects.requireNonNull(record, "record is required");

this.hapiVersion = hapiVersion;
this.transaction = transaction;
transactionBodyAndSignatureMap = parseTransactionBodyAndSignatureMap(transaction);
transactionType = getTransactionType(transactionBodyAndSignatureMap.getTransactionBody());
this.record = record;
transactionBytes = null;
recordBytes = null;
transactionBytes = transaction.toByteArray();
recordBytes = record.toByteArray();
}

// Used only in tests, default hapiVersion to RecordFile.HAPI_VERSION_NOT_SET
public RecordItem(Transaction transaction, TransactionRecord record) {
this(RecordFile.HAPI_VERSION_NOT_SET, transaction, record);
}

private static TransactionBodyAndSignatureMap parseTransactionBodyAndSignatureMap(Transaction transaction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
* ‍
*/

import static com.hedera.mirror.common.domain.entity.EntityType.CONTRACT;

import com.google.protobuf.ByteOutput;
import com.google.protobuf.ByteString;
import com.google.protobuf.UnsafeByteOperations;
Expand All @@ -37,10 +39,14 @@
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.hedera.mirror.common.domain.entity.EntityId;
import com.hedera.mirror.common.exception.InvalidEntityException;

@Log4j2
@UtilityClass
public class DomainUtils {

private static final int EVM_ADDRESS_LENGTH = 20;
private static final long NANOS_PER_SECOND = 1_000_000_000L;
private static final char NULL_CHARACTER = (char) 0;
private static final char NULL_REPLACEMENT = '�'; // Standard replacement character 0xFFFD
Expand Down Expand Up @@ -216,6 +222,40 @@ public static byte[] toBytes(ByteString byteString) {
return byteString.toByteArray();
}

public static ByteString fromBytes(byte[] bytes) {
if (bytes == null) {
return null;
}

return UnsafeByteOperations.unsafeWrap(bytes);
}

// The 'shard.realm.num' form evm address has 4 bytes for shard, and 8 bytes each for realm and num.
public static EntityId fromEvmAddress(byte[] evmAddress) {
try {
if (evmAddress != null && evmAddress.length == EVM_ADDRESS_LENGTH) {
ByteBuffer buffer = ByteBuffer.wrap(evmAddress);
return EntityId.of(buffer.getInt(), buffer.getLong(), buffer.getLong(), CONTRACT);
}
} catch (InvalidEntityException ex) {
log.debug("Failed to parse shard.realm.num form evm address into EntityId", ex);
}
return null;
}

public static byte[] toEvmAddress(EntityId contractId) {
if (EntityId.isEmpty(contractId)) {
throw new InvalidEntityException("Empty contractId");
}

byte[] evmAddress = new byte[EVM_ADDRESS_LENGTH];
ByteBuffer buffer = ByteBuffer.wrap(evmAddress);
buffer.putInt(contractId.getShardNum().intValue());
buffer.putLong(contractId.getRealmNum());
buffer.putLong(contractId.getEntityNum());
return evmAddress;
}

static class UnsafeByteOutput extends ByteOutput {

static final short SIZE = 12 + 4; // Size of the object header plus a compressed object reference to bytes field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public DomainWrapper<Contract, Contract.ContractBuilder> contract() {
.autoRenewPeriod(1800L)
.createdTimestamp(timestamp)
.deleted(false)
.evmAddress(create2EvmAddress())
.expirationTimestamp(timestamp + 30_000_000L)
.fileId(entityId(FILE))
.id(id)
Expand Down Expand Up @@ -338,6 +339,10 @@ public byte[] bytes(int length) {
return bytes;
}

public byte[] create2EvmAddress() {
return bytes(20);
}

public EntityId entityId(EntityType type) {
return EntityId.of(0L, 0L, id(), type);
}
Expand Down
Loading

0 comments on commit b3b616b

Please sign in to comment.