mirror of https://github.com/hyperledger/besu
[NC-1344] Create a simple WorldStateDownloader (#657)
Signed-off-by: Adrian Sutton <adrian.sutton@consensys.net>pull/2/head
parent
fcf619ce54
commit
055785b7e3
@ -0,0 +1,95 @@ |
||||
/* |
||||
* Copyright 2019 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.worldstate; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.core.Wei; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPInput; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; |
||||
|
||||
/** Represents the raw values associated with an account in the world state trie. */ |
||||
public class StateTrieAccountValue { |
||||
|
||||
private final long nonce; |
||||
private final Wei balance; |
||||
private final Hash storageRoot; |
||||
private final Hash codeHash; |
||||
|
||||
public StateTrieAccountValue( |
||||
final long nonce, final Wei balance, final Hash storageRoot, final Hash codeHash) { |
||||
this.nonce = nonce; |
||||
this.balance = balance; |
||||
this.storageRoot = storageRoot; |
||||
this.codeHash = codeHash; |
||||
} |
||||
|
||||
/** |
||||
* The account nonce, that is the number of transactions sent from that account. |
||||
* |
||||
* @return the account nonce. |
||||
*/ |
||||
public long getNonce() { |
||||
return nonce; |
||||
} |
||||
|
||||
/** |
||||
* The available balance of that account. |
||||
* |
||||
* @return the balance, in Wei, of the account. |
||||
*/ |
||||
public Wei getBalance() { |
||||
return balance; |
||||
} |
||||
|
||||
/** |
||||
* The hash of the root of the storage trie associated with this account. |
||||
* |
||||
* @return the hash of the root node of the storage trie. |
||||
*/ |
||||
public Hash getStorageRoot() { |
||||
return storageRoot; |
||||
} |
||||
|
||||
/** |
||||
* The hash of the EVM bytecode associated with this account. |
||||
* |
||||
* @return the hash of the account code (which may be {@link Hash#EMPTY}. |
||||
*/ |
||||
public Hash getCodeHash() { |
||||
return codeHash; |
||||
} |
||||
|
||||
public void writeTo(final RLPOutput out) { |
||||
out.startList(); |
||||
|
||||
out.writeLongScalar(nonce); |
||||
out.writeUInt256Scalar(balance); |
||||
out.writeBytesValue(storageRoot); |
||||
out.writeBytesValue(codeHash); |
||||
|
||||
out.endList(); |
||||
} |
||||
|
||||
public static StateTrieAccountValue readFrom(final RLPInput in) { |
||||
in.enterList(); |
||||
|
||||
final long nonce = in.readLongScalar(); |
||||
final Wei balance = in.readUInt256Scalar(Wei::wrap); |
||||
final Hash storageRoot = Hash.wrap(in.readBytes32()); |
||||
final Hash codeHash = Hash.wrap(in.readBytes32()); |
||||
|
||||
in.leaveList(); |
||||
|
||||
return new StateTrieAccountValue(nonce, balance, storageRoot, codeHash); |
||||
} |
||||
} |
@ -0,0 +1,157 @@ |
||||
/* |
||||
* Copyright 2019 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.storage.keyvalue; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.trie.MerklePatriciaTrie; |
||||
import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class KeyValueStorageWorldStateStorageTest { |
||||
|
||||
@Test |
||||
public void getCode_returnsEmpty() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
assertThat(storage.getCode(Hash.EMPTY)).contains(BytesValue.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void getAccountStateTrieNode_returnsEmptyNode() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
assertThat(storage.getAccountStateTrieNode(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
} |
||||
|
||||
@Test |
||||
public void getAccountStorageTrieNode_returnsEmptyNode() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
assertThat(storage.getAccountStorageTrieNode(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
} |
||||
|
||||
@Test |
||||
public void getNodeData_returnsEmptyValue() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
assertThat(storage.getNodeData(Hash.EMPTY)).contains(BytesValue.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void getNodeData_returnsEmptyNode() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
assertThat(storage.getNodeData(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
} |
||||
|
||||
@Test |
||||
public void getCode_saveAndGetSpecialValues() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage |
||||
.updater() |
||||
.putCode(MerklePatriciaTrie.EMPTY_TRIE_NODE) |
||||
.putCode(BytesValue.EMPTY) |
||||
.commit(); |
||||
|
||||
assertThat(storage.getCode(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
assertThat(storage.getCode(Hash.EMPTY)).contains(BytesValue.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void getCode_saveAndGetRegularValue() { |
||||
BytesValue bytes = BytesValue.fromHexString("0x123456"); |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage.updater().putCode(bytes).commit(); |
||||
|
||||
assertThat(storage.getCode(Hash.hash(bytes))).contains(bytes); |
||||
} |
||||
|
||||
@Test |
||||
public void getAccountStateTrieNode_saveAndGetSpecialValues() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage |
||||
.updater() |
||||
.putAccountStateTrieNode( |
||||
Hash.hash(MerklePatriciaTrie.EMPTY_TRIE_NODE), MerklePatriciaTrie.EMPTY_TRIE_NODE) |
||||
.putAccountStateTrieNode(Hash.hash(BytesValue.EMPTY), BytesValue.EMPTY) |
||||
.commit(); |
||||
|
||||
assertThat(storage.getAccountStateTrieNode(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
assertThat(storage.getAccountStateTrieNode(Hash.EMPTY)).contains(BytesValue.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void getAccountStateTrieNode_saveAndGetRegularValue() { |
||||
BytesValue bytes = BytesValue.fromHexString("0x123456"); |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage.updater().putAccountStateTrieNode(Hash.hash(bytes), bytes).commit(); |
||||
|
||||
assertThat(storage.getAccountStateTrieNode(Hash.hash(bytes))).contains(bytes); |
||||
} |
||||
|
||||
@Test |
||||
public void getAccountStorageTrieNode_saveAndGetSpecialValues() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage |
||||
.updater() |
||||
.putAccountStorageTrieNode( |
||||
Hash.hash(MerklePatriciaTrie.EMPTY_TRIE_NODE), MerklePatriciaTrie.EMPTY_TRIE_NODE) |
||||
.putAccountStorageTrieNode(Hash.hash(BytesValue.EMPTY), BytesValue.EMPTY) |
||||
.commit(); |
||||
|
||||
assertThat(storage.getAccountStorageTrieNode(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
assertThat(storage.getAccountStorageTrieNode(Hash.EMPTY)).contains(BytesValue.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void getAccountStorageTrieNode_saveAndGetRegularValue() { |
||||
BytesValue bytes = BytesValue.fromHexString("0x123456"); |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage.updater().putAccountStorageTrieNode(Hash.hash(bytes), bytes).commit(); |
||||
|
||||
assertThat(storage.getAccountStateTrieNode(Hash.hash(bytes))).contains(bytes); |
||||
} |
||||
|
||||
@Test |
||||
public void getNodeData_saveAndGetSpecialValues() { |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage |
||||
.updater() |
||||
.putAccountStorageTrieNode( |
||||
Hash.hash(MerklePatriciaTrie.EMPTY_TRIE_NODE), MerklePatriciaTrie.EMPTY_TRIE_NODE) |
||||
.putAccountStorageTrieNode(Hash.hash(BytesValue.EMPTY), BytesValue.EMPTY) |
||||
.commit(); |
||||
|
||||
assertThat(storage.getNodeData(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) |
||||
.contains(MerklePatriciaTrie.EMPTY_TRIE_NODE); |
||||
assertThat(storage.getNodeData(Hash.EMPTY)).contains(BytesValue.EMPTY); |
||||
} |
||||
|
||||
@Test |
||||
public void getNodeData_saveAndGetRegularValue() { |
||||
BytesValue bytes = BytesValue.fromHexString("0x123456"); |
||||
KeyValueStorageWorldStateStorage storage = emptyStorage(); |
||||
storage.updater().putAccountStorageTrieNode(Hash.hash(bytes), bytes).commit(); |
||||
|
||||
assertThat(storage.getNodeData(Hash.hash(bytes))).contains(bytes); |
||||
} |
||||
|
||||
private KeyValueStorageWorldStateStorage emptyStorage() { |
||||
return new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); |
||||
} |
||||
} |
@ -0,0 +1,61 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.rlp.RLP; |
||||
import tech.pegasys.pantheon.ethereum.trie.MerklePatriciaTrie; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.StateTrieAccountValue; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage.Updater; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
class AccountTrieNodeDataRequest extends TrieNodeDataRequest { |
||||
|
||||
AccountTrieNodeDataRequest(final Hash hash) { |
||||
super(Kind.ACCOUNT_TRIE_NODE, hash); |
||||
} |
||||
|
||||
@Override |
||||
public void persist(final Updater updater) { |
||||
checkNotNull(getData(), "Must set data before node can be persisted."); |
||||
updater.putAccountStateTrieNode(getHash(), getData()); |
||||
} |
||||
|
||||
@Override |
||||
protected NodeDataRequest createChildNodeDataRequest(final Hash childHash) { |
||||
return NodeDataRequest.createAccountDataRequest(childHash); |
||||
} |
||||
|
||||
@Override |
||||
protected List<NodeDataRequest> getRequestsFromTrieNodeValue(final BytesValue value) { |
||||
List<NodeDataRequest> nodeData = new ArrayList<>(2); |
||||
StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(RLP.input(value)); |
||||
// Add code, if appropriate
|
||||
if (!accountValue.getCodeHash().equals(Hash.EMPTY)) { |
||||
nodeData.add(NodeDataRequest.createCodeRequest(accountValue.getCodeHash())); |
||||
} |
||||
// Add storage, if appropriate
|
||||
if (!accountValue.getStorageRoot().equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { |
||||
// If storage is non-empty queue download
|
||||
NodeDataRequest storageNode = |
||||
NodeDataRequest.createStorageDataRequest(accountValue.getStorageRoot()); |
||||
nodeData.add(storageNode); |
||||
} |
||||
return nodeData; |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage.Updater; |
||||
|
||||
import java.util.stream.Stream; |
||||
|
||||
class CodeNodeDataRequest extends NodeDataRequest { |
||||
|
||||
CodeNodeDataRequest(final Hash hash) { |
||||
super(Kind.CODE, hash); |
||||
} |
||||
|
||||
@Override |
||||
public void persist(final Updater updater) { |
||||
checkNotNull(getData(), "Must set data before node can be persisted."); |
||||
updater.putCode(getHash(), getData()); |
||||
} |
||||
|
||||
@Override |
||||
public Stream<NodeDataRequest> getChildRequests() { |
||||
// Code nodes have nothing further to download
|
||||
return Stream.empty(); |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.util.stream.Stream; |
||||
|
||||
abstract class NodeDataRequest { |
||||
public enum Kind { |
||||
ACCOUNT_TRIE_NODE, |
||||
STORAGE_TRIE_NODE, |
||||
CODE |
||||
} |
||||
|
||||
private final Kind kind; |
||||
private final Hash hash; |
||||
private BytesValue data; |
||||
|
||||
protected NodeDataRequest(final Kind kind, final Hash hash) { |
||||
this.kind = kind; |
||||
this.hash = hash; |
||||
} |
||||
|
||||
public static AccountTrieNodeDataRequest createAccountDataRequest(final Hash hash) { |
||||
return new AccountTrieNodeDataRequest(hash); |
||||
} |
||||
|
||||
public static StorageTrieNodeDataRequest createStorageDataRequest(final Hash hash) { |
||||
return new StorageTrieNodeDataRequest(hash); |
||||
} |
||||
|
||||
public static CodeNodeDataRequest createCodeRequest(final Hash hash) { |
||||
return new CodeNodeDataRequest(hash); |
||||
} |
||||
|
||||
public Kind getKind() { |
||||
return kind; |
||||
} |
||||
|
||||
public Hash getHash() { |
||||
return hash; |
||||
} |
||||
|
||||
public BytesValue getData() { |
||||
return data; |
||||
} |
||||
|
||||
public NodeDataRequest setData(final BytesValue data) { |
||||
this.data = data; |
||||
return this; |
||||
} |
||||
|
||||
public abstract void persist(final WorldStateStorage.Updater updater); |
||||
|
||||
public abstract Stream<NodeDataRequest> getChildRequests(); |
||||
} |
@ -0,0 +1,46 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage.Updater; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
class StorageTrieNodeDataRequest extends TrieNodeDataRequest { |
||||
|
||||
StorageTrieNodeDataRequest(final Hash hash) { |
||||
super(Kind.STORAGE_TRIE_NODE, hash); |
||||
} |
||||
|
||||
@Override |
||||
public void persist(final Updater updater) { |
||||
checkNotNull(getData(), "Must set data before node can be persisted."); |
||||
updater.putAccountStorageTrieNode(getHash(), getData()); |
||||
} |
||||
|
||||
@Override |
||||
protected NodeDataRequest createChildNodeDataRequest(final Hash childHash) { |
||||
return NodeDataRequest.createStorageDataRequest(childHash); |
||||
} |
||||
|
||||
@Override |
||||
protected List<NodeDataRequest> getRequestsFromTrieNodeValue(final BytesValue value) { |
||||
// Nothing to do for terminal storage node
|
||||
return Collections.emptyList(); |
||||
} |
||||
} |
@ -0,0 +1,71 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.trie.Node; |
||||
import tech.pegasys.pantheon.ethereum.trie.TrieNodeDecoder; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.util.List; |
||||
import java.util.stream.Stream; |
||||
|
||||
abstract class TrieNodeDataRequest extends NodeDataRequest { |
||||
|
||||
private static final TrieNodeDecoder nodeDecoder = TrieNodeDecoder.create(); |
||||
|
||||
TrieNodeDataRequest(final Kind kind, final Hash hash) { |
||||
super(kind, hash); |
||||
} |
||||
|
||||
@Override |
||||
public Stream<NodeDataRequest> getChildRequests() { |
||||
if (getData() == null) { |
||||
// If this node hasn't been downloaded yet, we can't return any child data
|
||||
return Stream.empty(); |
||||
} |
||||
|
||||
final Node<BytesValue> node = nodeDecoder.decode(getData()); |
||||
return getRequestsFromLoadedTrieNode(node); |
||||
} |
||||
|
||||
private Stream<NodeDataRequest> getRequestsFromLoadedTrieNode(final Node<BytesValue> trieNode) { |
||||
// Process this node's children
|
||||
final Stream<NodeDataRequest> childRequests = |
||||
trieNode |
||||
.getChildren() |
||||
.map(List::stream) |
||||
.map(s -> s.flatMap(this::getRequestsFromChildTrieNode)) |
||||
.orElse(Stream.of()); |
||||
|
||||
// Process value at this node, if present
|
||||
return trieNode |
||||
.getValue() |
||||
.map(v -> Stream.concat(childRequests, (getRequestsFromTrieNodeValue(v).stream()))) |
||||
.orElse(childRequests); |
||||
} |
||||
|
||||
private Stream<NodeDataRequest> getRequestsFromChildTrieNode(final Node<BytesValue> trieNode) { |
||||
if (trieNode.isReferencedByHash()) { |
||||
// If child nodes are reference by hash, we need to download them
|
||||
NodeDataRequest req = createChildNodeDataRequest(Hash.wrap(trieNode.getHash())); |
||||
return Stream.of(req); |
||||
} |
||||
// Otherwise if the child's value has been inlined we can go ahead and process it
|
||||
return getRequestsFromLoadedTrieNode(trieNode); |
||||
} |
||||
|
||||
protected abstract NodeDataRequest createChildNodeDataRequest(final Hash childHash); |
||||
|
||||
protected abstract List<NodeDataRequest> getRequestsFromTrieNodeValue(final BytesValue value); |
||||
} |
@ -0,0 +1,201 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.core.BlockHeader; |
||||
import tech.pegasys.pantheon.ethereum.core.Hash; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.AbstractPeerTask.PeerTaskResult; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthContext; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthPeer; |
||||
import tech.pegasys.pantheon.ethereum.eth.sync.tasks.GetNodeDataFromPeerTask; |
||||
import tech.pegasys.pantheon.ethereum.eth.sync.tasks.WaitForPeerTask; |
||||
import tech.pegasys.pantheon.ethereum.trie.MerklePatriciaTrie; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage; |
||||
import tech.pegasys.pantheon.metrics.LabelledMetric; |
||||
import tech.pegasys.pantheon.metrics.OperationTimer; |
||||
import tech.pegasys.pantheon.services.queue.BigQueue; |
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
import java.util.stream.Collectors; |
||||
|
||||
public class WorldStateDownloader { |
||||
|
||||
private enum Status { |
||||
IDLE, |
||||
RUNNING, |
||||
DONE |
||||
} |
||||
|
||||
private final EthContext ethContext; |
||||
// The target header for which we want to retrieve world state
|
||||
private final BlockHeader header; |
||||
private final BigQueue<NodeDataRequest> pendingRequests; |
||||
private final WorldStateStorage.Updater worldStateStorageUpdater; |
||||
private final int hashCountPerRequest; |
||||
private final int maxOutstandingRequests; |
||||
private final AtomicInteger outstandingRequests = new AtomicInteger(0); |
||||
private final LabelledMetric<OperationTimer> ethTasksTimer; |
||||
private final WorldStateStorage worldStateStorage; |
||||
private final AtomicBoolean sendingRequests = new AtomicBoolean(false); |
||||
private volatile CompletableFuture<Void> future; |
||||
private volatile Status status = Status.IDLE; |
||||
|
||||
public WorldStateDownloader( |
||||
final EthContext ethContext, |
||||
final WorldStateStorage worldStateStorage, |
||||
final BlockHeader header, |
||||
final BigQueue<NodeDataRequest> pendingRequests, |
||||
final int hashCountPerRequest, |
||||
final int maxOutstandingRequests, |
||||
final LabelledMetric<OperationTimer> ethTasksTimer) { |
||||
this.ethContext = ethContext; |
||||
this.worldStateStorage = worldStateStorage; |
||||
this.header = header; |
||||
this.pendingRequests = pendingRequests; |
||||
this.hashCountPerRequest = hashCountPerRequest; |
||||
this.maxOutstandingRequests = maxOutstandingRequests; |
||||
this.ethTasksTimer = ethTasksTimer; |
||||
this.worldStateStorageUpdater = worldStateStorage.updater(); |
||||
|
||||
Hash stateRoot = header.getStateRoot(); |
||||
if (stateRoot.equals(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH)) { |
||||
// If we're requesting data for an empty world state, we're already done
|
||||
markDone(); |
||||
} else { |
||||
pendingRequests.enqueue(NodeDataRequest.createAccountDataRequest(header.getStateRoot())); |
||||
} |
||||
} |
||||
|
||||
public CompletableFuture<Void> run() { |
||||
synchronized (this) { |
||||
if (status == Status.DONE || status == Status.RUNNING) { |
||||
return future; |
||||
} |
||||
status = Status.RUNNING; |
||||
future = new CompletableFuture<>(); |
||||
} |
||||
|
||||
requestNodeData(); |
||||
return future; |
||||
} |
||||
|
||||
private void requestNodeData() { |
||||
if (sendingRequests.compareAndSet(false, true)) { |
||||
while (shouldRequestNodeData()) { |
||||
Optional<EthPeer> maybePeer = ethContext.getEthPeers().idlePeer(header.getNumber()); |
||||
|
||||
if (!maybePeer.isPresent()) { |
||||
// If no peer is available, wait and try again
|
||||
waitForNewPeer().whenComplete((r, t) -> requestNodeData()); |
||||
break; |
||||
} else { |
||||
EthPeer peer = maybePeer.get(); |
||||
|
||||
// Collect data to be requested
|
||||
List<NodeDataRequest> toRequest = new ArrayList<>(); |
||||
for (int i = 0; i < hashCountPerRequest; i++) { |
||||
NodeDataRequest pendingRequest = pendingRequests.dequeue(); |
||||
if (pendingRequest == null) { |
||||
break; |
||||
} |
||||
toRequest.add(pendingRequest); |
||||
} |
||||
|
||||
// Request and process node data
|
||||
outstandingRequests.incrementAndGet(); |
||||
sendAndProcessRequests(peer, toRequest) |
||||
.whenComplete( |
||||
(res, error) -> { |
||||
if (outstandingRequests.decrementAndGet() == 0 && pendingRequests.isEmpty()) { |
||||
// We're done
|
||||
worldStateStorageUpdater.commit(); |
||||
markDone(); |
||||
} else { |
||||
// Send out additional requests
|
||||
requestNodeData(); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
sendingRequests.set(false); |
||||
} |
||||
} |
||||
|
||||
private synchronized void markDone() { |
||||
if (future == null) { |
||||
future = CompletableFuture.completedFuture(null); |
||||
} else { |
||||
future.complete(null); |
||||
} |
||||
status = Status.DONE; |
||||
} |
||||
|
||||
private boolean shouldRequestNodeData() { |
||||
return !future.isDone() |
||||
&& outstandingRequests.get() < maxOutstandingRequests |
||||
&& !pendingRequests.isEmpty(); |
||||
} |
||||
|
||||
private CompletableFuture<?> waitForNewPeer() { |
||||
return ethContext |
||||
.getScheduler() |
||||
.timeout(WaitForPeerTask.create(ethContext, ethTasksTimer), Duration.ofSeconds(5)); |
||||
} |
||||
|
||||
private CompletableFuture<?> sendAndProcessRequests( |
||||
final EthPeer peer, final List<NodeDataRequest> requests) { |
||||
List<Hash> hashes = |
||||
requests.stream().map(NodeDataRequest::getHash).distinct().collect(Collectors.toList()); |
||||
return GetNodeDataFromPeerTask.forHashes(ethContext, hashes, ethTasksTimer) |
||||
.assignPeer(peer) |
||||
.run() |
||||
.thenApply(PeerTaskResult::getResult) |
||||
.thenApply(this::mapNodeDataByHash) |
||||
.whenComplete( |
||||
(data, err) -> { |
||||
boolean requestFailed = err != null; |
||||
for (NodeDataRequest request : requests) { |
||||
BytesValue matchingData = requestFailed ? null : data.get(request.getHash()); |
||||
if (matchingData == null) { |
||||
pendingRequests.enqueue(request); |
||||
} else { |
||||
// Persist request data
|
||||
request.setData(matchingData); |
||||
request.persist(worldStateStorageUpdater); |
||||
|
||||
// Queue child requests
|
||||
request |
||||
.getChildRequests() |
||||
.filter(n -> !worldStateStorage.contains(n.getHash())) |
||||
.forEach(pendingRequests::enqueue); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private Map<Hash, BytesValue> mapNodeDataByHash(final List<BytesValue> data) { |
||||
// Map data by hash
|
||||
Map<Hash, BytesValue> dataByHash = new HashMap<>(); |
||||
data.stream().forEach(d -> dataByHash.put(Hash.hash(d), d)); |
||||
return dataByHash; |
||||
} |
||||
} |
@ -0,0 +1,283 @@ |
||||
/* |
||||
* Copyright 2019 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.eth.sync.worldstate; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
|
||||
import tech.pegasys.pantheon.ethereum.chain.Blockchain; |
||||
import tech.pegasys.pantheon.ethereum.core.Account; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator; |
||||
import tech.pegasys.pantheon.ethereum.core.BlockDataGenerator.BlockOptions; |
||||
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; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManager; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.EthProtocolManagerTestUtil; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer; |
||||
import tech.pegasys.pantheon.ethereum.eth.manager.RespondingEthPeer.Responder; |
||||
import tech.pegasys.pantheon.ethereum.storage.keyvalue.KeyValueStorageWorldStateStorage; |
||||
import tech.pegasys.pantheon.ethereum.trie.MerklePatriciaTrie; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateArchive; |
||||
import tech.pegasys.pantheon.ethereum.worldstate.WorldStateStorage; |
||||
import tech.pegasys.pantheon.metrics.noop.NoOpMetricsSystem; |
||||
import tech.pegasys.pantheon.services.kvstore.InMemoryKeyValueStorage; |
||||
import tech.pegasys.pantheon.services.queue.BigQueue; |
||||
import tech.pegasys.pantheon.services.queue.InMemoryBigQueue; |
||||
import tech.pegasys.pantheon.util.bytes.Bytes32; |
||||
import tech.pegasys.pantheon.util.uint.UInt256; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.CompletableFuture; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.Stream; |
||||
|
||||
import org.junit.Test; |
||||
|
||||
public class WorldStateDownloaderTest { |
||||
|
||||
private static final Hash EMPTY_TRIE_ROOT = Hash.wrap(MerklePatriciaTrie.EMPTY_TRIE_NODE_HASH); |
||||
|
||||
@Test |
||||
public void downloadWorldStateFromPeers_onePeerOneWithManyRequestsOneAtATime() { |
||||
downloadAvailableWorldStateFromPeers(1, 50, 1, 1); |
||||
} |
||||
|
||||
@Test |
||||
public void downloadWorldStateFromPeers_onePeerOneWithManyRequests() { |
||||
downloadAvailableWorldStateFromPeers(1, 50, 1, 10); |
||||
} |
||||
|
||||
@Test |
||||
public void downloadWorldStateFromPeers_onePeerWithSingleRequest() { |
||||
downloadAvailableWorldStateFromPeers(1, 1, 100, 10); |
||||
} |
||||
|
||||
@Test |
||||
public void downloadWorldStateFromPeers_largeStateFromMultiplePeers() { |
||||
downloadAvailableWorldStateFromPeers(5, 100, 10, 10); |
||||
} |
||||
|
||||
@Test |
||||
public void downloadWorldStateFromPeers_smallStateFromMultiplePeers() { |
||||
downloadAvailableWorldStateFromPeers(5, 5, 1, 10); |
||||
} |
||||
|
||||
@Test |
||||
public void downloadWorldStateFromPeers_singleRequestWithMultiplePeers() { |
||||
downloadAvailableWorldStateFromPeers(5, 1, 50, 50); |
||||
} |
||||
|
||||
@Test |
||||
public void downloadEmptyWorldState() { |
||||
BlockDataGenerator dataGen = new BlockDataGenerator(1); |
||||
final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); |
||||
BlockHeader header = |
||||
dataGen |
||||
.block(BlockOptions.create().setStateRoot(EMPTY_TRIE_ROOT).setBlockNumber(10)) |
||||
.getHeader(); |
||||
|
||||
// Create some peers
|
||||
List<RespondingEthPeer> peers = |
||||
Stream.generate( |
||||
() -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) |
||||
.limit(5) |
||||
.collect(Collectors.toList()); |
||||
|
||||
BigQueue<NodeDataRequest> queue = new InMemoryBigQueue<>(); |
||||
WorldStateStorage localStorage = |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); |
||||
WorldStateDownloader downloader = |
||||
new WorldStateDownloader( |
||||
ethProtocolManager.ethContext(), |
||||
localStorage, |
||||
header, |
||||
queue, |
||||
10, |
||||
10, |
||||
NoOpMetricsSystem.NO_OP_LABELLED_TIMER); |
||||
|
||||
CompletableFuture<Void> future = downloader.run(); |
||||
assertThat(future).isDone(); |
||||
|
||||
// Peers should not have been queried
|
||||
for (RespondingEthPeer peer : peers) { |
||||
assertThat(peer.hasOutstandingRequests()).isFalse(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void canRecoverFromTimeouts() { |
||||
BlockDataGenerator dataGen = new BlockDataGenerator(1); |
||||
TimeoutPolicy timeoutPolicy = TimeoutPolicy.timeoutXTimes(2); |
||||
final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(timeoutPolicy); |
||||
|
||||
// Setup "remote" state
|
||||
final WorldStateStorage remoteStorage = |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); |
||||
final WorldStateArchive remoteWorldStateArchive = new WorldStateArchive(remoteStorage); |
||||
final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); |
||||
|
||||
// Generate accounts and save corresponding state root
|
||||
final List<Account> accounts = dataGen.createRandomAccounts(remoteWorldState, 20); |
||||
final Hash stateRoot = remoteWorldState.rootHash(); |
||||
assertThat(stateRoot).isNotEqualTo(EMPTY_TRIE_ROOT); // Sanity check
|
||||
BlockHeader header = |
||||
dataGen.block(BlockOptions.create().setStateRoot(stateRoot).setBlockNumber(10)).getHeader(); |
||||
|
||||
// Create some peers
|
||||
List<RespondingEthPeer> peers = |
||||
Stream.generate( |
||||
() -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) |
||||
.limit(5) |
||||
.collect(Collectors.toList()); |
||||
|
||||
BigQueue<NodeDataRequest> queue = new InMemoryBigQueue<>(); |
||||
WorldStateStorage localStorage = |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); |
||||
WorldStateDownloader downloader = |
||||
new WorldStateDownloader( |
||||
ethProtocolManager.ethContext(), |
||||
localStorage, |
||||
header, |
||||
queue, |
||||
10, |
||||
10, |
||||
NoOpMetricsSystem.NO_OP_LABELLED_TIMER); |
||||
|
||||
CompletableFuture<Void> result = downloader.run(); |
||||
|
||||
// Respond to node data requests
|
||||
Responder responder = |
||||
RespondingEthPeer.blockchainResponder(mock(Blockchain.class), remoteWorldStateArchive); |
||||
while (!result.isDone()) { |
||||
for (RespondingEthPeer peer : peers) { |
||||
peer.respond(responder); |
||||
} |
||||
} |
||||
|
||||
// Check that all expected account data was downloaded
|
||||
WorldStateArchive localWorldStateArchive = new WorldStateArchive(localStorage); |
||||
final WorldState localWorldState = localWorldStateArchive.get(stateRoot); |
||||
assertThat(result).isDone(); |
||||
assertAccountsMatch(localWorldState, accounts); |
||||
} |
||||
|
||||
private void downloadAvailableWorldStateFromPeers( |
||||
final int peerCount, |
||||
final int accountCount, |
||||
final int hashesPerRequest, |
||||
final int maxOutstandingRequests) { |
||||
final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); |
||||
final int trailingPeerCount = 5; |
||||
BlockDataGenerator dataGen = new BlockDataGenerator(1); |
||||
|
||||
// Setup "remote" state
|
||||
final WorldStateStorage remoteStorage = |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); |
||||
final WorldStateArchive remoteWorldStateArchive = new WorldStateArchive(remoteStorage); |
||||
final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); |
||||
|
||||
// Generate accounts and save corresponding state root
|
||||
final List<Account> accounts = dataGen.createRandomAccounts(remoteWorldState, accountCount); |
||||
final Hash stateRoot = remoteWorldState.rootHash(); |
||||
assertThat(stateRoot).isNotEqualTo(EMPTY_TRIE_ROOT); // Sanity check
|
||||
BlockHeader header = |
||||
dataGen.block(BlockOptions.create().setStateRoot(stateRoot).setBlockNumber(10)).getHeader(); |
||||
|
||||
// Generate more data that should not be downloaded
|
||||
final List<Account> otherAccounts = dataGen.createRandomAccounts(remoteWorldState, 5); |
||||
Hash otherStateRoot = remoteWorldState.rootHash(); |
||||
BlockHeader otherHeader = |
||||
dataGen |
||||
.block(BlockOptions.create().setStateRoot(otherStateRoot).setBlockNumber(11)) |
||||
.getHeader(); |
||||
assertThat(otherStateRoot).isNotEqualTo(stateRoot); // Sanity check
|
||||
|
||||
BigQueue<NodeDataRequest> queue = new InMemoryBigQueue<>(); |
||||
WorldStateStorage localStorage = |
||||
new KeyValueStorageWorldStateStorage(new InMemoryKeyValueStorage()); |
||||
WorldStateArchive localWorldStateArchive = new WorldStateArchive(localStorage); |
||||
WorldStateDownloader downloader = |
||||
new WorldStateDownloader( |
||||
ethProtocolManager.ethContext(), |
||||
localStorage, |
||||
header, |
||||
queue, |
||||
hashesPerRequest, |
||||
maxOutstandingRequests, |
||||
NoOpMetricsSystem.NO_OP_LABELLED_TIMER); |
||||
|
||||
// Create some peers that can respond
|
||||
List<RespondingEthPeer> usefulPeers = |
||||
Stream.generate( |
||||
() -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, header.getNumber())) |
||||
.limit(peerCount) |
||||
.collect(Collectors.toList()); |
||||
// And some irrelevant peers
|
||||
List<RespondingEthPeer> trailingPeers = |
||||
Stream.generate( |
||||
() -> |
||||
EthProtocolManagerTestUtil.createPeer( |
||||
ethProtocolManager, header.getNumber() - 1L)) |
||||
.limit(trailingPeerCount) |
||||
.collect(Collectors.toList()); |
||||
|
||||
// Start downloader
|
||||
CompletableFuture<?> result = downloader.run(); |
||||
|
||||
// Respond to node data requests
|
||||
Responder responder = |
||||
RespondingEthPeer.blockchainResponder(mock(Blockchain.class), remoteWorldStateArchive); |
||||
while (!result.isDone()) { |
||||
for (RespondingEthPeer peer : usefulPeers) { |
||||
peer.respond(responder); |
||||
} |
||||
} |
||||
|
||||
// Check that trailing peers were not queried for data
|
||||
for (RespondingEthPeer trailingPeer : trailingPeers) { |
||||
assertThat(trailingPeer.hasOutstandingRequests()).isFalse(); |
||||
} |
||||
|
||||
// Check that all expected account data was downloaded
|
||||
final WorldState localWorldState = localWorldStateArchive.get(stateRoot); |
||||
assertThat(result).isDone(); |
||||
assertAccountsMatch(localWorldState, accounts); |
||||
|
||||
// We shouldn't have any extra data locally
|
||||
assertThat(localStorage.contains(otherHeader.getStateRoot())).isFalse(); |
||||
for (Account otherAccount : otherAccounts) { |
||||
assertThat(localWorldState.get(otherAccount.getAddress())).isNull(); |
||||
} |
||||
} |
||||
|
||||
private void assertAccountsMatch( |
||||
final WorldState worldState, final List<Account> expectedAccounts) { |
||||
for (Account expectedAccount : expectedAccounts) { |
||||
Account actualAccount = worldState.get(expectedAccount.getAddress()); |
||||
assertThat(actualAccount).isNotNull(); |
||||
// Check each field
|
||||
assertThat(actualAccount.getNonce()).isEqualTo(expectedAccount.getNonce()); |
||||
assertThat(actualAccount.getCode()).isEqualTo(expectedAccount.getCode()); |
||||
assertThat(actualAccount.getBalance()).isEqualTo(expectedAccount.getBalance()); |
||||
|
||||
Map<Bytes32, UInt256> actualStorage = actualAccount.storageEntriesFrom(Bytes32.ZERO, 500); |
||||
Map<Bytes32, UInt256> expectedStorage = expectedAccount.storageEntriesFrom(Bytes32.ZERO, 500); |
||||
assertThat(actualStorage).isEqualTo(expectedStorage); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,36 @@ |
||||
/* |
||||
* Copyright 2019 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.trie; |
||||
|
||||
import tech.pegasys.pantheon.util.bytes.BytesValue; |
||||
|
||||
import java.util.Optional; |
||||
import java.util.function.Function; |
||||
|
||||
public class TrieNodeDecoder { |
||||
|
||||
private final StoredNodeFactory<BytesValue> nodeFactory; |
||||
|
||||
private TrieNodeDecoder() { |
||||
nodeFactory = |
||||
new StoredNodeFactory<>((h) -> Optional.empty(), Function.identity(), Function.identity()); |
||||
} |
||||
|
||||
public static TrieNodeDecoder create() { |
||||
return new TrieNodeDecoder(); |
||||
} |
||||
|
||||
public Node<BytesValue> decode(final BytesValue rlp) { |
||||
return nodeFactory.decode(rlp); |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
/* |
||||
* 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. |
||||
*/ |
||||
|
||||
apply plugin: 'java-library' |
||||
|
||||
jar { |
||||
baseName 'pantheon-queue' |
||||
manifest { |
||||
attributes( |
||||
'Specification-Title': baseName, |
||||
'Specification-Version': project.version, |
||||
'Implementation-Title': baseName, |
||||
'Implementation-Version': calculateVersion() |
||||
) |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
api project(':util') |
||||
implementation project(':metrics') |
||||
|
||||
implementation 'org.apache.logging.log4j:log4j-api' |
||||
implementation 'com.google.guava:guava' |
||||
|
||||
runtime 'org.apache.logging.log4j:log4j-core' |
||||
|
||||
testImplementation 'junit:junit' |
||||
} |
@ -0,0 +1,33 @@ |
||||
/* |
||||
* 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.services.queue; |
||||
|
||||
import java.io.Closeable; |
||||
|
||||
/** |
||||
* Represents a very large thread-safe queue that may exceed memory limits. |
||||
* |
||||
* @param <T> the type of data held in the queue |
||||
*/ |
||||
public interface BigQueue<T> extends Closeable { |
||||
|
||||
void enqueue(T value); |
||||
|
||||
T dequeue(); |
||||
|
||||
long size(); |
||||
|
||||
default boolean isEmpty() { |
||||
return size() == 0; |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
/* |
||||
* Copyright 2019 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.services.queue; |
||||
|
||||
import java.util.Queue; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
|
||||
public class InMemoryBigQueue<T> implements BigQueue<T> { |
||||
private final Queue<T> internalQueue = new ConcurrentLinkedQueue<>(); |
||||
|
||||
@Override |
||||
public void enqueue(final T value) { |
||||
internalQueue.add(value); |
||||
} |
||||
|
||||
@Override |
||||
public T dequeue() { |
||||
return internalQueue.poll(); |
||||
} |
||||
|
||||
@Override |
||||
public long size() { |
||||
return internalQueue.size(); |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
internalQueue.clear(); |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<Configuration status="INFO" monitorInterval="30"> |
||||
<Properties> |
||||
<Property name="root.log.level">INFO</Property> |
||||
</Properties> |
||||
<Appenders> |
||||
<Console name="Console" target="SYSTEM_OUT"> |
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSSZZZ} [%t] %-5level %c{1.} %msg%n" /> |
||||
</Console> |
||||
</Appenders> |
||||
<Loggers> |
||||
<Root level="${sys:root.log.level}"> |
||||
<AppenderRef ref="Console" /> |
||||
</Root> |
||||
</Loggers> |
||||
</Configuration> |
Loading…
Reference in new issue