Skip to content

Commit

Permalink
fix stateroot during leaf update
Browse files Browse the repository at this point in the history
Signed-off-by: Karim Taam <[email protected]>
  • Loading branch information
matkt committed Nov 5, 2024
1 parent c14d4eb commit 4eee471
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,10 @@ private List<Bytes> getStemExtensions() {
int depth = location.size();
List<Bytes> extensions = new ArrayList<>();
for (Node<V> childNode : getChildren()) {
if (childNode instanceof StemNode) {
if (childNode instanceof StemNode<V>) {
extensions.add(((StemNode<V>) childNode).getStem().slice(depth));
} else if (childNode instanceof StoredStemNode<V>) {
extensions.add(((StoredStemNode<V>) childNode).getStem().slice(depth));
}
}
return extensions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public Node<V> accept(NodeVisitor<V> visitor) {
return node.accept(visitor);
}

public Bytes getStem() {
return stem;
}

/**
* Get a string representation of the node.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,9 @@ public Node<V> visit(final StemNode<V> stemNode, final Bytes path) {
public Node<V> visit(final LeafNode<V> leafNode, final Bytes path) {
assert path.size() < 33;
LeafNode<V> newNode;
oldValue = leafNode.getValue();
if (oldValue != value) {
newNode =
new LeafNode<>(
leafNode.getLocation(), value, leafNode.isPersisted() ? oldValue : Optional.empty());
oldValue = leafNode.isPersisted() ? leafNode.getValue() : leafNode.getPrevious();
if (leafNode.getValue() != value) {
newNode = new LeafNode<>(leafNode.getLocation(), value, oldValue);
batchProcessor.ifPresent(
processor -> processor.addNodeToBatch(newNode.getLocation(), newNode));
newNode.markDirty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright Hyperledger Besu Contributors
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.ethereum.trie.verkle;

import org.hyperledger.besu.ethereum.trie.verkle.factory.StoredNodeFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class ReloadTrieTest {

private static VerkleTrieBatchHasher batchProcessor;
private static StoredNodeFactory<Bytes> nodeFactory;
private static NodeUpdaterMock nodeUpdater;

private static Stream<Arguments> provideGenesisAndStateRootExpected() {
return Stream.of(
Arguments.of(
createDataMap(
"/gen-devnet-7.csv",
"0x514a0e5715b0b6c635ac140a5f25b8665af36cf31836344d27d9645fc57eab76")),
Arguments.of(
createDataMap(
"/devnet-7-block-1.csv",
"0x5ac6bdb5f3dc47cfa7665c981f795a1116459dfeecde174433d14d710c433cc4")),
Arguments.of(
createDataMap(
"/devnet-7-block-2.csv",
"0x2a63a94a9cb5dc948f98bd9a18cd4db0214adbc7cef2297a5f84362b14cb6513")),
Arguments.of(
createDataMap(
"/devnet-7-block-3.csv",
"0x4818200283aff9e7fd8a788c28bd6e54a6545a6f3af9e8d75a784c43a4b75fbe")),
Arguments.of(
createDataMap(
"/devnet-7-block-4.csv",
"0x25d529a8a345af4db9e0987fbaea65a17632835180e3a1fb8eeed93743996dc9")),
Arguments.of(
createDataMap(
"/devnet-7-block-5.csv",
"0x19eefd036a31289e338bec949d777ad56c6e7e12e769399a1b1322e601f282cf")),
Arguments.of(
createDataMap(
"/devnet-7-block-6.csv",
"0x0338f6cf3d3087bd33b1d47960f39c2a75a5bc060cf56fcb31ab6f0203bd6866")),
Arguments.of(
createDataMap(
"/devnet-7-block-7.csv",
"0x3a897a8bbc000434f83a9a53e80916fb75bc232497284ccdc3e5c9643f9fc7fc")));
}

private static Map<String, String> createDataMap(String filePath, String expectedRoot) {
Map<String, String> data = new HashMap<>();
data.put("filePath", filePath);
data.put("expectedRoot", expectedRoot);
return data;
}

@BeforeAll
public static void setUp() {
nodeUpdater = new NodeUpdaterMock();
final NodeLoaderMock nodeLoader = new NodeLoaderMock(nodeUpdater.storage);
batchProcessor = new VerkleTrieBatchHasher();
nodeFactory = new StoredNodeFactory<>(nodeLoader, value -> value);
}

@ParameterizedTest
@MethodSource("provideGenesisAndStateRootExpected")
public void reloadTrieTest(final Map<String, String> data) throws IOException {
final String genesisCSVFile = data.get("filePath");
final String expectedRoot = data.get("expectedRoot");

StoredBatchedVerkleTrie<Bytes32, Bytes> trie =
new StoredBatchedVerkleTrie<>(batchProcessor, nodeFactory);
InputStream input = ReloadTrieTest.class.getResourceAsStream(genesisCSVFile);
try (Reader reader = new InputStreamReader(input, "UTF-8");
CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT); ) {
for (CSVRecord csvRecord : csvParser) {
Bytes32 key = Bytes32.fromHexString(csvRecord.get(0));
Bytes value = Bytes.fromHexString(csvRecord.get(1));
trie.put(key, value);
}
}
trie.commit(nodeUpdater);

Assertions.assertThat(trie.getRootHash())
.isEqualByComparingTo(Bytes32.fromHexString(expectedRoot));
}
}
3 changes: 3 additions & 0 deletions src/test/resources/devnet-7-block-1.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad700,0x0000000000000000000000000000000000000000000000000000000000000000
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad701,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad740,0xd3223e707747b853db863c5020016d2bbad1896abb16fadb522eee3ef6f8a0af
1 change: 1 addition & 0 deletions src/test/resources/devnet-7-block-2.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad741,0xb675fdc29884458a36f2e4df2439d6076a1d963169a1c6a261958bc8064141d6
8 changes: 8 additions & 0 deletions src/test/resources/devnet-7-block-3.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
0x242271cf1aaa13ede9bb0a1550d6f181c6135afb92be8270221f03cc8a721e00,0x00000000000000000000000000000000000000000000000000b5303ad38b8000
0x242271cf1aaa13ede9bb0a1550d6f181c6135afb92be8270221f03cc8a721e01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad742,0xb4bebc9af4e95d4d5cdc052efe78bca155e1f23c18cb89754fba998da964850d
0x96ed99018bcdb2439b664559f70a95ec161cfc6ef2b8e1b42ff61733e87f8e00,0x000000000000000000000000000000000000000000000000000032ceaf08eed0
0x96ed99018bcdb2439b664559f70a95ec161cfc6ef2b8e1b42ff61733e87f8e01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xd5f60a22f578b01222216496e3322676c2bd1a27f4814fff924ff5795b9dae00,0x0000000000000000000000000000000200000000033b2e3c9e65d3617abfc000
0xdf67dea9181141d6255ac05c7ada5a590fb30a375023f16c31223f067319e300,0x00000000000000000000000000000000000000000000000000b5303ad38b8000
0xdf67dea9181141d6255ac05c7ada5a590fb30a375023f16c31223f067319e301,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
28 changes: 28 additions & 0 deletions src/test/resources/devnet-7-block-4.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
0xd57b8a69aeb2f9a903ddcfd9369ba0f915d320b7d57a6f2e859353928493b101,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xd57b8a69aeb2f9a903ddcfd9369ba0f915d320b7d57a6f2e859353928493b100,0x0000000000000000000000000000000100000000000000000000000000000000
0x96ed99018bcdb2439b664559f70a95ec161cfc6ef2b8e1b42ff61733e87f8e00,0x0000000000000000000000000000000000000000000000000014680f9d8d592c
0x242271cf1aaa13ede9bb0a1550d6f181c6135afb92be8270221f03cc8a721e00,0x00000000000000000000000000000032000000000000000000a4fda9d59e70d5
0xdf67dea9181141d6255ac05c7ada5a590fb30a375023f16c31223f067319e300,0x00000000000000000000000000000032000000000000000000a5ecf2b4eceac9
0xc0237e3ff2222174a4406e598b9083eca7ac5394cfce7794dea7339c444f8b01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xc0237e3ff2222174a4406e598b9083eca7ac5394cfce7794dea7339c444f8b00,0x0000000000000000000000000000000100000000000000000000000000000000
0x52ca948195944cfa735a1d2def19f04a938295941eb624ead196fde83d135100,0x0000000000000000000000000000000100000000000000000000000000000000
0x52ca948195944cfa735a1d2def19f04a938295941eb624ead196fde83d135101,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x7b043edef79253e864b2098976fb926c98fd5bc55a765211526eb83a9edac700,0x0000000000000000000000000000000100000000000000000000000000000000
0x7b043edef79253e864b2098976fb926c98fd5bc55a765211526eb83a9edac701,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x3b5d2025a2d86757f10dcd793dd52d3ef008522116d9491c86def85241e40000,0x0000000000000000000000000000000100000000000000000000000000000000
0x3b5d2025a2d86757f10dcd793dd52d3ef008522116d9491c86def85241e40001,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad743,0xe72df0e23ccf3f7d961611cdb358dd77eeacfd5874c596a9b6c289bbe24eaebc
0x66cbd1783205798606ddde3c7ab71a2daba2243aa2066eeb597183f01cd30701,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x66cbd1783205798606ddde3c7ab71a2daba2243aa2066eeb597183f01cd30700,0x0000000000000000000000000000000100000000000000000000000000000000
0xa36637bb6dbdc343cbfb4efcbd788184db76340f71cb11f6a6be3b11c8709601,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xa36637bb6dbdc343cbfb4efcbd788184db76340f71cb11f6a6be3b11c8709600,0x0000000000000000000000000000000100000000000000000000000000000000
0x83600cb74dcd5bbff816546902a3e4e32a7731099720231b5b76361f7d98da01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x83600cb74dcd5bbff816546902a3e4e32a7731099720231b5b76361f7d98da00,0x0000000000000000000000000000000100000000000000000000000000000000
0x47f6c383b84d7a69c294c3ce8e2bb9b51822ac95e6f3687d0fc7955df2a43e00,0x0000000000000000000000000000000100000000000000000000000000000000
0x47f6c383b84d7a69c294c3ce8e2bb9b51822ac95e6f3687d0fc7955df2a43e01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xef4c4dab0e00ab80bd6029806f621755905462eccabeed4f39b2e0172b493000,0x0000000000000000000000000000000100000000000000000000000000000000
0xef4c4dab0e00ab80bd6029806f621755905462eccabeed4f39b2e0172b493001,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xfe75ef610182c86a9ddfe420cffb65b8d0be49c67fdece92607e7d061577e801,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xfe75ef610182c86a9ddfe420cffb65b8d0be49c67fdece92607e7d061577e800,0x0000000000000000000000000000000100000000000000000000000000000000
0x93ccc349d514f5d732690d06c5b5a626f83a91e9f06a42dbd0e6e8c116f82101,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x93ccc349d514f5d732690d06c5b5a626f83a91e9f06a42dbd0e6e8c116f82100,0x0000000000000000000000000000000100000000000000000000000000000000
1 change: 1 addition & 0 deletions src/test/resources/devnet-7-block-5.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad744,0xd22dde2b2e4198afd930db12a9affe5d0b4f887e912c3264db2629bc15c5c653
5 changes: 5 additions & 0 deletions src/test/resources/devnet-7-block-6.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
0x242271cf1aaa13ede9bb0a1550d6f181c6135afb92be8270221f03cc8a721e00,0x000000000000000000000000000000320000000000000000015a2de4a929f0d5
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad745,0x648b78057f87b8ee85a161c46c0f647290210489cab795d1d54e7c015f33ed16
0x96ed99018bcdb2439b664559f70a95ec161cfc6ef2b8e1b42ff61733e87f8e00,0x000000000000000000000000000000000000000000000000001490dc53a1b90c
0xd5f60a22f578b01222216496e3322676c2bd1a27f4814fff924ff5795b9dae00,0x0000000000000000000000000000000400000000033b2e3c9cfb37ea5894d8e0
0xdf67dea9181141d6255ac05c7ada5a590fb30a375023f16c31223f067319e300,0x000000000000000000000000000000320000000000000000015b1d2d88786ac9
42 changes: 42 additions & 0 deletions src/test/resources/devnet-7-block-7.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
0x06500b755e95a1cae7c0380b81eefc1a6c637365d23ab04ac9ff659e27aa6a00,0x0000000000000000000000000000000100000000000000000000000000000000
0x06500b755e95a1cae7c0380b81eefc1a6c637365d23ab04ac9ff659e27aa6a01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x8dc3f7b978faffc53b48dc1da4657a7e01462a7dbab22ec4ddc602afe2803f00,0x0000000000000000000000000000000100000000000000000000000000000000
0x8dc3f7b978faffc53b48dc1da4657a7e01462a7dbab22ec4ddc602afe2803f01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x638447632f1434844a3c4c6bd69fc55edf3048b15b7ac50de3b84ce2e511c001,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x638447632f1434844a3c4c6bd69fc55edf3048b15b7ac50de3b84ce2e511c000,0x0000000000000000000000000000000100000000000000000000000000000000
0xb60bd055097a6f49f74f423d7cfd348840c8a2557d5c13170b0472688a480100,0x0000000000000000000000000000000100000000000000000000000000000000
0xb60bd055097a6f49f74f423d7cfd348840c8a2557d5c13170b0472688a480101,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x7cfc621bdde2e832c68726d675ed941a033d9aaeeadb918939028b61092ed100,0x0000000000000000000000000000000100000000000000000000000000000000
0x7cfc621bdde2e832c68726d675ed941a033d9aaeeadb918939028b61092ed101,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x96ed99018bcdb2439b664559f70a95ec161cfc6ef2b8e1b42ff61733e87f8e00,0x0000000000000000000000000000000000000000000000000027deb4f19b4d90
0xa0905023569f2bba0ac978b6066eab3940d9288a228e485c1033a43ef12ae501,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xa0905023569f2bba0ac978b6066eab3940d9288a228e485c1033a43ef12ae500,0x0000000000000000000000000000000100000000000000000000000000000000
0xf4afa2ee16c146e5ec91393b06db13c60219af25bf9b11022efa6ba43479f400,0x0000000000000000000000000000000100000000000000000000000000000000
0xf4afa2ee16c146e5ec91393b06db13c60219af25bf9b11022efa6ba43479f401,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xb843c715717afa4726b6cd8e429e6d012e91c7cf0742150d6ac170d89c67b800,0x0000000000000000000000000000000100000000000000000000000000000000
0xb843c715717afa4726b6cd8e429e6d012e91c7cf0742150d6ac170d89c67b801,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x9fd425188b9917b41b2afe93a7b993f3c1ec96069a3f0966f5fd1c98da87a501,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x9fd425188b9917b41b2afe93a7b993f3c1ec96069a3f0966f5fd1c98da87a500,0x0000000000000000000000000000000100000000000000000000000000000000
0x245a0401e4bb329a37599f06f0ecaa264009fa9fe204f68c2ef70072df4e2301,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x245a0401e4bb329a37599f06f0ecaa264009fa9fe204f68c2ef70072df4e2300,0x0000000000000000000000000000000100000000000000000000000000000000
0x242271cf1aaa13ede9bb0a1550d6f181c6135afb92be8270221f03cc8a721e00,0x000000000000000000000000000000640000000000000000014c49efc39d57a9
0xdf67dea9181141d6255ac05c7ada5a590fb30a375023f16c31223f067319e300,0x000000000000000000000000000000640000000000000000014df2b2e51e2771
0x677e2dc09e1b6c4e9189de4b693d02bc5663b3d4d21b5466580bd1a01d300500,0x0000000000000000000000000000000100000000000000000000000000000000
0x677e2dc09e1b6c4e9189de4b693d02bc5663b3d4d21b5466580bd1a01d300501,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xf28ad2ca0f9269815a6781e07ece3491bd3cfec6d7600f51a2d8f1cde9dd3001,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xf28ad2ca0f9269815a6781e07ece3491bd3cfec6d7600f51a2d8f1cde9dd3000,0x0000000000000000000000000000000100000000000000000000000000000000
0x465a7870a11d3d7d1ccfe492d4e9d346d14ba90d4c25365df0ee3f87e8201701,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x465a7870a11d3d7d1ccfe492d4e9d346d14ba90d4c25365df0ee3f87e8201700,0x0000000000000000000000000000000100000000000000000000000000000000
0x2978696828257c5a2366ac963c76601827ad3a5c63ea3a069395b0aba92cb600,0x0000000000000000000000000000000100000000000000000000000000000000
0x2978696828257c5a2366ac963c76601827ad3a5c63ea3a069395b0aba92cb601,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xa7e216cd20afae7770abdc3a2292ef9f5c47e35e064d627c0e352313d22d2100,0x0000000000000000000000000000000100000000000000000000000000000000
0xa7e216cd20afae7770abdc3a2292ef9f5c47e35e064d627c0e352313d22d2101,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xcdb13af8ff1652748b969bc69a877c79ef7a31615aea3ee94796b57d7a8e8600,0x0000000000000000000000000000000100000000000000000000000000000000
0xcdb13af8ff1652748b969bc69a877c79ef7a31615aea3ee94796b57d7a8e8601,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x5b5fdfedd6a0e932da408ac7d772a36513d1eee9b9926e52620c43a433aad746,0x8f501d9292824248a829d498611d29efd7ccc730bf6e5bef7e3a9ad18c349bea
0xdcfecdcf8f71103fa8c135082bacf1bde458bc854cd5bca2d6da8c32e7ef2701,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0xdcfecdcf8f71103fa8c135082bacf1bde458bc854cd5bca2d6da8c32e7ef2700,0x0000000000000000000000000000000100000000000000000000000000000000
0x0c75ad1c00f4cfd77db95255f396f2c7682f4c9a72141a94e0c7f2e8d9b5fe01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
0x0c75ad1c00f4cfd77db95255f396f2c7682f4c9a72141a94e0c7f2e8d9b5fe00,0x0000000000000000000000000000000100000000000000000000000000000000
0x2356616187a55137aa6299d3b34a8e8bc2b83eb1a0d88b08b13db9ddb5af0d00,0x0000000000000000000000000000000100000000000000000000000000000000
0x2356616187a55137aa6299d3b34a8e8bc2b83eb1a0d88b08b13db9ddb5af0d01,0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470

0 comments on commit 4eee471

Please sign in to comment.