diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AbstractWorldUpdater.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AbstractWorldUpdater.java
index 65cc473d5d..53776729ca 100644
--- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AbstractWorldUpdater.java
+++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AbstractWorldUpdater.java
@@ -234,6 +234,11 @@ public Address getAddress() {
return address;
}
+ @Override
+ public Hash getAddressHash() {
+ return Hash.hash(getAddress());
+ }
+
@Override
public long getNonce() {
return nonce;
diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java
index 8fd80eac9b..e8cd5ab2e6 100644
--- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java
+++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java
@@ -12,146 +12,23 @@
*/
package tech.pegasys.pantheon.ethereum.core;
-import tech.pegasys.pantheon.util.bytes.Bytes32;
-import tech.pegasys.pantheon.util.bytes.BytesValue;
-import tech.pegasys.pantheon.util.uint.UInt256;
-
-import java.util.NavigableMap;
-
/**
* A world state account.
*
- *
An account has four properties associated with it:
- *
- *
- * - Nonce: the number of transactions sent (and committed) from the account.
- *
- Balance: the amount of Wei (10^18 Ether) owned by the account.
- *
- Storage: a key-value mapping between 256-bit integer values. Note that formally, a
- * key always has an associated value in that mapping, with 0 being the default (and thus, in
- * practice, only non-zero mappings are stored and setting a key to the value 0 is akin to
- * "removing" that key).
- *
- Code: arbitrary-length sequence of bytes that corresponds to EVM bytecode.
- *
- Version: the version of the EVM bytecode.
- *
+ * In addition to holding the account state, a full account provides access to the account
+ * address, which is not stored directly in the world state trie (account's are indexed by the hash
+ * of their address).
*/
-public interface Account {
+public interface Account extends AccountState {
long DEFAULT_NONCE = 0L;
Wei DEFAULT_BALANCE = Wei.ZERO;
int DEFAULT_VERSION = 0;
- /**
- * The Keccak-256 hash of the account address.
- *
- *
Note that the world state does not store account addresses, only their hashes, and so this
- * is how account are truly identified. So while accounts can be queried by their address (through
- * first computing their hash), one cannot infer the address from an account simply from the world
- * state.
- *
- * @return the Keccak-256 hash of the account address.
- */
- default Hash getAddressHash() {
- return Hash.hash(getAddress());
- }
-
/**
* The account address.
*
* @return the account address
*/
Address getAddress();
-
- /**
- * The account nonce, that is the number of transactions sent from that account.
- *
- * @return the account nonce.
- */
- long getNonce();
-
- /**
- * The available balance of that account.
- *
- * @return the balance, in Wei, of the account.
- */
- Wei getBalance();
-
- /**
- * The EVM bytecode associated with this account.
- *
- * @return the account code (which can be empty).
- */
- BytesValue getCode();
-
- /**
- * The hash of the EVM bytecode associated with this account.
- *
- * @return the hash of the account code (which may be {@link Hash#EMPTY}).
- */
- Hash getCodeHash();
-
- /**
- * Whether the account has (non empty) EVM bytecode associated to it.
- *
- *
This is functionally equivalent to {@code !code().isEmpty()}, though could be implemented
- * more efficiently.
- *
- * @return Whether the account has EVM bytecode associated to it.
- */
- default boolean hasCode() {
- return !getCode().isEmpty();
- }
-
- /**
- * The version of the EVM bytecode associated with this account.
- *
- * @return the version of the account code. Default is zero.
- */
- int getVersion();
-
- /**
- * Retrieves a value in the account storage given its key.
- *
- * @param key the key to retrieve in the account storage.
- * @return the value associated to {@code key} in the account storage. Note that this is never
- * {@code null}, but 0 acts as a default value.
- */
- UInt256 getStorageValue(UInt256 key);
-
- /**
- * Retrieves the original value from before the current transaction in the account storage given
- * its key.
- *
- * @param key the key to retrieve in the account storage.
- * @return the original value associated to {@code key} in the account storage. Note that this is
- * never {@code null}, but 0 acts as a default value.
- */
- UInt256 getOriginalStorageValue(UInt256 key);
-
- /**
- * Whether the account is "empty".
- *
- *
An account is defined as empty if:
- *
- *
- * - its nonce is 0;
- *
- its balance is 0;
- *
- its associated code is empty (the zero-length byte sequence).
- *
- *
- * @return {@code true} if the account is empty with regard to the definition above, {@code false}
- * otherwise.
- */
- default boolean isEmpty() {
- return getNonce() == 0 && getBalance().isZero() && !hasCode();
- }
-
- /**
- * Retrieve up to {@code limit} storage entries beginning from the first entry with hash equal to
- * or greater than {@code startKeyHash}.
- *
- * @param startKeyHash the first key hash to return.
- * @param limit the maximum number of entries to return.
- * @return the requested storage entries as a map of key hash to entry.
- */
- NavigableMap storageEntriesFrom(Bytes32 startKeyHash, int limit);
}
diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AccountState.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AccountState.java
new file mode 100644
index 0000000000..78c90e57e9
--- /dev/null
+++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/AccountState.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2018 ConsenSys AG.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package tech.pegasys.pantheon.ethereum.core;
+
+import tech.pegasys.pantheon.util.bytes.Bytes32;
+import tech.pegasys.pantheon.util.bytes.BytesValue;
+import tech.pegasys.pantheon.util.uint.UInt256;
+
+import java.util.NavigableMap;
+
+/**
+ * An account state.
+ *
+ * An account has four properties associated with it:
+ *
+ *
+ * - Nonce: the number of transactions sent (and committed) from the account.
+ *
- Balance: the amount of Wei (10^18 Ether) owned by the account.
+ *
- Storage: a key-value mapping between 256-bit integer values. Note that formally, a
+ * key always has an associated value in that mapping, with 0 being the default (and thus, in
+ * practice, only non-zero mappings are stored and setting a key to the value 0 is akin to
+ * "removing" that key).
+ *
- Code: arbitrary-length sequence of bytes that corresponds to EVM bytecode.
+ *
- Version: the version of the EVM bytecode.
+ *
+ */
+public interface AccountState {
+
+ /**
+ * The Keccak-256 hash of the account address.
+ *
+ * Note that the world state does not store account addresses, only their hashes, and so this
+ * is how account are truly identified. So while accounts can be queried by their address (through
+ * first computing their hash), one cannot infer the address from an account simply from the world
+ * state.
+ *
+ * @return the Keccak-256 hash of the account address.
+ */
+ Hash getAddressHash();
+
+ /**
+ * The account nonce, that is the number of transactions sent from that account.
+ *
+ * @return the account nonce.
+ */
+ long getNonce();
+
+ /**
+ * The available balance of that account.
+ *
+ * @return the balance, in Wei, of the account.
+ */
+ Wei getBalance();
+
+ /**
+ * The EVM bytecode associated with this account.
+ *
+ * @return the account code (which can be empty).
+ */
+ BytesValue getCode();
+
+ /**
+ * The hash of the EVM bytecode associated with this account.
+ *
+ * @return the hash of the account code (which may be {@link Hash#EMPTY}).
+ */
+ Hash getCodeHash();
+
+ /**
+ * Whether the account has (non empty) EVM bytecode associated to it.
+ *
+ *
This is functionally equivalent to {@code !code().isEmpty()}, though could be implemented
+ * more efficiently.
+ *
+ * @return Whether the account has EVM bytecode associated to it.
+ */
+ default boolean hasCode() {
+ return !getCode().isEmpty();
+ }
+
+ /**
+ * The version of the EVM bytecode associated with this account.
+ *
+ * @return the version of the account code. Default is zero.
+ */
+ int getVersion();
+
+ /**
+ * Retrieves a value in the account storage given its key.
+ *
+ * @param key the key to retrieve in the account storage.
+ * @return the value associated to {@code key} in the account storage. Note that this is never
+ * {@code null}, but 0 acts as a default value.
+ */
+ UInt256 getStorageValue(UInt256 key);
+
+ /**
+ * Retrieves the original value from before the current transaction in the account storage given
+ * its key.
+ *
+ * @param key the key to retrieve in the account storage.
+ * @return the original value associated to {@code key} in the account storage. Note that this is
+ * never {@code null}, but 0 acts as a default value.
+ */
+ UInt256 getOriginalStorageValue(UInt256 key);
+
+ /**
+ * Whether the account is "empty".
+ *
+ *
An account is defined as empty if:
+ *
+ *
+ * - its nonce is 0;
+ *
- its balance is 0;
+ *
- its associated code is empty (the zero-length byte sequence).
+ *
+ *
+ * @return {@code true} if the account is empty with regard to the definition above, {@code false}
+ * otherwise.
+ */
+ default boolean isEmpty() {
+ return getNonce() == 0 && getBalance().isZero() && !hasCode();
+ }
+
+ /**
+ * Retrieve up to {@code limit} storage entries beginning from the first entry with hash equal to
+ * or greater than {@code startKeyHash}.
+ *
+ * @param startKeyHash the first key hash to return.
+ * @param limit the maximum number of entries to return.
+ * @return the requested storage entries as a map of key hash to entry.
+ */
+ NavigableMap storageEntriesFrom(Bytes32 startKeyHash, int limit);
+}
diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldState.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldState.java
index 8e5af56e48..1538b5d1cb 100644
--- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldState.java
+++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldState.java
@@ -13,7 +13,11 @@
package tech.pegasys.pantheon.ethereum.core;
import tech.pegasys.pantheon.util.bytes.Bytes32;
+import tech.pegasys.pantheon.util.bytes.BytesValue;
+import tech.pegasys.pantheon.util.uint.UInt256;
+import java.util.NavigableMap;
+import java.util.Optional;
import java.util.stream.Stream;
/**
@@ -41,5 +45,65 @@ public interface WorldState extends WorldView {
* @return a stream of all the accounts (in no particular order) contained in the world state
* represented by the root hash of this object at the time of the call.
*/
- Stream streamAccounts(Bytes32 startKeyHash, int limit);
+ Stream streamAccounts(Bytes32 startKeyHash, int limit);
+
+ class StreamableAccount implements AccountState {
+ private final Optional address;
+ private final AccountState accountState;
+
+ public StreamableAccount(final Optional address, final AccountState accountState) {
+ this.address = address;
+ this.accountState = accountState;
+ }
+
+ public Optional getAddress() {
+ return address;
+ }
+
+ @Override
+ public Hash getAddressHash() {
+ return accountState.getAddressHash();
+ }
+
+ @Override
+ public long getNonce() {
+ return accountState.getNonce();
+ }
+
+ @Override
+ public Wei getBalance() {
+ return accountState.getBalance();
+ }
+
+ @Override
+ public BytesValue getCode() {
+ return accountState.getCode();
+ }
+
+ @Override
+ public Hash getCodeHash() {
+ return accountState.getCodeHash();
+ }
+
+ @Override
+ public int getVersion() {
+ return accountState.getVersion();
+ }
+
+ @Override
+ public UInt256 getStorageValue(final UInt256 key) {
+ return accountState.getStorageValue(key);
+ }
+
+ @Override
+ public UInt256 getOriginalStorageValue(final UInt256 key) {
+ return accountState.getOriginalStorageValue(key);
+ }
+
+ @Override
+ public NavigableMap storageEntriesFrom(
+ final Bytes32 startKeyHash, final int limit) {
+ return accountState.storageEntriesFrom(startKeyHash, limit);
+ }
+ }
}
diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldState.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldState.java
index 50d54b17fb..b65cbfffa4 100644
--- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldState.java
+++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldState.java
@@ -14,6 +14,7 @@
import tech.pegasys.pantheon.ethereum.core.AbstractWorldUpdater;
import tech.pegasys.pantheon.ethereum.core.Account;
+import tech.pegasys.pantheon.ethereum.core.AccountState;
import tech.pegasys.pantheon.ethereum.core.AccountStorageEntry;
import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.Hash;
@@ -110,11 +111,11 @@ public Account get(final Address address) {
.orElse(null);
}
- private AccountState deserializeAccount(
+ private WorldStateAccount deserializeAccount(
final Address address, final Hash addressHash, final BytesValue encoded) throws RLPException {
final RLPInput in = RLP.input(encoded);
final StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(in);
- return new AccountState(address, addressHash, accountValue);
+ return new WorldStateAccount(address, addressHash, accountValue);
}
private static BytesValue serializeAccount(
@@ -134,16 +135,16 @@ public WorldUpdater updater() {
}
@Override
- public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) {
+ public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) {
return accountStateTrie.entriesFrom(startKeyHash, limit).entrySet().stream()
.map(
- entry ->
- deserializeAccount(
- // TODO: Account address should be an {@code Optional} rather than defaulting to
- // ZERO
- getAccountTrieKeyPreimage(entry.getKey()).orElse(Address.ZERO),
- Hash.wrap(entry.getKey()),
- entry.getValue()));
+ entry -> {
+ final Optional address = getAccountTrieKeyPreimage(entry.getKey());
+ final AccountState account =
+ deserializeAccount(
+ address.orElse(Address.ZERO), Hash.wrap(entry.getKey()), entry.getValue());
+ return new StreamableAccount(address, account);
+ });
}
@Override
@@ -203,7 +204,7 @@ private Optional getAccountTrieKeyPreimage(final Bytes32 trieKey) {
// An immutable class that represents an individual account as stored in
// in the world state's underlying merkle patricia trie.
- protected class AccountState implements Account {
+ protected class WorldStateAccount implements Account {
private final Address address;
private final Hash addressHash;
@@ -213,7 +214,7 @@ protected class AccountState implements Account {
// Lazily initialized since we don't always access storage.
private volatile MerklePatriciaTrie storageTrie;
- private AccountState(
+ private WorldStateAccount(
final Address address, final Hash addressHash, final StateTrieAccountValue accountValue) {
this.address = address;
@@ -337,14 +338,14 @@ public String toString() {
}
protected static class Updater
- extends AbstractWorldUpdater {
+ extends AbstractWorldUpdater {
protected Updater(final DefaultMutableWorldState world) {
super(world);
}
@Override
- protected AccountState getForMutation(final Address address) {
+ protected WorldStateAccount getForMutation(final Address address) {
final DefaultMutableWorldState wrapped = wrappedWorldView();
final Hash addressHash = Hash.hash(address);
return wrapped
@@ -376,8 +377,8 @@ public void commit() {
wrapped.updatedAccountCode.remove(address);
}
- for (final UpdateTrackingAccount updated : updatedAccounts()) {
- final AccountState origin = updated.getWrappedAccount();
+ for (final UpdateTrackingAccount updated : updatedAccounts()) {
+ final WorldStateAccount origin = updated.getWrappedAccount();
// Save the code in key-value storage ...
Hash codeHash = origin == null ? Hash.EMPTY : origin.getCodeHash();
diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java
index 2916e3ec8c..504f21f587 100644
--- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java
+++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/worldstate/DefaultMutableWorldStateTest.java
@@ -27,6 +27,7 @@
import tech.pegasys.pantheon.ethereum.core.MutableWorldState;
import tech.pegasys.pantheon.ethereum.core.Wei;
import tech.pegasys.pantheon.ethereum.core.WorldState;
+import tech.pegasys.pantheon.ethereum.core.WorldState.StreamableAccount;
import tech.pegasys.pantheon.ethereum.core.WorldUpdater;
import tech.pegasys.pantheon.ethereum.storage.keyvalue.WorldStateKeyValueStorage;
import tech.pegasys.pantheon.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage;
@@ -167,7 +168,7 @@ public void removeAccount_AccountExistsAndIsPersisted() {
@Test
public void streamAccounts_empty() {
final MutableWorldState worldState = createEmpty();
- final Stream accounts = worldState.streamAccounts(Bytes32.ZERO, 10);
+ final Stream accounts = worldState.streamAccounts(Bytes32.ZERO, 10);
assertThat(accounts.count()).isEqualTo(0L);
}
@@ -178,17 +179,17 @@ public void streamAccounts_singleAccount() {
updater.createAccount(ADDRESS).setBalance(Wei.of(100000));
updater.commit();
- List accounts =
+ List accounts =
worldState.streamAccounts(Bytes32.ZERO, 10).collect(Collectors.toList());
assertThat(accounts.size()).isEqualTo(1L);
- assertThat(accounts.get(0).getAddress()).isEqualTo(ADDRESS);
+ assertThat(accounts.get(0).getAddress()).hasValue(ADDRESS);
assertThat(accounts.get(0).getBalance()).isEqualTo(Wei.of(100000));
// Check again after persisting
worldState.persist();
accounts = worldState.streamAccounts(Bytes32.ZERO, 10).collect(Collectors.toList());
assertThat(accounts.size()).isEqualTo(1L);
- assertThat(accounts.get(0).getAddress()).isEqualTo(ADDRESS);
+ assertThat(accounts.get(0).getAddress()).hasValue(ADDRESS);
assertThat(accounts.get(0).getBalance()).isEqualTo(Wei.of(100000));
}
@@ -214,28 +215,28 @@ public void streamAccounts_multipleAccounts() {
final Hash startHash = accountAIsFirst ? accountA.getAddressHash() : accountB.getAddressHash();
// Get first account
- final List firstAccount =
+ final List firstAccount =
worldState.streamAccounts(startHash, 1).collect(Collectors.toList());
assertThat(firstAccount.size()).isEqualTo(1L);
assertThat(firstAccount.get(0).getAddress())
- .isEqualTo(accountAIsFirst ? accountA.getAddress() : accountB.getAddress());
+ .hasValue(accountAIsFirst ? accountA.getAddress() : accountB.getAddress());
// Get both accounts
- final List allAccounts =
+ final List allAccounts =
worldState.streamAccounts(Bytes32.ZERO, 2).collect(Collectors.toList());
assertThat(allAccounts.size()).isEqualTo(2L);
assertThat(allAccounts.get(0).getAddress())
- .isEqualTo(accountAIsFirst ? accountA.getAddress() : accountB.getAddress());
+ .hasValue(accountAIsFirst ? accountA.getAddress() : accountB.getAddress());
assertThat(allAccounts.get(1).getAddress())
- .isEqualTo(accountAIsFirst ? accountB.getAddress() : accountA.getAddress());
+ .hasValue(accountAIsFirst ? accountB.getAddress() : accountA.getAddress());
// Get second account
final Bytes32 startHashForSecondAccount = startHash.asUInt256().plus(1L).getBytes();
- final List secondAccount =
+ final List secondAccount =
worldState.streamAccounts(startHashForSecondAccount, 100).collect(Collectors.toList());
assertThat(secondAccount.size()).isEqualTo(1L);
assertThat(secondAccount.get(0).getAddress())
- .isEqualTo(accountAIsFirst ? accountB.getAddress() : accountA.getAddress());
+ .hasValue(accountAIsFirst ? accountB.getAddress() : accountA.getAddress());
}
@Test
diff --git a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugAccountRange.java b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugAccountRange.java
index 8b58c6c1e8..370e8e0d68 100644
--- a/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugAccountRange.java
+++ b/ethereum/jsonrpc/src/main/java/tech/pegasys/pantheon/ethereum/jsonrpc/internal/methods/DebugAccountRange.java
@@ -12,10 +12,11 @@
*/
package tech.pegasys.pantheon.ethereum.jsonrpc.internal.methods;
-import tech.pegasys.pantheon.ethereum.core.Account;
+import tech.pegasys.pantheon.ethereum.core.Address;
import tech.pegasys.pantheon.ethereum.core.BlockHeader;
import tech.pegasys.pantheon.ethereum.core.Hash;
import tech.pegasys.pantheon.ethereum.core.MutableWorldState;
+import tech.pegasys.pantheon.ethereum.core.WorldState.StreamableAccount;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.JsonRpcRequest;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.BlockParameterOrBlockHash;
import tech.pegasys.pantheon.ethereum.jsonrpc.internal.parameters.JsonRpcParameter;
@@ -84,7 +85,7 @@ public JsonRpcResponse response(final JsonRpcRequest request) {
if (state.isEmpty()) {
return emptyResponse(request);
} else {
- final List accounts =
+ final List accounts =
state
.get()
.streamAccounts(Bytes32.fromHexStringLenient(addressHash), maxResults + 1)
@@ -102,7 +103,7 @@ public JsonRpcResponse response(final JsonRpcRequest request) {
.collect(
Collectors.toMap(
account -> account.getAddressHash().toString(),
- account -> account.getAddress().toString())),
+ account -> account.getAddress().orElse(Address.ZERO).toString())),
nextKey.toString()));
}
}