From e9396faed1fd7dfd52ae518a952e1ba8b742fed8 Mon Sep 17 00:00:00 2001 From: Alexandra Roatis Date: Wed, 3 Apr 2019 10:45:12 -0400 Subject: [PATCH] feature: adding the graphDatabase for storing the object graph bug fix: closing the contractIndexDatabase --- .../zero/impl/db/AbstractContractDetails.java | 2 + .../zero/impl/db/AionContractDetailsImpl.java | 299 +++++++++++------- .../aion/zero/impl/db/AionRepositoryImpl.java | 11 + .../impl/db/ContractDetailsCacheImpl.java | 62 ++-- modMcf/src/org/aion/mcf/config/CfgDb.java | 1 + .../org/aion/mcf/db/AbstractRepository.java | 20 +- .../src/org/aion/mcf/db/DetailsDataStore.java | 12 +- modMcf/src/org/aion/mcf/ds/XorDataSource.java | 5 +- 8 files changed, 279 insertions(+), 133 deletions(-) diff --git a/modAionImpl/src/org/aion/zero/impl/db/AbstractContractDetails.java b/modAionImpl/src/org/aion/zero/impl/db/AbstractContractDetails.java index bb89e0db42..51754d2b0d 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/AbstractContractDetails.java +++ b/modAionImpl/src/org/aion/zero/impl/db/AbstractContractDetails.java @@ -23,6 +23,8 @@ public abstract class AbstractContractDetails implements ContractDetails { protected int detailsInMemoryStorageLimit; private Map codes = new HashMap<>(); + // classes extending this rely on this value starting off as null + protected byte[] objectGraph = null; // using the default transaction type to specify undefined VM protected byte vmType = TransactionTypes.DEFAULT; diff --git a/modAionImpl/src/org/aion/zero/impl/db/AionContractDetailsImpl.java b/modAionImpl/src/org/aion/zero/impl/db/AionContractDetailsImpl.java index 117fade221..124f4d8264 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/AionContractDetailsImpl.java +++ b/modAionImpl/src/org/aion/zero/impl/db/AionContractDetailsImpl.java @@ -1,16 +1,18 @@ package org.aion.zero.impl.db; -import static org.aion.crypto.HashUtil.EMPTY_LIST_HASH; +import static org.aion.crypto.HashUtil.EMPTY_DATA_HASH; import static org.aion.crypto.HashUtil.EMPTY_TRIE_HASH; import static org.aion.crypto.HashUtil.h256; import static org.aion.types.ByteArrayWrapper.wrap; import static org.aion.util.bytes.ByteUtil.EMPTY_BYTE_ARRAY; +import com.google.common.annotations.VisibleForTesting; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import org.aion.interfaces.db.ByteArrayKeyValueStore; import org.aion.interfaces.db.ContractDetails; import org.aion.mcf.ds.XorDataSource; @@ -24,8 +26,10 @@ import org.aion.types.ByteArrayWrapper; public class AionContractDetailsImpl extends AbstractContractDetails { + private static final int HASH_SIZE = 32; private ByteArrayKeyValueStore dataSource; + private ByteArrayKeyValueStore objectGraphSource = null; private byte[] rlpEncoded; @@ -35,8 +39,10 @@ public class AionContractDetailsImpl extends AbstractContractDetails { public boolean externalStorage; private ByteArrayKeyValueStore externalStorageDataSource; + private ByteArrayKeyValueStore contractObjectGraphSource = null; - protected byte[] objectGraphHash = EMPTY_LIST_HASH; + private byte[] objectGraphHash = EMPTY_DATA_HASH; + private byte[] concatenatedStorageHash = EMPTY_DATA_HASH; public AionContractDetailsImpl() {} @@ -113,26 +119,38 @@ public ByteArrayWrapper get(ByteArrayWrapper key) { } public byte getVmType() { - // TODO: is this sufficient? return vmType; } @Override public byte[] getObjectGraph() { - // TODO - return EMPTY_BYTE_ARRAY; + if (objectGraph == null) { + // the object graph was not stored yet + if (java.util.Arrays.equals(objectGraphHash, EMPTY_DATA_HASH)) { + return EMPTY_BYTE_ARRAY; + } else { + // note: the enforced use of optional is rather cumbersome here + Optional dbVal = getContractObjectGraphSource().get(objectGraphHash); + objectGraph = dbVal.isPresent() ? dbVal.get() : null; + } + } + + return objectGraph == null ? EMPTY_BYTE_ARRAY : objectGraph; } @Override public void setObjectGraph(byte[] graph) { - // TODO + Objects.requireNonNull(graph); + + this.objectGraph = graph; + this.objectGraphHash = h256(objectGraph); + this.setDirty(true); + this.rlpEncoded = null; } - @Override - public void setObjectGraphSource(ByteArrayKeyValueStore objectGraphSource) { - // TODO - } + // TODO: use until the AVM impl is added + private final boolean enableObjectGraph = false; /** * Returns the storage hash. @@ -141,19 +159,26 @@ public void setObjectGraphSource(ByteArrayKeyValueStore objectGraphSource) { */ @Override public byte[] getStorageHash() { - if (vmType == TransactionTypes.AVM_CREATE_CODE && false) { // check disabled - // todo: store the result - byte[] a = storageTrie.getRootHash(); - byte[] b = objectGraphHash; - byte[] c = new byte[a.length + b.length]; - System.arraycopy(a, 0, c, 0, a.length); - System.arraycopy(b, 0, c, a.length, b.length); - return h256(c); + if (vmType == TransactionTypes.AVM_CREATE_CODE && enableObjectGraph) { + return computeAvmStorageHash(); } else { return storageTrie.getRootHash(); } } + /** + * Computes the concatenated storage hash used by the AVM by concatenating 1: the storage root + * and 2: the object graph and hashing the result. + */ + private byte[] computeAvmStorageHash() { + byte[] storageRoot = storageTrie.getRootHash(); + byte[] graphHash = getObjectGraph(); + byte[] concatenated = new byte[storageRoot.length + graphHash.length]; + System.arraycopy(storageRoot, 0, concatenated, 0, storageRoot.length); + System.arraycopy(graphHash, 0, concatenated, storageRoot.length, graphHash.length); + return h256(concatenated); + } + /** * Decodes an AionContractDetailsImpl object from the RLP encoding rlpCode. * @@ -179,7 +204,7 @@ public void decode(byte[] rlpCode, boolean fastCheck) { if (rlpList.size() == 5) { // compatible with old encoding - decodeFvmPreFork(rlpList, fastCheck); + decodeEncodingWithoutVmType(rlpList, fastCheck); // only FVM contracts used the old encoding vmType = TransactionTypes.FVM_CREATE_CODE; @@ -189,7 +214,7 @@ public void decode(byte[] rlpCode, boolean fastCheck) { getEncoded(); } else { // compatible with new encoding - decodePostFork(rlpList, fastCheck); + decodeEncodingWithVmType(rlpList, fastCheck); // compatible with new encoding that differentiates encoding based on VM used this.rlpEncoded = rlpCode; @@ -198,11 +223,11 @@ public void decode(byte[] rlpCode, boolean fastCheck) { /** * Decodes the old version of encoding which was a list of 5 elements, specifically:
- * { address, isExternalStorage, storageRoot, storage, code } + * { 0:address, 1:isExternalStorage, 2:storageRoot, 3:storage, 4:code } * *

Only FVM contracts used this encoding on the mainnet and mastery networks. */ - public void decodeFvmPreFork(RLPList rlpList, boolean fastCheck) { + public void decodeEncodingWithoutVmType(RLPList rlpList, boolean fastCheck) { RLPItem isExternalStorage = (RLPItem) rlpList.get(1); RLPItem storage = (RLPItem) rlpList.get(3); this.externalStorage = isExternalStorage.getRLPData().length > 0; @@ -249,15 +274,10 @@ public void decodeFvmPreFork(RLPList rlpList, boolean fastCheck) { } /** - * Decodes the new version of encoding. - * - *

The encoding is a list of 6 elements for FVM contracts:
- * { address, vmType, isExternalStorage, storageRoot, storage, code } - * - *

The encoding is a list of 7 elements for AVM contracts:
- * { address, vmType, isExternalStorage, storageRoot, storage, code, objectGraphHash } + * Decodes the new version of encoding which is a list of 6 elements, specifically:
+ * { 0:address, 1:vmType, 2:isExternalStorage, 3:storageRoot, 4:storage, 5:code } */ - public void decodePostFork(RLPList rlpList, boolean fastCheck) { + public void decodeEncodingWithVmType(RLPList rlpList, boolean fastCheck) { RLPItem isExternalStorage = (RLPItem) rlpList.get(2); RLPItem storage = (RLPItem) rlpList.get(4); this.externalStorage = isExternalStorage.getRLPData().length > 0; @@ -281,7 +301,7 @@ public void decodePostFork(RLPList rlpList, boolean fastCheck) { this.address = Address.wrap(address.getRLPData()); } - if (vm == null || vm.getRLPData() == null || vm.getRLPData().length != 1) { + if (vm == null || vm.getRLPData() == null) { throw new IllegalArgumentException("rlp decode error: invalid vm code"); } else { this.vmType = vm.getRLPData()[0]; @@ -295,9 +315,33 @@ public void decodePostFork(RLPList rlpList, boolean fastCheck) { setCode(code.getRLPData()); } + // different values based on the VM used + byte[] storageRootHash; + if (vmType == TransactionTypes.AVM_CREATE_CODE && enableObjectGraph) { + // points to the storage hash and the object graph hash + concatenatedStorageHash = storageRoot.getRLPData(); + + RLPList data = + RLP.decode2(getContractObjectGraphSource().get(concatenatedStorageHash).get()); + if (!(data.get(0) instanceof RLPList)) { + throw new IllegalArgumentException( + "rlp decode error: invalid concatenated storage for AVM"); + } + RLPList pair = (RLPList) data.get(0); + if (pair.size() != 2) { + throw new IllegalArgumentException( + "rlp decode error: invalid concatenated storage for AVM"); + } + + storageRootHash = pair.get(0).getRLPData(); + objectGraphHash = pair.get(1).getRLPData(); + } else { + storageRootHash = storageRoot.getRLPData(); + } + // load/deserialize storage trie if (externalStorage) { - storageTrie = new SecureTrie(getExternalStorageDataSource(), storageRoot.getRLPData()); + storageTrie = new SecureTrie(getExternalStorageDataSource(), storageRootHash); } else { storageTrie.deserialize(storage.getRLPData()); } @@ -308,23 +352,14 @@ public void decodePostFork(RLPList rlpList, boolean fastCheck) { externalStorage = true; storageTrie.getCache().setDB(getExternalStorageDataSource()); } - - // get object graph hash only for AVM - if (vmType == TransactionTypes.AVM_CREATE_CODE) { - RLPItem graphHash = (RLPItem) rlpList.get(6); - if (graphHash == null - || graphHash.getRLPData() == null - || graphHash.getRLPData().length != 32) { - throw new IllegalArgumentException("rlp decode error: invalid object graph hash"); - } else { - this.objectGraphHash = graphHash.getRLPData(); - } - } } /** * Returns an rlp encoding of this AionContractDetailsImpl object. * + *

The encoding is a list of 6 elements:
+ * { 0:address, 1:vmType, 2:isExternalStorage, 3:storageRoot, 4:storage, 5:code } + * * @return an rlp encoding of this. */ @Override @@ -332,6 +367,7 @@ public byte[] getEncoded() { if (rlpEncoded == null) { byte[] rlpAddress = RLP.encodeElement(address.toBytes()); + byte[] rlpVmType = RLP.encodeByte(vmType); byte[] rlpIsExternalStorage = RLP.encodeByte((byte) (externalStorage ? 1 : 0)); byte[] rlpStorageRoot = RLP.encodeElement( @@ -347,69 +383,17 @@ public byte[] getEncoded() { this.rlpEncoded = RLP.encodeList( - rlpAddress, rlpIsExternalStorage, rlpStorageRoot, rlpStorage, rlpCode); + rlpAddress, + rlpVmType, + rlpIsExternalStorage, + rlpStorageRoot, + rlpStorage, + rlpCode); } return rlpEncoded; } - /** - * Returns an rlp encoding of this AionContractDetailsImpl object. - * - *

The encoding is a list of 6 elements for FVM contracts:
- * { address, vmType, isExternalStorage, storageRoot, storage, code } - * - *

The encoding is a list of 7 elements for AVM contracts:
- * { address, vmType, isExternalStorage, storageRoot, storage, code, objectGraphHash } - * - * @return an rlp encoding of this object - */ - public byte[] getEncodedPostFork() { - if (rlpEncoded == null) { - - byte[] rlpAddress = RLP.encodeElement(address.toBytes()); - byte[] rlpVmType = RLP.encodeByte(vmType); - byte[] rlpIsExternalStorage = RLP.encodeByte((byte) (externalStorage ? 1 : 0)); - byte[] rlpStorageRoot = - RLP.encodeElement( - externalStorage ? storageTrie.getRootHash() : EMPTY_BYTE_ARRAY); - byte[] rlpStorage = - RLP.encodeElement(externalStorage ? EMPTY_BYTE_ARRAY : storageTrie.serialize()); - byte[][] codes = new byte[getCodes().size()][]; - int i = 0; - for (byte[] bytes : this.getCodes().values()) { - codes[i++] = RLP.encodeElement(bytes); - } - byte[] rlpCode = RLP.encodeList(codes); - - if (vmType == TransactionTypes.FVM_CREATE_CODE) { - this.rlpEncoded = - RLP.encodeList( - rlpAddress, - rlpVmType, - rlpIsExternalStorage, - rlpStorageRoot, - rlpStorage, - rlpCode); - } else if (vmType == TransactionTypes.AVM_CREATE_CODE) { - // storing also the object graph for the AVM - byte[] rlpObjectGraphHash = RLP.encodeElement(objectGraphHash); - this.rlpEncoded = - RLP.encodeList( - rlpAddress, - rlpVmType, - rlpIsExternalStorage, - rlpStorageRoot, - rlpStorage, - rlpCode, - rlpObjectGraphHash); - } - } - - // the encoding will be null when the VM code is not set to one of the two allowed VMs - return rlpEncoded; - } - /** * Get the address associated with this AionContractDetailsImpl. * @@ -437,6 +421,20 @@ public void setAddress(Address address) { /** Syncs the storage trie. */ @Override public void syncStorage() { + if (vmType == TransactionTypes.AVM_CREATE_CODE && enableObjectGraph) { + if (objectGraph == null || Arrays.equals(objectGraphHash, EMPTY_DATA_HASH)) { + throw new IllegalStateException( + "The AVM object graph must be set before pushing data to disk."); + } + getContractObjectGraphSource().put(objectGraphHash, objectGraph); + getContractObjectGraphSource() + .put( + computeAvmStorageHash(), + RLP.encodeList( + RLP.encodeElement(storageTrie.getRootHash()), + RLP.encodeElement(getObjectGraph()))); + } + if (externalStorage) { storageTrie.sync(); } @@ -451,6 +449,11 @@ public void setDataSource(ByteArrayKeyValueStore dataSource) { this.dataSource = dataSource; } + @Override + public void setObjectGraphSource(ByteArrayKeyValueStore objectGraphSource) { + this.objectGraphSource = objectGraphSource; + } + /** * Returns the external storage data source. * @@ -465,12 +468,31 @@ private ByteArrayKeyValueStore getExternalStorageDataSource() { return externalStorageDataSource; } + /** + * Returns the data source specific to the current contract. + * + * @return the data source specific to the current contract. + */ + private ByteArrayKeyValueStore getContractObjectGraphSource() { + if (contractObjectGraphSource == null) { + contractObjectGraphSource = + new XorDataSource( + objectGraphSource, + h256(("details-graph/" + address.toString()).getBytes())); + } + return contractObjectGraphSource; + } + /** * Sets the external storage data source to dataSource. * * @param dataSource The new data source. + * @implNote The tests are taking a shortcut here in bypassing the XorDataSource created by + * {@link #getExternalStorageDataSource()}. Do not use this method in production. */ - public void setExternalStorageDataSource(ByteArrayKeyValueStore dataSource) { + @VisibleForTesting + void setExternalStorageDataSource(ByteArrayKeyValueStore dataSource) { + // TODO: regarding the node above: the tests should be updated and the method removed this.externalStorageDataSource = dataSource; this.externalStorage = true; this.storageTrie = new SecureTrie(getExternalStorageDataSource()); @@ -486,14 +508,46 @@ public void setExternalStorageDataSource(ByteArrayKeyValueStore dataSource) { @Override public ContractDetails getSnapshotTo(byte[] hash) { - SecureTrie snapStorage = - wrap(hash).equals(wrap(EMPTY_TRIE_HASH)) - ? new SecureTrie(storageTrie.getCache(), "".getBytes()) - : new SecureTrie(storageTrie.getCache(), hash); - snapStorage.withPruningEnabled(storageTrie.isPruningEnabled()); + SecureTrie snapStorage; + AionContractDetailsImpl details; + if (vmType == TransactionTypes.AVM_CREATE_CODE && enableObjectGraph) { + // get the concatenated storage hash from storage + RLPList data = RLP.decode2(getContractObjectGraphSource().get(hash).get()); + if (!(data.get(0) instanceof RLPList)) { + throw new IllegalArgumentException( + "rlp decode error: invalid concatenated storage for AVM"); + } + RLPList pair = (RLPList) data.get(0); + if (pair.size() != 2) { + throw new IllegalArgumentException( + "rlp decode error: invalid concatenated storage for AVM"); + } + + snapStorage = + wrap(hash).equals(wrap(EMPTY_TRIE_HASH)) + ? new SecureTrie(storageTrie.getCache(), "".getBytes()) + : new SecureTrie(storageTrie.getCache(), pair.get(0).getRLPData()); + snapStorage.withPruningEnabled(storageTrie.isPruningEnabled()); - AionContractDetailsImpl details = - new AionContractDetailsImpl(this.address, snapStorage, getCodes()); + details = new AionContractDetailsImpl(this.address, snapStorage, getCodes()); + + // object graph information + details.objectGraphSource = this.objectGraphSource; + details.objectGraphHash = pair.get(1).getRLPData(); + details.concatenatedStorageHash = hash; + } else { + snapStorage = + wrap(hash).equals(wrap(EMPTY_TRIE_HASH)) + ? new SecureTrie(storageTrie.getCache(), "".getBytes()) + : new SecureTrie(storageTrie.getCache(), hash); + snapStorage.withPruningEnabled(storageTrie.isPruningEnabled()); + details = new AionContractDetailsImpl(this.address, snapStorage, getCodes()); + } + + // vm information + details.vmType = this.vmType; + + // storage information details.externalStorage = this.externalStorage; details.externalStorageDataSource = this.externalStorageDataSource; details.dataSource = dataSource; @@ -518,9 +572,34 @@ public ContractDetails getSnapshotTo(byte[] hash) { @Override public AionContractDetailsImpl copy() { AionContractDetailsImpl aionContractDetailsCopy = new AionContractDetailsImpl(); + + // vm information + aionContractDetailsCopy.vmType = this.vmType; + + // storage information aionContractDetailsCopy.dataSource = this.dataSource; aionContractDetailsCopy.externalStorageDataSource = this.externalStorageDataSource; aionContractDetailsCopy.externalStorage = this.externalStorage; + + // object graph information + aionContractDetailsCopy.objectGraphSource = this.objectGraphSource; + aionContractDetailsCopy.contractObjectGraphSource = this.contractObjectGraphSource; + aionContractDetailsCopy.objectGraph = + objectGraph == null + ? null + : Arrays.copyOf(this.objectGraph, this.objectGraph.length); + aionContractDetailsCopy.objectGraphHash = + Arrays.equals(objectGraphHash, EMPTY_DATA_HASH) + ? EMPTY_DATA_HASH + : Arrays.copyOf(this.objectGraphHash, this.objectGraphHash.length); + + // storage hash used by AVM + aionContractDetailsCopy.concatenatedStorageHash = + Arrays.equals(concatenatedStorageHash, EMPTY_DATA_HASH) + ? EMPTY_DATA_HASH + : Arrays.copyOf( + this.concatenatedStorageHash, this.concatenatedStorageHash.length); + aionContractDetailsCopy.prune = this.prune; aionContractDetailsCopy.detailsInMemoryStorageLimit = this.detailsInMemoryStorageLimit; aionContractDetailsCopy.setCodes(getDeepCopyOfCodes()); diff --git a/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java b/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java index e3fabf8a31..0d801e122e 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java +++ b/modAionImpl/src/org/aion/zero/impl/db/AionRepositoryImpl.java @@ -660,6 +660,17 @@ public void close() { LOGGEN.error("Exception occurred while closing the details data source.", e); } + try { + if (contractIndexDatabase != null) { + contractIndexDatabase.close(); + LOGGEN.info("contractIndexDatabase store closed."); + contractIndexDatabase = null; + } + } catch (Exception e) { + LOGGEN.error( + "Exception occurred while closing the pendingTxCacheDatabase store.", e); + } + try { if (stateDatabase != null) { stateDatabase.close(); diff --git a/modAionImpl/src/org/aion/zero/impl/db/ContractDetailsCacheImpl.java b/modAionImpl/src/org/aion/zero/impl/db/ContractDetailsCacheImpl.java index cc8af0b7ea..3cb7691954 100644 --- a/modAionImpl/src/org/aion/zero/impl/db/ContractDetailsCacheImpl.java +++ b/modAionImpl/src/org/aion/zero/impl/db/ContractDetailsCacheImpl.java @@ -32,6 +32,10 @@ public ContractDetailsCacheImpl(ContractDetails origContract) { public static ContractDetailsCacheImpl copy(ContractDetailsCacheImpl cache) { ContractDetailsCacheImpl copy = new ContractDetailsCacheImpl(cache.origContract); copy.setCodes(new HashMap<>(cache.getCodes())); + copy.vmType = cache.vmType; + if (cache.objectGraph != null) { + copy.objectGraph = Arrays.copyOf(cache.objectGraph, cache.objectGraph.length); + } copy.storage = new HashMap<>(cache.storage); copy.setDirty(cache.isDirty()); copy.setDeleted(cache.isDeleted()); @@ -106,15 +110,22 @@ public byte getVmType() { @Override public byte[] getObjectGraph() { - return new byte[0]; + if (objectGraph == null) { + if (origContract == null) { + return null; + } else { + objectGraph = origContract.getObjectGraph(); + } + } + return objectGraph; } @Override - public void setObjectGraph(byte[] graph) {} + public void setObjectGraph(byte[] graph) { + Objects.requireNonNull(graph); - @Override - public void setObjectGraphSource(ByteArrayKeyValueStore objectGraphSource) { - // TODO + objectGraph = graph; + setDirty(true); } /** @@ -124,7 +135,6 @@ public void setObjectGraphSource(ByteArrayKeyValueStore objectGraphSource) { */ @Override public byte[] getStorageHash() { - // TODO: apply changes resulting from AVM switch return origContract == null ? null : origContract.getStorageHash(); } @@ -177,9 +187,10 @@ public void syncStorage() { } /** - * Puts all of the key-value pairs in this ContractDetailsCacheImple into the original contract - * injected into this class' constructor, transfers over any code and sets the original contract - * to dirty only if it already is dirty or if this class is dirty, otherwise sets it as clean. + * Puts all of the key-value pairs and object graph from this ContractDetailsCacheImpl into the + * original contract injected into this class' constructor, transfers over any code and sets the + * original contract to dirty only if it already is dirty or if this class is dirty, otherwise + * sets it as clean. */ public void commit() { @@ -187,6 +198,12 @@ public void commit() { return; } + // passing on the object graph + if (objectGraph != null) { + origContract.setObjectGraph(objectGraph); + } + + // passing on the storage keys for (ByteArrayWrapper key : storage.keySet()) { ByteArrayWrapper value = storage.get(key); if (value != null) { @@ -216,6 +233,12 @@ public void setDataSource(ByteArrayKeyValueStore dataSource) { throw new UnsupportedOperationException("Can't set datasource in cache implementation."); } + /** This method is not supported. */ + @Override + public void setObjectGraphSource(ByteArrayKeyValueStore objectGraphSource) { + throw new UnsupportedOperationException("Can't set datasource in cache implementation."); + } + /** * Returns a sufficiently deep copy of this contract details object. * @@ -244,15 +267,18 @@ public ContractDetailsCacheImpl copy() { ContractDetails originalContractCopy = (this.origContract == null) ? null : this.origContract.copy(); - ContractDetailsCacheImpl contractDetailsCacheCopy = - new ContractDetailsCacheImpl(originalContractCopy); - contractDetailsCacheCopy.storage = getDeepCopyOfStorage(); - contractDetailsCacheCopy.prune = this.prune; - contractDetailsCacheCopy.detailsInMemoryStorageLimit = this.detailsInMemoryStorageLimit; - contractDetailsCacheCopy.setCodes(getDeepCopyOfCodes()); - contractDetailsCacheCopy.setDirty(this.isDirty()); - contractDetailsCacheCopy.setDeleted(this.isDeleted()); - return contractDetailsCacheCopy; + ContractDetailsCacheImpl copy = new ContractDetailsCacheImpl(originalContractCopy); + copy.vmType = this.vmType; + if (this.objectGraph != null) { + copy.objectGraph = Arrays.copyOf(this.objectGraph, this.objectGraph.length); + } + copy.storage = getDeepCopyOfStorage(); + copy.prune = this.prune; + copy.detailsInMemoryStorageLimit = this.detailsInMemoryStorageLimit; + copy.setCodes(getDeepCopyOfCodes()); + copy.setDirty(this.isDirty()); + copy.setDeleted(this.isDeleted()); + return copy; } private Map getDeepCopyOfCodes() { diff --git a/modMcf/src/org/aion/mcf/config/CfgDb.java b/modMcf/src/org/aion/mcf/config/CfgDb.java index 4ab4dfd41e..d2eac7358d 100644 --- a/modMcf/src/org/aion/mcf/config/CfgDb.java +++ b/modMcf/src/org/aion/mcf/config/CfgDb.java @@ -29,6 +29,7 @@ public static class Names { public static final String CONTRACT_INDEX = "contractIndex"; public static final String DETAILS = "details"; public static final String STORAGE = "storage"; + public static final String GRAPH = "graph"; public static final String STATE = "state"; public static final String STATE_ARCHIVE = "stateArchive"; diff --git a/modMcf/src/org/aion/mcf/db/AbstractRepository.java b/modMcf/src/org/aion/mcf/db/AbstractRepository.java index 9a25949dec..dc99af6827 100644 --- a/modMcf/src/org/aion/mcf/db/AbstractRepository.java +++ b/modMcf/src/org/aion/mcf/db/AbstractRepository.java @@ -52,6 +52,7 @@ public abstract class AbstractRepository< protected static final String CONTRACT_INDEX_DB = Names.CONTRACT_INDEX; protected static final String DETAILS_DB = Names.DETAILS; protected static final String STORAGE_DB = Names.STORAGE; + protected static final String GRAPH_DB = Names.GRAPH; protected static final String STATE_DB = Names.STATE; protected static final String STATE_ARCHIVE_DB = Names.STATE_ARCHIVE; protected static final String PENDING_TX_POOL_DB = Names.TX_POOL; @@ -70,6 +71,7 @@ public abstract class AbstractRepository< protected ByteArrayKeyValueDatabase contractIndexDatabase; protected ByteArrayKeyValueDatabase detailsDatabase; protected ByteArrayKeyValueDatabase storageDatabase; + protected ByteArrayKeyValueDatabase graphDatabase; protected ByteArrayKeyValueDatabase indexDatabase; protected ByteArrayKeyValueDatabase blockDatabase; protected ByteArrayKeyValueDatabase stateDatabase; @@ -186,7 +188,8 @@ protected void initializeDatabasesAndCaches() throws InvalidFilePathException { } databaseGroup.add(transactionDatabase); - // getting details specific properties + // getting contract index specific properties + // this db will be used only for fast sync sharedProps = cfg.getDatabaseConfig(CONTRACT_INDEX_DB); sharedProps.setProperty(Props.ENABLE_LOCKING, "false"); sharedProps.setProperty(Props.DB_PATH, cfg.getDbPath()); @@ -219,6 +222,17 @@ protected void initializeDatabasesAndCaches() throws InvalidFilePathException { } databaseGroup.add(storageDatabase); + // getting graph specific properties + sharedProps = cfg.getDatabaseConfig(GRAPH_DB); + sharedProps.setProperty(Props.ENABLE_LOCKING, "false"); + sharedProps.setProperty(Props.DB_PATH, cfg.getDbPath()); + sharedProps.setProperty(Props.DB_NAME, GRAPH_DB); + this.graphDatabase = connectAndOpen(sharedProps, LOG); + if (graphDatabase == null || graphDatabase.isClosed()) { + throw newException(GRAPH_DB, sharedProps); + } + databaseGroup.add(graphDatabase); + // getting index specific properties sharedProps = cfg.getDatabaseConfig(INDEX_DB); sharedProps.setProperty(Props.ENABLE_LOCKING, "false"); @@ -268,7 +282,9 @@ protected void initializeDatabasesAndCaches() throws InvalidFilePathException { databaseGroup.add(pendingTxCacheDatabase); // Setup the cache for transaction data source. - this.detailsDS = new DetailsDataStore<>(detailsDatabase, storageDatabase, this.cfg); + this.detailsDS = + new DetailsDataStore<>( + detailsDatabase, storageDatabase, graphDatabase, this.cfg); // pruning config pruneEnabled = this.cfg.getPruneConfig().isEnabled(); diff --git a/modMcf/src/org/aion/mcf/db/DetailsDataStore.java b/modMcf/src/org/aion/mcf/db/DetailsDataStore.java index e89a39ad62..1e813de0f7 100644 --- a/modMcf/src/org/aion/mcf/db/DetailsDataStore.java +++ b/modMcf/src/org/aion/mcf/db/DetailsDataStore.java @@ -25,6 +25,7 @@ public class DetailsDataStore< private ByteArrayKeyValueDatabase detailsSrc; private ByteArrayKeyValueDatabase storageSrc; + private ByteArrayKeyValueDatabase graphSrc; private Set removes = new HashSet<>(); public DetailsDataStore() {} @@ -32,16 +33,20 @@ public DetailsDataStore() {} public DetailsDataStore( ByteArrayKeyValueDatabase detailsCache, ByteArrayKeyValueDatabase storageCache, + ByteArrayKeyValueDatabase graphCache, RepositoryConfig repoConfig) { this.repoConfig = repoConfig; - withDb(detailsCache, storageCache); + withDb(detailsCache, storageCache, graphCache); } public DetailsDataStore withDb( - ByteArrayKeyValueDatabase detailsSrc, ByteArrayKeyValueDatabase storageSrc) { + ByteArrayKeyValueDatabase detailsSrc, + ByteArrayKeyValueDatabase storageSrc, + ByteArrayKeyValueDatabase graphSrc) { this.detailsSrc = detailsSrc; this.storageSrc = storageSrc; + this.graphSrc = graphSrc; this.storageDSPrune = new JournalPruneDataSource(storageSrc); return this; } @@ -69,6 +74,7 @@ public synchronized ContractDetails get(byte[] key) { // Found something from cache or database, return it by decoding it. ContractDetails detailsImpl = repoConfig.contractDetailsImpl(); detailsImpl.setDataSource(storageDSPrune); + detailsImpl.setObjectGraphSource(graphSrc); detailsImpl.decode(rawDetails.get()); // We can safely get as we checked // if it is present. @@ -141,6 +147,7 @@ public void syncLargeStorage() { // Decode the details. ContractDetails detailsImpl = repoConfig.contractDetailsImpl(); detailsImpl.setDataSource(storageDSPrune); + detailsImpl.setObjectGraphSource(graphSrc); detailsImpl.decode(rawDetails.get(), true); // We can safely get as we checked if it is present. @@ -161,6 +168,7 @@ public synchronized void close() { try { detailsSrc.close(); storageSrc.close(); + graphSrc.close(); } catch (Exception e) { throw new RuntimeException("error closing db"); } diff --git a/modMcf/src/org/aion/mcf/ds/XorDataSource.java b/modMcf/src/org/aion/mcf/ds/XorDataSource.java index 17366d3a89..5e67a675b9 100644 --- a/modMcf/src/org/aion/mcf/ds/XorDataSource.java +++ b/modMcf/src/org/aion/mcf/ds/XorDataSource.java @@ -92,7 +92,10 @@ public void close() throws Exception { @Override public void deleteBatch(Collection keys) { - // TODO Auto-generated method stub + // NOTE: implementing this method will cause a break in consensus + // due to the fact that the serialized storage must have old roots + // to enable reverting to a different state for the account in case of a fork + // TODO remove this black magic :P } @Override