Skip to content

Commit

Permalink
AKI-87: add getStorage and putStorage to Blockchain
Browse files Browse the repository at this point in the history
- Key cannot be null
- Key must be 32 bytes
- Billing is linear based on the value size
  • Loading branch information
beidouz committed Apr 10, 2019
1 parent cf00253 commit 16537d4
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 6 deletions.
21 changes: 21 additions & 0 deletions org.aion.avm.api/src/avm/Blockchain.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ public static BigInteger getBlockDifficulty() {
// Storage
//===================

/**
* puts the key-value data of an account
*
* @param key key of the key-value data pair
* @param value value of the key-value data pair
* @throws IllegalArgumentException when the arguments are invalud, e.g. NULL address
*/
public static void putStorage(byte[] key, byte[] value) throws IllegalArgumentException {
}

/**
* Returns the storage value
*
* @param key key of the key-value data pair
* @return the value in storage associated to the given address and key
* @throws IllegalArgumentException when the arguments are invalid, e.g. NULL address
*/
public static byte[] getStorage(byte[] key) throws IllegalArgumentException {
return null;
}

/**
* Returns the balance of an account.
*
Expand Down
38 changes: 35 additions & 3 deletions org.aion.avm.core/src/org/aion/avm/core/BlockchainRuntimeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ public org.aion.avm.shadow.java.math.BigInteger avm_getBlockDifficulty() {
return org.aion.avm.shadow.java.math.BigInteger.avm_valueOf(kernel.getBlockDifficulty());
}

@Override
public void avm_putStorage(ByteArray key, ByteArray value) {
require(key != null, "Key can't be NULL");
require(key.getUnderlying().length == 32, "Key must be 32 bytes");

org.aion.types.Address contractAddress = getContractAddress();
if (value == null) {
kernel.removeStorage(contractAddress, key.getUnderlying());
} else {
kernel.putStorage(contractAddress, key.getUnderlying(), value.getUnderlying());
}
}

@Override
public ByteArray avm_getStorage(ByteArray key) {
require(key != null, "Key can't be NULL");
require(key.getUnderlying().length == 32, "Key must be 32 bytes");

org.aion.types.Address contractAddress = getContractAddress();
byte[] data = this.kernel.getStorage(contractAddress, key.getUnderlying());
return (null != data)
? new ByteArray(data)
: null;
}

@Override
public org.aion.avm.shadow.java.math.BigInteger avm_getBalance(Address address) {
require(null != address, "Address can't be NULL");
Expand All @@ -117,9 +142,7 @@ public org.aion.avm.shadow.java.math.BigInteger avm_getBalance(Address address)
@Override
public org.aion.avm.shadow.java.math.BigInteger avm_getBalanceOfThisContract() {
// This method can be called inside clinit so CREATE is a valid context.
org.aion.types.Address contractAddress = (tx.isContractCreationTransaction())
? this.capabilities.generateContractAddress(this.tx)
: tx.getDestinationAddress();
org.aion.types.Address contractAddress = getContractAddress();

// Acquire resource before reading
avm.getResourceMonitor().acquire(contractAddress.toBytes(), this.task);
Expand Down Expand Up @@ -408,4 +431,13 @@ private Result runInternalCall(InternalTransaction internalTx) {
return new Result(newResult.getResultCode().isSuccess(),
newResult.getReturnData() == null ? null : new ByteArray(newResult.getReturnData()));
}

private org.aion.types.Address getContractAddress() {
org.aion.types.Address contractAddress =
(tx.isContractCreationTransaction())
? this.capabilities.generateContractAddress(this.tx)
: tx.getDestinationAddress();

return contractAddress;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public void setData(byte[] key, byte[] value) {
writeFile(fileNameForKey(key), value);
}

@Override
public void removeData(byte[] key) {
deleteFile(fileNameForKey(key));
}

@Override
public Map<ByteArrayWrapper, byte[]> getStorageEntries() {
Map<ByteArrayWrapper, byte[]> result = new HashMap<>();
Expand Down Expand Up @@ -140,6 +145,16 @@ private void writeFile(String fileName, byte[] data) {
}
}

private void deleteFile(String fileName) {
Path oneFile = new File(this.accountDirectory, fileName).toPath();
try {
Files.deleteIfExists(oneFile);
} catch (IOException e) {
// This implementation doesn't handle exceptions.
throw RuntimeAssertionError.unexpected(e);
}
}

private long decodeLong(byte[] data) {
long value = (
((long)(0xff & data[0]) << 56)
Expand Down
7 changes: 7 additions & 0 deletions org.aion.avm.core/src/org/aion/data/IAccountStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ public interface IAccountStore {
*/
public void setData(byte[] key, byte[] value);

/**
* Removes the application key-value store.
*
* @param key The key to remove.
*/
public void removeData(byte[] key);

/**
* Used only for testing and will be removed in the future.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public void setData(byte[] key, byte[] value) {
this.storage.put(new ByteArrayWrapper(key), value);
}

@Override
public void removeData(byte[] key) {
this.storage.remove(new ByteArrayWrapper(key));
}

@Override
public Map<ByteArrayWrapper, byte[]> getStorageEntries() {
return this.storage;
Expand Down
2 changes: 1 addition & 1 deletion org.aion.avm.core/src/org/aion/kernel/CachingKernel.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public byte[] getBlockHashByNumber(long blockNumber) {

@Override
public void removeStorage(Address address, byte[] key) {
throw RuntimeAssertionError.unreachable("This class does not implement this method.");
lazyCreateAccount(address.toBytes()).removeData(key);
}

@Override
Expand Down
3 changes: 2 additions & 1 deletion org.aion.avm.core/src/org/aion/kernel/TestingKernel.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ public byte[] getBlockHashByNumber(long blockNumber) {

@Override
public void removeStorage(Address address, byte[] key) {
throw new AssertionError("This class does not implement this method.");
IAccountStore account = this.dataStore.openAccount(address.toBytes());
lazyCreateAccount(address.toBytes()).removeData(key);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,11 @@ public byte[] getBlockHashByNumber(long blockNumber) {

@Override
public void removeStorage(Address address, byte[] key) {
throw new AssertionError("This class does not implement this method.");
Consumer<KernelInterface> write = (kernel) -> {
kernel.removeStorage(address, key);
};
write.accept(writeCache);
writeLog.add(write);
}

@Override
Expand Down
146 changes: 146 additions & 0 deletions org.aion.avm.core/test/org/aion/avm/core/KeyValueStoreTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package org.aion.avm.core;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import java.math.BigInteger;
import org.aion.avm.core.blockchainruntime.EmptyCapabilities;
import org.aion.avm.core.dappreading.JarBuilder;
import org.aion.avm.core.util.ABIUtil;
import org.aion.avm.core.util.CodeAndArguments;
import org.aion.avm.core.util.Helpers;
import org.aion.kernel.AvmTransactionResult;
import org.aion.kernel.AvmTransactionResult.Code;
import org.aion.kernel.Block;
import org.aion.kernel.TestingKernel;
import org.aion.kernel.Transaction;
import org.aion.types.Address;
import org.aion.vm.api.interfaces.TransactionResult;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class KeyValueStoreTest {
// transaction
private long energyLimit = 10_000_000L;
private long energyPrice = 1L;

// block
private Block block = new Block(new byte[32], 1, Helpers.randomAddress(), System.currentTimeMillis(), new byte[0]);

// kernel & vm
private TestingKernel kernel;
private AvmImpl avm;

private Address deployer = TestingKernel.PREMINED_ADDRESS;
private Address dappAddress;


@Before
public void setup() {
this.kernel = new TestingKernel(block);

AvmConfiguration avmConfig = new AvmConfiguration();
this.avm = CommonAvmFactory.buildAvmInstanceForConfiguration(new EmptyCapabilities(), avmConfig);

byte[] jar = JarBuilder.buildJarForMainAndClassesAndUserlib(KeyValueStoreTestTarget.class);
Transaction tx = Transaction.create(deployer, kernel.getNonce(deployer), BigInteger.ZERO, new CodeAndArguments(jar, null).encodeToBytes(), energyLimit, energyPrice);
TransactionResult txResult = avm.run(this.kernel, new Transaction[] {tx})[0].get();
assertEquals(Code.SUCCESS, txResult.getResultCode());
dappAddress = Address.wrap(txResult.getReturnData());
}

@After
public void tearDown() {
this.avm.shutdown();
}

@Test
public void testGetStorageKeyNotExist() {
byte[] key = Helpers.randomBytes(32);
byte[] data = ABIUtil.encodeMethodArguments("testAvmGetStorage", key);
Transaction tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
AvmTransactionResult txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(null, txResult.getReturnData());
}

@Test
public void testGetStorageWrongSizeKey() {
byte[] key = Helpers.randomBytes(33);
byte[] data = ABIUtil.encodeMethodArguments("testAvmGetStorage", key);
Transaction tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
AvmTransactionResult txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.FAILED_EXCEPTION, txResult.getResultCode());
}

@Test
public void testPutStorageWrongSizeKey() {
byte[] key = Helpers.randomBytes(33);
byte[] value = Helpers.randomBytes(32);

byte[] data = ABIUtil.encodeMethodArguments("testAvmPutStorage", key, value);
Transaction tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
AvmTransactionResult txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.FAILED_EXCEPTION, txResult.getResultCode());
}

@Test
public void testPutGetStorageSuccess() {
byte[] key = Helpers.randomBytes(32);
byte[] value = Helpers.randomBytes(32);

byte[] data = ABIUtil.encodeMethodArguments("testAvmPutStorage", key, value);
Transaction tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
AvmTransactionResult txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(new byte[0], txResult.getReturnData());

data = ABIUtil.encodeMethodArguments("testAvmGetStorage", key);
tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(value, txResult.getReturnData());
}

@Test
public void testStoragePutNullDelete() {
byte[] key = Helpers.randomBytes(32);
byte[] value = Helpers.randomBytes(32);

byte[] data = ABIUtil.encodeMethodArguments("testAvmPutStorage", key, value);
Transaction tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
AvmTransactionResult txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(new byte[0], txResult.getReturnData());

data = ABIUtil.encodeMethodArguments("testAvmGetStorage", key);
tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(value, txResult.getReturnData());

// put null to delete
data = ABIUtil.encodeMethodArguments("testAvmPutStorageNullValue", key);
tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(new byte[0], txResult.getReturnData());

data = ABIUtil.encodeMethodArguments("testAvmGetStorage", key);
tx = Transaction.call(deployer, dappAddress, kernel.getNonce(deployer), BigInteger.ZERO, data, energyLimit, energyPrice);
txResult = (AvmTransactionResult) avm.run(this.kernel, new Transaction[] {tx})[0].get();

assertEquals(Code.SUCCESS, txResult.getResultCode());
assertArrayEquals(null, txResult.getReturnData());

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.aion.avm.core;

import avm.Blockchain;
import org.aion.avm.userlib.abi.ABIDecoder;

public class KeyValueStoreTestTarget {

public static byte[] testAvmGetStorage(byte[] key) {
return Blockchain.getStorage(key);
}

public static void testAvmPutStorage(byte[] key, byte[] value) {
Blockchain.putStorage(key, value);
}

public static byte[] main() {
ABIDecoder decoder = new ABIDecoder(Blockchain.getData());
String methodName = decoder.decodeMethodName();

if (methodName == null) {
return new byte[0];
} else {
if (methodName.equals("testAvmGetStorage")) {
byte[] key = decoder.decodeOneByteArray();
return testAvmGetStorage(key);
} else if (methodName.equals("testAvmPutStorage")) {
byte[] key = decoder.decodeOneByteArray();
byte[] value = decoder.decodeOneByteArray();
testAvmPutStorage(key, value);
return new byte[0];
} else if (methodName.equals("testAvmPutStorageNullValue")) {
byte[] key = decoder.decodeOneByteArray();
testAvmPutStorage(key, null);
return new byte[0];
}
else {
return new byte[0];
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,25 @@ public Address avm_getBlockCoinbase() {
return new Address(blockCoinbase.toBytes());
}

@Override
public void avm_putStorage(ByteArray key, ByteArray value) {
Objects.requireNonNull(address);
if (value == null) {
kernel.removeStorage(address, key.getUnderlying());
} else {
kernel.putStorage(address, key.getUnderlying(), value.getUnderlying());
}
}

@Override
public ByteArray avm_getStorage(ByteArray key) {
Objects.requireNonNull(key);
byte[] data = this.kernel.getStorage(address, key.getUnderlying());
return (null != data)
? new ByteArray(data)
: null;
}

@Override
public BigInteger avm_getBalance(Address address) {
Objects.requireNonNull(address);
Expand Down
Loading

0 comments on commit 16537d4

Please sign in to comment.