Skip to content

Commit aed80db

Browse files
committed
feat(snap): download state leaves only and build state trie on client side
1 parent cc2a1c6 commit aed80db

25 files changed

+935
-26
lines changed

rskj-core/src/integrationTest/java/co/rsk/snap/SnapshotSyncIntegrationTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
*/
1919
package co.rsk.snap;
2020

21+
import co.rsk.core.RskAddress;
2122
import co.rsk.util.*;
2223
import co.rsk.util.cli.NodeIntegrationTestCommandLine;
2324
import com.fasterxml.jackson.databind.JsonNode;
2425
import com.squareup.okhttp.Response;
2526
import org.apache.commons.lang3.StringUtils;
2627
import org.apache.commons.lang3.tuple.ImmutablePair;
2728
import org.apache.commons.lang3.tuple.Pair;
29+
import org.ethereum.crypto.ECKey;
2830
import org.junit.jupiter.api.AfterEach;
2931
import org.junit.jupiter.api.Test;
3032
import org.junit.jupiter.api.io.TempDir;
@@ -170,14 +172,20 @@ private String readServerNodeId(Path serverDatabasePath) throws IOException {
170172
private void generateBlocks() throws IOException {
171173
List<String> accounts = OkHttpClientTestFixture.PRE_FUNDED_ACCOUNTS;
172174
Random rand = new Random(111);
175+
int bulkSize = 10;
173176

174177
for (int i = 0; i < 1001; i++) {
175-
OkHttpClientTestFixture.FromToAddressPair[] pairs = IntStream.range(0, 10)
176-
.mapToObj(n -> of(accounts.get(rand.nextInt(accounts.size())), accounts.get(rand.nextInt(accounts.size()))))
178+
int bulkPos = bulkSize * i;
179+
OkHttpClientTestFixture.FromToAddressPair[] pairs = IntStream.range(0, bulkSize)
180+
.mapToObj(n -> of(accounts.get(rand.nextInt(accounts.size())), accountFromSeed("cow #" + bulkPos + n)))
177181
.toArray(OkHttpClientTestFixture.FromToAddressPair[]::new);
178182
Response response = OkHttpClientTestFixture.sendBulkTransactions(portServerRpc, pairs);
179183
assertTrue(response.isSuccessful());
180184
response.body().close();
181185
}
182186
}
187+
188+
private static String accountFromSeed(String seed) {
189+
return "0x" + new RskAddress(ECKey.fromPrivate(seed.getBytes()).getAddress());
190+
}
183191
}

rskj-core/src/integrationTest/java/co/rsk/util/OkHttpClientTestFixture.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ public static Response sendBulkTransactions(int rpcPort, FromToAddressPair... fr
163163
Objects.requireNonNull(fromToAddresses);
164164

165165
String gas = "0x9C40";
166-
String gasPrice = "0x10";
167-
String value = "0x500";
166+
String gasPrice = "0x1";
167+
String value = "0x1";
168168

169169
String[] placeholders = new String[]{
170170
"<ADDRESS_FROM>", "<ADDRESS_TO>", "<GAS>",

rskj-core/src/main/java/co/rsk/RskContext.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,6 +2090,7 @@ private SnapshotProcessor getSnapshotProcessor() {
20902090
getPeersInformation(),
20912091
getBlockStore(),
20922092
getTransactionPool(),
2093+
getStateRootHandler(),
20932094
getSnapBlockParentDependantValidationRule(),
20942095
getSnapBlockValidationRule(),
20952096
new BlockHeaderParentCompositeRule(

rskj-core/src/main/java/co/rsk/metrics/profilers/MetricKind.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,6 @@ public enum MetricKind {
6060
SNAP_BLOCKS_RESPONSE,
6161
SNAP_STATE_CHUNK_REQUEST,
6262
SNAP_STATE_CHUNK_RESPONSE,
63+
SNAP_STATE_V2_CHUNK_REQUEST,
64+
SNAP_STATE_V2_CHUNK_RESPONSE,
6365
}

rskj-core/src/main/java/co/rsk/net/SnapshotProcessor.java

Lines changed: 222 additions & 9 deletions
Large diffs are not rendered by default.

rskj-core/src/main/java/co/rsk/net/SyncProcessor.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,19 @@ public void processStateChunkResponse(Peer peer, SnapStateChunkResponseMessage r
264264
}
265265
}
266266

267+
public void processStateChunkResponse(Peer peer, SnapStateChunkV2ResponseMessage responseMessage) {
268+
peersInformation.getOrRegisterPeer(peer);
269+
270+
long messageId = responseMessage.getId();
271+
MessageType messageType = responseMessage.getMessageType();
272+
if (isPending(messageId, messageType)) {
273+
removePendingMessage(messageId, messageType);
274+
syncState.onSnapStateChunk(peer, responseMessage);
275+
} else {
276+
notifyUnexpectedMessageToPeerScoring(peer, "snap state chunk");
277+
}
278+
}
279+
267280
@Override
268281
public void sendSkeletonRequest(Peer peer, long height) {
269282
logger.debug("Send skeleton request to node {} height {}", peer.getPeerNodeID(), height);
@@ -318,7 +331,7 @@ public void startBlockForwardSyncing(Peer peer) {
318331

319332
@Override
320333
public void startSnapCapablePeerSelection() {
321-
logger.info("Start peer selection");
334+
logger.info("Start snap peer selection");
322335
setSyncState(new SnapCapablePeerSelectionSyncState(this, syncConfiguration, peersInformation, blockHeaderValidationRule, difficultyRule));
323336
}
324337

rskj-core/src/main/java/co/rsk/net/messages/MessageType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,18 @@ public Message createMessage(BlockFactory blockFactory, RLPList list) {
294294
return SnapBlocksResponseMessage.decodeMessage(blockFactory, list);
295295
}
296296
},
297+
SNAP_STATE_CHUNK_V2_REQUEST_MESSAGE(26) {
298+
@Override
299+
public Message createMessage(BlockFactory blockFactory, RLPList list) {
300+
return SnapStateChunkV2RequestMessage.decodeMessage(list);
301+
}
302+
},
303+
SNAP_STATE_CHUNK_V2_RESPONSE_MESSAGE(27) {
304+
@Override
305+
public Message createMessage(BlockFactory blockFactory, RLPList list) {
306+
return SnapStateChunkV2ResponseMessage.decodeMessage(list);
307+
}
308+
},
297309
;
298310

299311
private final int type;

rskj-core/src/main/java/co/rsk/net/messages/MessageVisitor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,16 @@ public void apply(SnapStateChunkResponseMessage message) {
219219
this.syncProcessor.processStateChunkResponse(sender, message);
220220
}
221221

222+
public void apply(SnapStateChunkV2RequestMessage message) {
223+
logger.debug("snapshot chunk v2 request : {}", message.getId());
224+
this.snapshotProcessor.processStateChunkRequest(sender, message);
225+
}
226+
227+
public void apply(SnapStateChunkV2ResponseMessage message) {
228+
logger.debug("snapshot chunk v2 response : {}", message.getId());
229+
this.syncProcessor.processStateChunkResponse(sender, message);
230+
}
231+
222232
public void apply(TransactionsMessage message) {
223233
if (blockProcessor.hasBetterBlockToSync()) {
224234
loggerMessageProcess.debug("Message[{}] not processed.", message.getMessageType());
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* This file is part of RskJ
3+
* Copyright (C) 2025 RSK Labs Ltd.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
package co.rsk.net.messages;
20+
21+
import org.bouncycastle.util.BigIntegers;
22+
import org.ethereum.util.RLP;
23+
import org.ethereum.util.RLPList;
24+
25+
public class SnapStateChunkV2RequestMessage extends MessageWithId {
26+
27+
private final long id;
28+
29+
private final byte[] blockHash;
30+
31+
private final byte[] fromKey;
32+
33+
public SnapStateChunkV2RequestMessage(long id, byte[] blockHash, byte[] fromKey) {
34+
this.id = id;
35+
this.blockHash = blockHash;
36+
this.fromKey = fromKey;
37+
}
38+
39+
@Override
40+
public long getId() {
41+
return this.id;
42+
}
43+
44+
public byte[] getBlockHash() {
45+
return blockHash;
46+
}
47+
48+
public byte[] getFromKey() {
49+
return fromKey;
50+
}
51+
52+
@Override
53+
protected byte[] getEncodedMessageWithoutId() {
54+
byte[] rlpBlockHash = RLP.encodeElement(this.blockHash);
55+
byte[] rlpFromKey = RLP.encodeElement(this.fromKey);
56+
return RLP.encodeList(rlpBlockHash, rlpFromKey);
57+
}
58+
59+
@Override
60+
public MessageType getMessageType() {
61+
return MessageType.SNAP_STATE_CHUNK_V2_REQUEST_MESSAGE;
62+
}
63+
64+
@Override
65+
public MessageType getResponseMessageType() {
66+
return MessageType.SNAP_STATE_CHUNK_V2_RESPONSE_MESSAGE;
67+
}
68+
69+
@Override
70+
public void accept(MessageVisitor v) {
71+
v.apply(this);
72+
}
73+
74+
public static Message decodeMessage(RLPList list) {
75+
byte[] rlpId = list.get(0).getRLPData();
76+
77+
long id = rlpId == null ? 0 : BigIntegers.fromUnsignedByteArray(rlpId).longValue();
78+
79+
RLPList message = (RLPList)RLP.decode2(list.get(1).getRLPData()).get(0);
80+
byte[] rlpBlockHash = message.get(0).getRLPData();
81+
byte[] rlpFromKey = message.get(1).getRLPData();
82+
83+
return new SnapStateChunkV2RequestMessage(id, rlpBlockHash, rlpFromKey);
84+
}
85+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* This file is part of RskJ
3+
* Copyright (C) 2025 RSK Labs Ltd.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Lesser General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
package co.rsk.net.messages;
20+
21+
import co.rsk.trie.TrieChunk;
22+
import org.bouncycastle.util.BigIntegers;
23+
import org.ethereum.util.RLP;
24+
import org.ethereum.util.RLPList;
25+
26+
public class SnapStateChunkV2ResponseMessage extends MessageWithId {
27+
28+
private final long id;
29+
30+
private final TrieChunk chunk;
31+
32+
public SnapStateChunkV2ResponseMessage(long id, TrieChunk chunk) {
33+
this.id = id;
34+
this.chunk = chunk;
35+
}
36+
37+
@Override
38+
public long getId() {
39+
return this.id;
40+
}
41+
42+
public TrieChunk getChunk() {
43+
return chunk;
44+
}
45+
46+
@Override
47+
protected byte[] getEncodedMessageWithoutId() {
48+
return chunk.encode();
49+
}
50+
51+
@Override
52+
public MessageType getMessageType() {
53+
return MessageType.SNAP_STATE_CHUNK_V2_RESPONSE_MESSAGE;
54+
}
55+
56+
@Override
57+
public void accept(MessageVisitor v) {
58+
v.apply(this);
59+
}
60+
61+
public static Message decodeMessage(RLPList list) {
62+
byte[] rlpId = list.get(0).getRLPData();
63+
64+
long id = rlpId == null ? 0 : BigIntegers.fromUnsignedByteArray(rlpId).longValue();
65+
66+
RLPList message = (RLPList)RLP.decode2(list.get(1).getRLPData()).get(0);
67+
68+
TrieChunk chunk = TrieChunk.decode(message);
69+
70+
return new SnapStateChunkV2ResponseMessage(id, chunk);
71+
}
72+
}

0 commit comments

Comments
 (0)