|
|
|
@ -20,7 +20,9 @@ import static org.hyperledger.besu.ethereum.trie.verkle.util.Parameters.CODE_OFF |
|
|
|
|
import static org.hyperledger.besu.ethereum.trie.verkle.util.Parameters.VERKLE_NODE_WIDTH; |
|
|
|
|
import static org.hyperledger.besu.evm.internal.Words.clampedAdd; |
|
|
|
|
|
|
|
|
|
import org.hyperledger.besu.datatypes.AccessWitness; |
|
|
|
|
import org.hyperledger.besu.datatypes.Address; |
|
|
|
|
import org.hyperledger.besu.datatypes.Wei; |
|
|
|
|
import org.hyperledger.besu.ethereum.trie.verkle.adapter.TrieKeyAdapter; |
|
|
|
|
import org.hyperledger.besu.ethereum.trie.verkle.hasher.PedersenHasher; |
|
|
|
|
|
|
|
|
@ -28,146 +30,174 @@ import java.util.HashMap; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Map; |
|
|
|
|
import java.util.Objects; |
|
|
|
|
import java.util.Optional; |
|
|
|
|
|
|
|
|
|
import org.apache.tuweni.units.bigints.UInt256; |
|
|
|
|
import org.slf4j.Logger; |
|
|
|
|
import org.slf4j.LoggerFactory; |
|
|
|
|
|
|
|
|
|
public class Eip4762AccessWitness implements org.hyperledger.besu.datatypes.AccessWitness { |
|
|
|
|
public class Eip4762AccessWitness implements AccessWitness { |
|
|
|
|
|
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(Eip4762AccessWitness.class); |
|
|
|
|
private static final TrieKeyAdapter TRIE_KEY_ADAPTER = new TrieKeyAdapter(new PedersenHasher()); |
|
|
|
|
private static final long WITNESS_BRANCH_READ_COST = 1900; |
|
|
|
|
private static final long WITNESS_CHUNK_READ_COST = 200; |
|
|
|
|
private static final long WITNESS_BRANCH_WRITE_COST = 3000; |
|
|
|
|
private static final long WITNESS_CHUNK_WRITE_COST = 500; |
|
|
|
|
private static final long WITNESS_CHUNK_FILL_COST = 6200; |
|
|
|
|
private static final long WITNESS_BRANCH_COST = 1900; |
|
|
|
|
private static final long WITNESS_CHUNK_COST = 200; |
|
|
|
|
private static final long SUBTREE_EDIT_COST = 3000; |
|
|
|
|
private static final long CHUNK_EDIT_COST = 500; |
|
|
|
|
private static final long CHUNK_FILL_COST = 6200; |
|
|
|
|
|
|
|
|
|
private static final UInt256 zeroTreeIndex = UInt256.ZERO; |
|
|
|
|
private static final byte AccessWitnessReadFlag = 1; |
|
|
|
|
private static final byte AccessWitnessWriteFlag = 2; |
|
|
|
|
private final Map<BranchAccessKey, Byte> branches; |
|
|
|
|
private final Map<ChunkAccessKey, Byte> chunks; |
|
|
|
|
private final Map<LeafAccessKey, Integer> leaves; |
|
|
|
|
private final Map<BranchAccessKey, Integer> branches; |
|
|
|
|
|
|
|
|
|
public Eip4762AccessWitness() { |
|
|
|
|
this(new HashMap<>(), new HashMap<>()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Eip4762AccessWitness( |
|
|
|
|
final Map<BranchAccessKey, Byte> branches, final Map<ChunkAccessKey, Byte> chunks) { |
|
|
|
|
final Map<LeafAccessKey, Integer> leaves, final Map<BranchAccessKey, Integer> branches) { |
|
|
|
|
this.branches = branches; |
|
|
|
|
this.chunks = chunks; |
|
|
|
|
this.leaves = leaves; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public List<Address> keys() { |
|
|
|
|
return this.chunks.keySet().stream() |
|
|
|
|
.map(chunkAccessKey -> chunkAccessKey.branchAccessKey().address()) |
|
|
|
|
.toList(); |
|
|
|
|
public long touchAddressAndChargeRead(final Address address, final UInt256 leafKey) { |
|
|
|
|
return touchAddressOnReadAndComputeGas(address, zeroTreeIndex, leafKey); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeProofOfAbsence(final Address address) { |
|
|
|
|
public long touchAndChargeValueTransfer( |
|
|
|
|
final Address caller, |
|
|
|
|
final Address target, |
|
|
|
|
final boolean isAccountCreation, |
|
|
|
|
final long warmReadCost) { |
|
|
|
|
|
|
|
|
|
long gas = 0; |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnReadAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnReadAndComputeGas(address, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
gas, touchAddressOnWriteResetAndComputeGas(caller, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeMessageCall(final Address address) { |
|
|
|
|
return touchAddressOnReadAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeValueTransfer(final Address caller, final Address target) { |
|
|
|
|
if (isAccountCreation) { |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteSetAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteSetAndComputeGas(target, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
long gas = 0; |
|
|
|
|
long readTargetStatelessGas = |
|
|
|
|
touchAddressOnReadAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY); |
|
|
|
|
if (readTargetStatelessGas == 0) { |
|
|
|
|
readTargetStatelessGas = clampedAdd(readTargetStatelessGas, warmReadCost); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(caller, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas, touchAddressOnWriteResetAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
return gas; |
|
|
|
|
return clampedAdd(gas, readTargetStatelessGas); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeContractCreateInit( |
|
|
|
|
final Address address, final boolean createSendsValue) { |
|
|
|
|
public long touchAndChargeValueTransferSelfDestruct( |
|
|
|
|
final Address caller, |
|
|
|
|
final Address target, |
|
|
|
|
final boolean isAccountCreation, |
|
|
|
|
final long warmReadCost) { |
|
|
|
|
|
|
|
|
|
long gas = 0; |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas, touchAddressOnWriteResetAndComputeGas(caller, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
if (caller.equals(target)) { |
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (createSendsValue) { |
|
|
|
|
if (isAccountCreation) { |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteSetAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas, touchAddressOnWriteSetAndComputeGas(target, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
return gas; |
|
|
|
|
|
|
|
|
|
long readTargetStatelessGas = |
|
|
|
|
touchAddressOnReadAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY); |
|
|
|
|
if (readTargetStatelessGas == 0) { |
|
|
|
|
readTargetStatelessGas = clampedAdd(readTargetStatelessGas, warmReadCost); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteResetAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
return clampedAdd(gas, readTargetStatelessGas); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeContractCreateCompleted(final Address address) { |
|
|
|
|
|
|
|
|
|
public long touchAndChargeProofOfAbsence(final Address address) { |
|
|
|
|
long gas = 0; |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas, touchAddressOnReadAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(address, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
gas, touchAddressOnReadAndComputeGas(address, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unused") |
|
|
|
|
@Override |
|
|
|
|
public long touchTxOriginAndComputeGas(final Address origin) { |
|
|
|
|
public long touchAndChargeContractCreateCompleted(final Address address) { |
|
|
|
|
|
|
|
|
|
long gas = 0; |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnReadAndComputeGas(origin, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas, |
|
|
|
|
touchAddressOnWriteResetAndComputeGas(address, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(origin, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd(gas, touchAddressOnReadAndComputeGas(origin, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
// modifying this after update on EIP-4762 to not charge simple transfers
|
|
|
|
|
gas, touchAddressOnWriteResetAndComputeGas(address, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unused") |
|
|
|
|
@Override |
|
|
|
|
public long touchTxExistingAndComputeGas(final Address target, final boolean sendsValue) { |
|
|
|
|
|
|
|
|
|
long gas = 0; |
|
|
|
|
public void touchBaseTx(final Address origin, final Optional<Address> target, final Wei value) { |
|
|
|
|
LOG.atDebug().log("START OF UNCHARGED COSTS"); |
|
|
|
|
touchTxOrigin(origin); |
|
|
|
|
if (target.isPresent()) { // is not contract creation
|
|
|
|
|
final Address to = target.get(); |
|
|
|
|
final boolean sendsValue = !Wei.ZERO.equals(value); |
|
|
|
|
touchTxExisting(to, sendsValue); |
|
|
|
|
} |
|
|
|
|
LOG.atDebug().log("END OF UNCHARGED COSTS"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnReadAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
gas = |
|
|
|
|
clampedAdd(gas, touchAddressOnReadAndComputeGas(target, zeroTreeIndex, CODE_HASH_LEAF_KEY)); |
|
|
|
|
private void touchTxOrigin(final Address origin) { |
|
|
|
|
touchAddressOnWriteResetAndComputeGas(origin, zeroTreeIndex, BASIC_DATA_LEAF_KEY); |
|
|
|
|
touchAddressOnReadAndComputeGas(origin, zeroTreeIndex, CODE_HASH_LEAF_KEY); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (sendsValue) { |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, touchAddressOnWriteAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY)); |
|
|
|
|
private void touchTxExisting(final Address target, final boolean sendsValue) { |
|
|
|
|
touchAddressOnReadAndComputeGas(target, zeroTreeIndex, CODE_HASH_LEAF_KEY); |
|
|
|
|
if (!sendsValue) { |
|
|
|
|
touchAddressOnReadAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
// modifying this after update on EIP-4762 to not charge simple transfers
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
// TODO: not done right now on Geth either - implement case if target does not exist yet - a
|
|
|
|
|
// CODEHASH_LEAF will be touched too and WriteSet will be called
|
|
|
|
|
touchAddressOnWriteResetAndComputeGas(target, zeroTreeIndex, BASIC_DATA_LEAF_KEY); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@ -177,7 +207,7 @@ public class Eip4762AccessWitness implements org.hyperledger.besu.datatypes.Acce |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, |
|
|
|
|
touchAddressOnWriteAndComputeGas( |
|
|
|
|
touchAddressOnWriteSetAndComputeGas( |
|
|
|
|
address, |
|
|
|
|
CODE_OFFSET.add(i).divide(VERKLE_NODE_WIDTH), |
|
|
|
|
CODE_OFFSET.add(i).mod(VERKLE_NODE_WIDTH))); |
|
|
|
@ -187,173 +217,217 @@ public class Eip4762AccessWitness implements org.hyperledger.besu.datatypes.Acce |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchCodeChunks( |
|
|
|
|
final Address address, final long startPc, final long readSize, final long codeLength) { |
|
|
|
|
final Address contractAddress, |
|
|
|
|
final boolean isContractInDeployment, |
|
|
|
|
final long startPc, |
|
|
|
|
final long readSize, |
|
|
|
|
final long codeLength) { |
|
|
|
|
long gas = 0; |
|
|
|
|
if ((readSize == 0 && codeLength == 0) || startPc > codeLength) { |
|
|
|
|
|
|
|
|
|
if (isContractInDeployment || readSize == 0 || startPc >= codeLength) { |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
long endPc = startPc + readSize; |
|
|
|
|
if (endPc > codeLength) { |
|
|
|
|
endPc = codeLength; |
|
|
|
|
} |
|
|
|
|
if (endPc > 0) { |
|
|
|
|
endPc -= 1; |
|
|
|
|
} |
|
|
|
|
for (long i = startPc / 31; i <= endPc / 31; i++) { |
|
|
|
|
|
|
|
|
|
// last byte read is limited by code length, and it is an index, hence the decrement
|
|
|
|
|
long endPc = Math.min(startPc + readSize, codeLength) - 1L; |
|
|
|
|
for (long i = startPc / 31L; i <= endPc / 31L; i++) { |
|
|
|
|
gas = |
|
|
|
|
clampedAdd( |
|
|
|
|
gas, |
|
|
|
|
touchAddressOnReadAndComputeGas( |
|
|
|
|
address, |
|
|
|
|
contractAddress, |
|
|
|
|
CODE_OFFSET.add(i).divide(VERKLE_NODE_WIDTH), |
|
|
|
|
CODE_OFFSET.add(i).mod(VERKLE_NODE_WIDTH))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAddressOnWriteAndComputeGas( |
|
|
|
|
private long touchAddressOnWriteResetAndComputeGas( |
|
|
|
|
final Address address, final UInt256 treeIndex, final UInt256 subIndex) { |
|
|
|
|
return touchAddressAndChargeGas(address, treeIndex, subIndex, AccessMode.WRITE_RESET); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return touchAddressAndChargeGas(address, treeIndex, subIndex, true); |
|
|
|
|
private long touchAddressOnWriteSetAndComputeGas( |
|
|
|
|
final Address address, final UInt256 treeIndex, final UInt256 subIndex) { |
|
|
|
|
// TODO: change to WRITE_SET when CHUNK_FILL is implemented. Still not implemented in devnet-7
|
|
|
|
|
return touchAddressAndChargeGas(address, treeIndex, subIndex, AccessMode.WRITE_RESET); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAddressOnReadAndComputeGas( |
|
|
|
|
private long touchAddressOnReadAndComputeGas( |
|
|
|
|
final Address address, final UInt256 treeIndex, final UInt256 subIndex) { |
|
|
|
|
return touchAddressAndChargeGas(address, treeIndex, subIndex, false); |
|
|
|
|
return touchAddressAndChargeGas(address, treeIndex, subIndex, AccessMode.READ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private List<UInt256> getStorageSlotTreeIndexes(final UInt256 storageKey) { |
|
|
|
|
return List.of( |
|
|
|
|
TRIE_KEY_ADAPTER.locateStorageKeyOffset(storageKey), |
|
|
|
|
TRIE_KEY_ADAPTER.locateStorageKeySuffix(storageKey)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeStorageLoad(final Address address, final UInt256 storageKey) { |
|
|
|
|
List<UInt256> treeIndexes = getStorageSlotTreeIndexes(storageKey); |
|
|
|
|
return touchAddressOnReadAndComputeGas(address, treeIndexes.get(0), treeIndexes.get(1)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public long touchAndChargeStorageStore( |
|
|
|
|
final Address address, final UInt256 storageKey, final boolean hasPreviousValue) { |
|
|
|
|
List<UInt256> treeIndexes = getStorageSlotTreeIndexes(storageKey); |
|
|
|
|
if (!hasPreviousValue) { |
|
|
|
|
return touchAddressOnWriteSetAndComputeGas(address, treeIndexes.get(0), treeIndexes.get(1)); |
|
|
|
|
} |
|
|
|
|
return touchAddressOnWriteResetAndComputeGas(address, treeIndexes.get(0), treeIndexes.get(1)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public long touchAddressAndChargeGas( |
|
|
|
|
final Address address, |
|
|
|
|
final UInt256 treeIndex, |
|
|
|
|
final UInt256 subIndex, |
|
|
|
|
final boolean isWrite) { |
|
|
|
|
|
|
|
|
|
AccessEvents accessEvent = touchAddress(address, treeIndex, subIndex, isWrite); |
|
|
|
|
boolean logEnabled = false; |
|
|
|
|
final int accessMode) { |
|
|
|
|
final short accessEvents = touchAddress(address, treeIndex, subIndex, accessMode); |
|
|
|
|
long gas = 0; |
|
|
|
|
if (accessEvent.isBranchRead()) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_BRANCH_READ_COST); |
|
|
|
|
if (logEnabled) { |
|
|
|
|
System.out.println( |
|
|
|
|
"touchAddressAndChargeGas WitnessBranchReadCost " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ isWrite |
|
|
|
|
+ " " |
|
|
|
|
+ gas); |
|
|
|
|
} |
|
|
|
|
if (AccessEvents.isBranchRead(accessEvents)) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_BRANCH_COST); |
|
|
|
|
final long gasView = gas; |
|
|
|
|
LOG.atDebug().log( |
|
|
|
|
() -> |
|
|
|
|
"touchAddressAndChargeGas WITNESS_BRANCH_COST " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ AccessMode.toString(accessMode) |
|
|
|
|
+ " " |
|
|
|
|
+ gasView); |
|
|
|
|
} |
|
|
|
|
if (accessEvent.isChunkRead()) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_CHUNK_READ_COST); |
|
|
|
|
if (logEnabled) { |
|
|
|
|
System.out.println( |
|
|
|
|
"touchAddressAndChargeGas WitnessChunkReadCost " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ isWrite |
|
|
|
|
+ " " |
|
|
|
|
+ gas); |
|
|
|
|
} |
|
|
|
|
if (AccessEvents.isLeafRead(accessEvents)) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_CHUNK_COST); |
|
|
|
|
final long gasView = gas; |
|
|
|
|
LOG.atDebug().log( |
|
|
|
|
() -> |
|
|
|
|
"touchAddressAndChargeGas WITNESS_CHUNK_COST " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ AccessMode.toString(accessMode) |
|
|
|
|
+ " " |
|
|
|
|
+ gasView); |
|
|
|
|
} |
|
|
|
|
if (accessEvent.isBranchWrite()) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_BRANCH_WRITE_COST); |
|
|
|
|
if (logEnabled) { |
|
|
|
|
System.out.println( |
|
|
|
|
"touchAddressAndChargeGas WitnessBranchWriteCost " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ isWrite |
|
|
|
|
+ " " |
|
|
|
|
+ gas); |
|
|
|
|
} |
|
|
|
|
if (AccessEvents.isBranchWrite(accessEvents)) { |
|
|
|
|
gas = clampedAdd(gas, SUBTREE_EDIT_COST); |
|
|
|
|
final long gasView = gas; |
|
|
|
|
LOG.atDebug().log( |
|
|
|
|
() -> |
|
|
|
|
"touchAddressAndChargeGas SUBTREE_EDIT_COST " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ AccessMode.toString(accessMode) |
|
|
|
|
+ " " |
|
|
|
|
+ gasView); |
|
|
|
|
} |
|
|
|
|
if (accessEvent.isChunkWrite()) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_CHUNK_WRITE_COST); |
|
|
|
|
if (logEnabled) { |
|
|
|
|
System.out.println( |
|
|
|
|
"touchAddressAndChargeGas WitnessChunkWriteCost " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ isWrite |
|
|
|
|
+ " " |
|
|
|
|
+ gas); |
|
|
|
|
} |
|
|
|
|
if (AccessEvents.isLeafReset(accessEvents)) { |
|
|
|
|
gas = clampedAdd(gas, CHUNK_EDIT_COST); |
|
|
|
|
final long gasView = gas; |
|
|
|
|
LOG.atDebug().log( |
|
|
|
|
() -> |
|
|
|
|
"touchAddressAndChargeGas CHUNK_EDIT_COST " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ AccessMode.toString(accessMode) |
|
|
|
|
+ " " |
|
|
|
|
+ gasView); |
|
|
|
|
} |
|
|
|
|
if (accessEvent.isChunkFill()) { |
|
|
|
|
gas = clampedAdd(gas, WITNESS_CHUNK_FILL_COST); |
|
|
|
|
if (logEnabled) { |
|
|
|
|
System.out.println( |
|
|
|
|
"touchAddressAndChargeGas WitnessChunkFillCost " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ isWrite |
|
|
|
|
+ " " |
|
|
|
|
+ gas); |
|
|
|
|
} |
|
|
|
|
if (AccessEvents.isLeafSet(accessEvents)) { |
|
|
|
|
gas = clampedAdd(gas, CHUNK_FILL_COST); |
|
|
|
|
final long gasView = gas; |
|
|
|
|
LOG.atDebug().log( |
|
|
|
|
() -> |
|
|
|
|
"touchAddressAndChargeGas CHUNK_FILL_COST " |
|
|
|
|
+ address |
|
|
|
|
+ " " |
|
|
|
|
+ treeIndex |
|
|
|
|
+ " " |
|
|
|
|
+ subIndex |
|
|
|
|
+ " " |
|
|
|
|
+ AccessMode.toString(accessMode) |
|
|
|
|
+ " " |
|
|
|
|
+ gasView); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return gas; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public AccessEvents touchAddress( |
|
|
|
|
final Address addr, final UInt256 treeIndex, final UInt256 subIndex, final boolean isWrite) { |
|
|
|
|
AccessEvents accessEvents = new AccessEvents(); |
|
|
|
|
public short touchAddress( |
|
|
|
|
final Address addr, final UInt256 treeIndex, final UInt256 leafIndex, final int accessMode) { |
|
|
|
|
short accessEvents = AccessEvents.NONE; |
|
|
|
|
BranchAccessKey branchKey = new BranchAccessKey(addr, treeIndex); |
|
|
|
|
|
|
|
|
|
ChunkAccessKey chunkKey = new ChunkAccessKey(addr, treeIndex, subIndex); |
|
|
|
|
accessEvents |= touchAddressForBranch(branchKey, accessMode); |
|
|
|
|
if (leafIndex != null) { |
|
|
|
|
accessEvents |= touchAddressForLeaf(branchKey, leafIndex, accessMode); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return accessEvents; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private short touchAddressForBranch(final BranchAccessKey branchKey, final int accessMode) { |
|
|
|
|
short accessEvents = AccessEvents.NONE; |
|
|
|
|
|
|
|
|
|
// Read access.
|
|
|
|
|
if (!this.branches.containsKey(branchKey)) { |
|
|
|
|
accessEvents.setBranchRead(true); |
|
|
|
|
this.branches.put(branchKey, AccessWitnessReadFlag); |
|
|
|
|
} |
|
|
|
|
if (!this.chunks.containsKey(chunkKey)) { |
|
|
|
|
accessEvents.setChunkRead(true); |
|
|
|
|
this.chunks.put(chunkKey, AccessWitnessReadFlag); |
|
|
|
|
accessEvents |= AccessEvents.BRANCH_READ; |
|
|
|
|
this.branches.put(branchKey, AccessMode.READ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO VERKLE: for now testnet doesn't charge
|
|
|
|
|
// chunk filling costs if the leaf was previously empty in the state
|
|
|
|
|
// boolean chunkFill = false;
|
|
|
|
|
// A write is always a read
|
|
|
|
|
if (AccessMode.isWrite(accessMode)) { |
|
|
|
|
int previousAccessMode = |
|
|
|
|
!this.branches.containsKey(branchKey) ? AccessMode.NONE : this.branches.get(branchKey); |
|
|
|
|
if (!AccessMode.isWrite(previousAccessMode)) { |
|
|
|
|
accessEvents |= AccessEvents.BRANCH_WRITE; |
|
|
|
|
this.branches.put(branchKey, (previousAccessMode | accessMode)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (isWrite) { |
|
|
|
|
return accessEvents; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ((this.branches.get(branchKey) & AccessWitnessWriteFlag) == 0) { |
|
|
|
|
accessEvents.setBranchWrite(true); |
|
|
|
|
this.branches.put( |
|
|
|
|
branchKey, (byte) (this.branches.get(branchKey) | AccessWitnessWriteFlag)); |
|
|
|
|
} |
|
|
|
|
private short touchAddressForLeaf( |
|
|
|
|
final BranchAccessKey branchKey, final UInt256 subIndex, final int accessMode) { |
|
|
|
|
LeafAccessKey leafKey = new LeafAccessKey(branchKey, subIndex); |
|
|
|
|
short accessEvents = AccessEvents.NONE; |
|
|
|
|
|
|
|
|
|
byte chunkValue = this.chunks.get(chunkKey); |
|
|
|
|
if (!this.leaves.containsKey(leafKey)) { |
|
|
|
|
accessEvents |= AccessEvents.LEAF_READ; |
|
|
|
|
this.leaves.put(leafKey, AccessMode.READ); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ((chunkValue & AccessWitnessWriteFlag) == 0) { |
|
|
|
|
accessEvents.setChunkWrite(true); |
|
|
|
|
this.chunks.put(chunkKey, (byte) (this.chunks.get(chunkKey) | AccessWitnessWriteFlag)); |
|
|
|
|
// A write is always a read
|
|
|
|
|
if (AccessMode.isWrite(accessMode)) { |
|
|
|
|
int previousAccessMode = |
|
|
|
|
!this.leaves.containsKey(leafKey) ? AccessMode.NONE : this.leaves.get(leafKey); |
|
|
|
|
if (!AccessMode.isWrite(previousAccessMode)) { |
|
|
|
|
accessEvents |= AccessEvents.LEAF_RESET; |
|
|
|
|
if (AccessMode.isWriteSet(accessMode)) { |
|
|
|
|
accessEvents |= AccessEvents.LEAF_SET; |
|
|
|
|
} |
|
|
|
|
this.leaves.put(leafKey, (previousAccessMode | accessMode)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return accessEvents; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -362,41 +436,20 @@ public class Eip4762AccessWitness implements org.hyperledger.besu.datatypes.Acce |
|
|
|
|
if (this == o) return true; |
|
|
|
|
if (o == null || getClass() != o.getClass()) return false; |
|
|
|
|
Eip4762AccessWitness that = (Eip4762AccessWitness) o; |
|
|
|
|
return Objects.equals(branches, that.branches) && Objects.equals(chunks, that.chunks); |
|
|
|
|
return Objects.equals(branches, that.branches) && Objects.equals(leaves, that.leaves); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public int hashCode() { |
|
|
|
|
return Objects.hash(branches, chunks); |
|
|
|
|
return Objects.hash(branches, leaves); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public String toString() { |
|
|
|
|
return "AccessWitness{" + "branches=" + branches + ", chunks=" + chunks + '}'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Map<BranchAccessKey, Byte> getBranches() { |
|
|
|
|
return branches; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Map<ChunkAccessKey, Byte> getChunks() { |
|
|
|
|
return chunks; |
|
|
|
|
return "AccessWitness{" + "leaves=" + leaves + ", branches=" + branches + '}'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public record BranchAccessKey(Address address, UInt256 treeIndex) {} |
|
|
|
|
; |
|
|
|
|
|
|
|
|
|
public record ChunkAccessKey(BranchAccessKey branchAccessKey, UInt256 chunkIndex) { |
|
|
|
|
public ChunkAccessKey( |
|
|
|
|
final Address address, final UInt256 treeIndex, final UInt256 chunkIndex) { |
|
|
|
|
this(new BranchAccessKey(address, treeIndex), chunkIndex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public List<UInt256> getStorageSlotTreeIndexes(final UInt256 storageKey) { |
|
|
|
|
return List.of( |
|
|
|
|
TRIE_KEY_ADAPTER.locateStorageKeyOffset(storageKey), |
|
|
|
|
TRIE_KEY_ADAPTER.locateStorageKeySuffix(storageKey)); |
|
|
|
|
} |
|
|
|
|
public record LeafAccessKey(BranchAccessKey branchAccessKey, UInt256 leafIndex) {} |
|
|
|
|
} |
|
|
|
|