Skip to content

Commit

Permalink
Adding the structure for transient storage opcodes
Browse files Browse the repository at this point in the history
- Adding scratch from the DSL tests and contracts for tests

- The tests are still failing, I am investigating to find the root cause

- Adding more logic to the map structures

- Finishing test validation for basic scenarios of TLOAD/TSTORE

- Now we need to add more scenarios to validate that the memory isn't shared and
erased in the end of the transaction.
  • Loading branch information
fmacleal committed Oct 21, 2024
1 parent ce6b734 commit 920e00a
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 78 deletions.
11 changes: 9 additions & 2 deletions rskj-core/src/main/java/org/ethereum/db/MutableRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
import co.rsk.crypto.Keccak256;
import co.rsk.db.MutableTrieCache;
import co.rsk.db.MutableTrieImpl;
import co.rsk.trie.*;
import co.rsk.trie.IterationElement;
import co.rsk.trie.MutableTrie;
import co.rsk.trie.Trie;
import co.rsk.trie.TrieKeySlice;
import co.rsk.trie.TrieStore;
import com.google.common.annotations.VisibleForTesting;
import org.ethereum.core.AccountState;
import org.ethereum.core.Repository;
Expand All @@ -37,7 +41,10 @@

import javax.annotation.Nonnull;
import java.math.BigInteger;
import java.util.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;

public class MutableRepository implements Repository {
private static final Logger logger = LoggerFactory.getLogger("repository");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* This file is part of RskJ
* Copyright (C) 2024 RSK Labs Ltd.
* (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.ethereum.db;

import co.rsk.db.MutableTrieImpl;
import co.rsk.trie.Trie;
import co.rsk.trie.TrieStoreImpl;
import org.ethereum.core.Repository;
import org.ethereum.datasource.HashMapDB;

public class TransientStorageRepositoryCreator {

private TransientStorageRepositoryCreator() {
}

public static Repository createNewTransientStorage() {
return new MutableRepository(new MutableTrieImpl(new TrieStoreImpl(new HashMapDB()), new Trie()));
}
}
31 changes: 19 additions & 12 deletions rskj-core/src/main/java/org/ethereum/vm/VM.java
Original file line number Diff line number Diff line change
Expand Up @@ -1327,30 +1327,30 @@ else if (oldValue != null && newValue.isZero()) {
}

protected void doTLOAD(){

DataWord key = program.stackPop();
DataWord address = program.getOwnerAddress();

if (isLogEnabled) {
logger.info("Executing TLOAD with parameters: address={} | key = {}", address, key);
logger.info("Executing TLOAD with parameters: key = {}", key);
}
DataWord val = program.transientStorageLoad(key);

program.transientStorageSave(key, address);
// key could be returned to the pool, but storageLoad semantics should be checked
if (val == null) {
val = DataWord.ZERO;
}

program.stackPush(val);
// key could be returned to the pool, but transientStorageLoad semantics should be checked
// to make sure storageLoad always gets a copy, not a reference.
program.step();
}

protected void doTSTORE(){

DataWord address = program.stackPop();
DataWord value = program.stackPop();
DataWord address = program.getOwnerAddress();
DataWord key = DataWord.ZERO;

if (isLogEnabled) {
logger.info("Executing TSTORE with parameters: address={} | key = {} | value = {}", address, key, value);
logger.info("Executing TSTORE with parameters: address={} | value = {}", address, value);
}
program.transientStorageLoad(address, key, value);
program.transientStorageSave(address, value);
program.step();
}

Expand Down Expand Up @@ -1967,9 +1967,16 @@ protected void executeOpcode() {
break;
case OpCodes.OP_SSTORE: doSSTORE();
break;
case OpCodes.OP_TLOAD: doTLOAD();
case OpCodes.OP_TLOAD:
if (!activations.isActive(RSKIP446)) {
throw Program.ExceptionHelper.invalidOpCode(program);
}
doTLOAD();
break;
case OpCodes.OP_TSTORE: doTSTORE();
if (!activations.isActive(RSKIP446)) {
throw Program.ExceptionHelper.invalidOpCode(program);
}
break;
case OpCodes.OP_JUMP: doJUMP();
break;
Expand Down
27 changes: 23 additions & 4 deletions rskj-core/src/main/java/org/ethereum/vm/program/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.ethereum.core.SignatureCache;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.TransientStorageRepositoryCreator;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.FastByteComparisons;
import org.ethereum.vm.DataWord;
Expand Down Expand Up @@ -72,8 +73,10 @@
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

Expand All @@ -95,7 +98,6 @@ public class Program {
private static final Logger logger = LoggerFactory.getLogger("VM");
private static final Logger gasLogger = LoggerFactory.getLogger("gas");


public static final long MAX_MEMORY = (1<<30);

//Max size for stack checks
Expand All @@ -115,6 +117,7 @@ public class Program {
private final Stack stack;
private final Memory memory;
private final Storage storage;
private final Map<RskAddress, Repository> transientStorages;
private byte[] returnDataBuffer;

private final ProgramResult result = new ProgramResult();
Expand Down Expand Up @@ -178,6 +181,7 @@ public Program(
this.stack = setupProgramListener(new Stack());
this.stack.ensureCapacity(1024); // faster?
this.storage = setupProgramListener(new Storage(programInvoke));
this.transientStorages = new HashMap<>();
this.deletedAccountsInBlock = new HashSet<>(deletedAccounts);
this.signatureCache = signatureCache;
precompile();
Expand Down Expand Up @@ -453,6 +457,16 @@ public Repository getStorage() {
return this.storage;
}

public Repository getTransientStorage(RskAddress addr) {
Repository current = transientStorages.get(addr);
if(current == null) {
current = TransientStorageRepositoryCreator.createNewTransientStorage();
transientStorages.put(addr, current);
}
return current;
}


@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public void createContract(DataWord value, DataWord memStart, DataWord memSize) {
RskAddress senderAddress = new RskAddress(getOwnerAddress());
Expand Down Expand Up @@ -985,8 +999,10 @@ private void storageSave(byte[] key, byte[] val) {
getStorage().addStorageRow(getOwnerRskAddress(), keyWord, valWord);
}

public void transientStorageSave(DataWord key, DataWord address) {

public void transientStorageSave(DataWord key, DataWord value) {
RskAddress addr = getOwnerRskAddress();
Repository storage = getTransientStorage(addr);
storage.addStorageRow(addr, key, value);
}

private RskAddress getOwnerRskAddress() {
Expand Down Expand Up @@ -1099,8 +1115,11 @@ public DataWord storageLoad(DataWord key) {
return getStorage().getStorageValue(getOwnerRskAddress(), key);
}

public void transientStorageLoad(DataWord address, DataWord key, DataWord value) {
public DataWord transientStorageLoad(DataWord key) {
RskAddress addr = getOwnerRskAddress();
Repository currentTransientStorage = getTransientStorage(addr);

return currentTransientStorage.getStorageValue(addr, key);
}

public DataWord getPrevHash() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
import org.ethereum.core.Block;
import org.ethereum.core.Transaction;
import org.ethereum.core.TransactionReceipt;
import org.ethereum.core.util.TransactionReceiptUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;

public class TransientStorageDslTest {

Expand All @@ -45,23 +47,59 @@ void testTransientStorageOpcodesExecutionsWithRSKIPActivated() throws FileNotFou
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String contractCreationTx = "txTestTransientStorageContract";
Transaction contractTransaction = world.getTransactionByName("txTestTransientStorageContract");
assertNotNull(contractTransaction);
String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract";
assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true);

Block bestBlock = world.getBlockByName("b03");
Assertions.assertEquals(1, bestBlock.getTransactionsList().size());
TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName(contractCreationTx);
String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract";
assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true);

assertNotNull(contractTransactionReceipt);
byte[] status = contractTransactionReceipt.getStatus();
assertNotNull(status);
assertEquals(1, status.length);
assertEquals(1, status[0]);
String checkingOpcodesTxName = "txTestTransientStorageOpCodes";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b03", true);
Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));

String checkingOpcodesTxName2 = "txTestTransientStorageOpCodesOtherValue";
TransactionReceipt txReceipt2 = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName2, "b04", true);
Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt2, "OK", null));
}

@Test
void testTransientStorageOpcodesShareMemorySameTransaction() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract";
assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true);

String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract";
assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true);

String checkingOpcodesTxName = "txTestTransientStorageNestedTransactionShareMemory";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b05", true);
Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt, "OK", null));
}

@Test
void testTransientStorageOpcodesDoesntShareMemoryFromOtherContract() throws FileNotFoundException, DslProcessorException {
DslParser parser = DslParser.fromResource("dsl/transaction_storage_rskip446/tload_tstore_basic_contract.txt");
World world = new World();
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract";
assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true);

String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract";
assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true);

String checkingOpcodesTxName = "txTestTransientStorageNestedTransactionOtherContractDoesntShareMemory";
TransactionReceipt txReceipt = assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b06", true);
Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txReceipt, "ERROR", new String[]{"bytes32"}));
}

@Test
void testTransientStorageOpcodesExecutionsWithRSKIPDeactivated() throws FileNotFoundException, DslProcessorException {
void testTransientStorageOpcodesExecutionFailsWithRSKIPDeactivated() throws FileNotFoundException, DslProcessorException {
TestSystemProperties rskip446Disabled = new TestSystemProperties(rawConfig ->
rawConfig.withValue("blockchain.config.hardforkActivationHeights.lovell700", ConfigValueFactory.fromAnyRef(-1))
);
Expand All @@ -71,19 +109,36 @@ void testTransientStorageOpcodesExecutionsWithRSKIPDeactivated() throws FileNotF
WorldDslProcessor processor = new WorldDslProcessor(world);
processor.processCommands(parser);

String contractCreationTx = "txTestTransientStorageContract";
Transaction contractTransaction = world.getTransactionByName("txTestTransientStorageContract");
assertNotNull(contractTransaction);
String mainContractTransientStorageCreationTxName = "txTestTransientStorageContract";
assertTransactionReceiptWithStatus(world, mainContractTransientStorageCreationTxName, "b01", true);

String secondaryContractTransientStorageCreationTxName = "txTestTransientStorageOtherContract";
assertTransactionReceiptWithStatus(world, secondaryContractTransientStorageCreationTxName, "b02", true);

String checkingOpcodesTxName = "txTestTransientStorageOpCodes";
assertTransactionReceiptWithStatus(world, checkingOpcodesTxName, "b03", false);
}

Block bestBlock = world.getBlockByName("b03");
Assertions.assertEquals(1, bestBlock.getTransactionsList().size());
TransactionReceipt contractTransactionReceipt = world.getTransactionReceiptByName(contractCreationTx);
private static TransactionReceipt assertTransactionReceiptWithStatus(World world, String txName, String blockName, boolean withSuccess) {
Transaction txCreation = world.getTransactionByName(txName);
assertNotNull(txCreation);

assertNotNull(contractTransactionReceipt);
byte[] status = contractTransactionReceipt.getStatus();
Block block = world.getBlockByName(blockName);
assertEquals(1, block.getTransactionsList().size());

TransactionReceipt txReceipt = world.getTransactionReceiptByName(txName);
assertNotNull(txReceipt);

byte[] status = txReceipt.getStatus();
assertNotNull(status);
assertEquals(1, status.length);
assertEquals(1, status[0]);

if(withSuccess) {
assertEquals(1, status.length);
assertEquals(1, status[0]);
} else {
assertEquals(0, status.length);
}
return txReceipt;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ActivationConfigTest {
" rskip434: arrowhead631",
" rskip428: lovell700",
" rskip438: lovell700",
" rskip446: lovell700",
"}"
));

Expand Down
Loading

0 comments on commit 920e00a

Please sign in to comment.