Skip to content

Commit

Permalink
Speed up initcode validation (hyperledger#5050)
Browse files Browse the repository at this point in the history
Speedup initcode validation by
- optimizing the jumpdest loop
- not caching initcode in the code cache 
   (but keep codecache for normal contracts).

Signed-off-by: Danno Ferrin <[email protected]>
  • Loading branch information
shemnon authored Feb 3, 2023
1 parent 53e11e3 commit 7245e01
Show file tree
Hide file tree
Showing 18 changed files with 201 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
Expand Down Expand Up @@ -378,9 +377,7 @@ public TransactionProcessingResult processTransaction(
.contract(contractAddress)
.inputData(Bytes.EMPTY)
.versionedHashes(transaction.getVersionedHashes())
.code(
contractCreationProcessor.getCodeFromEVM(
Hash.hash(initCodeBytes), initCodeBytes))
.code(contractCreationProcessor.getCodeFromEVM(null, initCodeBytes))
.build();
} else {
@SuppressWarnings("OptionalGetWithoutIsPresent") // isContractCall tests isPresent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@ public TransactionProcessingResult processTransaction(
.address(privateContractAddress)
.contract(privateContractAddress)
.inputData(Bytes.EMPTY)
.code(
contractCreationProcessor.getCodeFromEVM(
Hash.hash(initCodeBytes), initCodeBytes))
.code(contractCreationProcessor.getCodeFromEVM(null, initCodeBytes))
.build();
} else {
final Address to = transaction.getTo().get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.code.CodeFactory;
import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.code.EOFLayout;
Expand Down Expand Up @@ -117,7 +116,7 @@ public String considerCode(final String hexCode) {
return "err: layout - " + layout.getInvalidReason() + "\n";
}

var code = CodeFactory.createCode(codeBytes, Hash.hash(codeBytes), 1, true);
var code = CodeFactory.createCode(codeBytes, 1, true);
if (!code.isValid()) {
return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n";
}
Expand Down
9 changes: 5 additions & 4 deletions evm/src/main/java/org/hyperledger/besu/evm/EVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,12 @@ public Operation[] getOperationsUnsafe() {
* @return the code
*/
public Code getCode(final Hash codeHash, final Bytes codeBytes) {
Code result = codeCache.getIfPresent(codeHash);
Code result = codeHash == null ? null : codeCache.getIfPresent(codeHash);
if (result == null) {
result =
CodeFactory.createCode(codeBytes, codeHash, evmSpecVersion.getMaxEofVersion(), false);
codeCache.put(codeHash, result);
result = CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion(), false);
if (codeHash != null) {
codeCache.put(codeHash, result);
}
}
return result;
}
Expand Down
32 changes: 13 additions & 19 deletions evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.hyperledger.besu.evm.code;

import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.Code;

import org.apache.tuweni.bytes.Bytes;
Expand All @@ -35,62 +34,57 @@ private CodeFactory() {
* Create Code.
*
* @param bytes the bytes
* @param codeHash the code hash
* @param maxEofVersion the max eof version
* @param inCreateOperation the in create operation
* @return the code
*/
public static Code createCode(
final Bytes bytes,
final Hash codeHash,
final int maxEofVersion,
final boolean inCreateOperation) {
final Bytes bytes, final int maxEofVersion, final boolean inCreateOperation) {
if (maxEofVersion == 0) {
return new CodeV0(bytes, codeHash);
return new CodeV0(bytes);
} else if (maxEofVersion == 1) {
int codeSize = bytes.size();
if (codeSize > 0 && bytes.get(0) == EOF_LEAD_BYTE) {
if (codeSize == 1 && !inCreateOperation) {
return new CodeV0(bytes, codeHash);
return new CodeV0(bytes);
}
if (codeSize < 3) {
return new CodeInvalid(codeHash, bytes, "EOF Container too short");
return new CodeInvalid(bytes, "EOF Container too short");
}
if (bytes.get(1) != 0) {
if (inCreateOperation) {
// because some 0xef code made it to mainnet, this is only an error at contract create
return new CodeInvalid(codeHash, bytes, "Incorrect second byte");
return new CodeInvalid(bytes, "Incorrect second byte");
} else {
return new CodeV0(bytes, codeHash);
return new CodeV0(bytes);
}
}
int version = bytes.get(2);
if (version != 1) {
return new CodeInvalid(codeHash, bytes, "Unsupported EOF Version: " + version);
return new CodeInvalid(bytes, "Unsupported EOF Version: " + version);
}

final EOFLayout layout = EOFLayout.parseEOF(bytes);
if (!layout.isValid()) {
return new CodeInvalid(
codeHash, bytes, "Invalid EOF Layout: " + layout.getInvalidReason());
return new CodeInvalid(bytes, "Invalid EOF Layout: " + layout.getInvalidReason());
}

final String codeValidationError = CodeV1Validation.validateCode(layout);
if (codeValidationError != null) {
return new CodeInvalid(codeHash, bytes, "EOF Code Invalid : " + codeValidationError);
return new CodeInvalid(bytes, "EOF Code Invalid : " + codeValidationError);
}

final String stackValidationError = CodeV1Validation.validateStack(layout);
if (stackValidationError != null) {
return new CodeInvalid(codeHash, bytes, "EOF Code Invalid : " + stackValidationError);
return new CodeInvalid(bytes, "EOF Code Invalid : " + stackValidationError);
}

return new CodeV1(codeHash, layout);
return new CodeV1(layout);
} else {
return new CodeV0(bytes, codeHash);
return new CodeV0(bytes);
}
} else {
return new CodeInvalid(codeHash, bytes, "Unsupported max code version " + maxEofVersion);
return new CodeInvalid(bytes, "Unsupported max code version " + maxEofVersion);
}
}
}
12 changes: 7 additions & 5 deletions evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.Code;

import java.util.function.Supplier;

import com.google.common.base.Suppliers;
import org.apache.tuweni.bytes.Bytes;

/**
Expand All @@ -27,21 +30,20 @@
*/
public class CodeInvalid implements Code {

private final Hash codeHash;
private final Supplier<Hash> codeHash;
private final Bytes codeBytes;

private final String invalidReason;

/**
* Instantiates a new Code invalid.
*
* @param codeHash the code hash
* @param codeBytes the code bytes
* @param invalidReason the invalid reason
*/
public CodeInvalid(final Hash codeHash, final Bytes codeBytes, final String invalidReason) {
this.codeHash = codeHash;
public CodeInvalid(final Bytes codeBytes, final String invalidReason) {
this.codeBytes = codeBytes;
this.codeHash = Suppliers.memoize(() -> Hash.hash(codeBytes));
this.invalidReason = invalidReason;
}

Expand All @@ -66,7 +68,7 @@ public Bytes getBytes() {

@Override
public Hash getCodeHash() {
return codeHash;
return codeHash.get();
}

@Override
Expand Down
159 changes: 143 additions & 16 deletions evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,24 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.operation.JumpDestOperation;
import org.hyperledger.besu.evm.operation.PushOperation;

import java.util.function.Supplier;

import com.google.common.base.MoreObjects;
import com.google.common.base.Suppliers;
import org.apache.tuweni.bytes.Bytes;

/** The CodeV0. */
public class CodeV0 implements Code {

/** The constant EMPTY_CODE. */
public static final CodeV0 EMPTY_CODE = new CodeV0(Bytes.EMPTY, Hash.EMPTY);
public static final CodeV0 EMPTY_CODE = new CodeV0(Bytes.EMPTY);

/** The bytes representing the code. */
private final Bytes bytes;

/** The hash of the code, needed for accessing metadata about the bytecode */
private final Hash codeHash;
private final Supplier<Hash> codeHash;

/** Used to cache valid jump destinations. */
private long[] validJumpDestinations;
Expand All @@ -46,11 +48,10 @@ public class CodeV0 implements Code {
* Public constructor.
*
* @param bytes The byte representation of the code.
* @param codeHash the Hash of the bytes in the code.
*/
CodeV0(final Bytes bytes, final Hash codeHash) {
CodeV0(final Bytes bytes) {
this.bytes = bytes;
this.codeHash = codeHash;
this.codeHash = Suppliers.memoize(() -> Hash.hash(bytes));
this.codeSectionZero = new CodeSection(bytes.size(), 0, -1, -1, 0);
}

Expand Down Expand Up @@ -97,7 +98,7 @@ public String toString() {

@Override
public Hash getCodeHash() {
return codeHash;
return codeHash.get();
}

@Override
Expand Down Expand Up @@ -155,15 +156,141 @@ long[] calculateJumpDests() {
int j = i & 0x3F;
for (; j < max; i++, j++) {
final byte operationNum = rawCode[i];
if (operationNum == JumpDestOperation.OPCODE) {
thisEntry |= 1L << j;
} else if (operationNum > PushOperation.PUSH_BASE) {
// not needed - && operationNum <= PushOperation.PUSH_MAX
// Java quirk, all bytes are signed, and PUSH32 is 127, which is Byte.MAX_VALUE
// so we don't need to check the upper bound as it will never be violated
final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE;
j += multiByteDataLen;
i += multiByteDataLen;
if (operationNum >= JumpDestOperation.OPCODE) {
switch (operationNum) {
case JumpDestOperation.OPCODE:
thisEntry |= 1L << j;
break;
case 0x60:
i += 1;
j += 1;
break;
case 0x61:
i += 2;
j += 2;
break;
case 0x62:
i += 3;
j += 3;
break;
case 0x63:
i += 4;
j += 4;
break;
case 0x64:
i += 5;
j += 5;
break;
case 0x65:
i += 6;
j += 6;
break;
case 0x66:
i += 7;
j += 7;
break;
case 0x67:
i += 8;
j += 8;
break;
case 0x68:
i += 9;
j += 9;
break;
case 0x69:
i += 10;
j += 10;
break;
case 0x6a:
i += 11;
j += 11;
break;
case 0x6b:
i += 12;
j += 12;
break;
case 0x6c:
i += 13;
j += 13;
break;
case 0x6d:
i += 14;
j += 14;
break;
case 0x6e:
i += 15;
j += 15;
break;
case 0x6f:
i += 16;
j += 16;
break;
case 0x70:
i += 17;
j += 17;
break;
case 0x71:
i += 18;
j += 18;
break;
case 0x72:
i += 19;
j += 19;
break;
case 0x73:
i += 20;
j += 20;
break;
case 0x74:
i += 21;
j += 21;
break;
case 0x75:
i += 22;
j += 22;
break;
case 0x76:
i += 23;
j += 23;
break;
case 0x77:
i += 24;
j += 24;
break;
case 0x78:
i += 25;
j += 25;
break;
case 0x79:
i += 26;
j += 26;
break;
case 0x7a:
i += 27;
j += 27;
break;
case 0x7b:
i += 28;
j += 28;
break;
case 0x7c:
i += 29;
j += 29;
break;
case 0x7d:
i += 30;
j += 30;
break;
case 0x7e:
i += 31;
j += 31;
break;
case 0x7f:
i += 32;
j += 32;
break;
default:
}
}
}
bitmap[entryPos] = thisEntry;
Expand Down
Loading

0 comments on commit 7245e01

Please sign in to comment.