diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/HobbitsP2PNetwork.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/HobbitsP2PNetwork.java index 78adeb43578..df5d5655107 100644 --- a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/HobbitsP2PNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/HobbitsP2PNetwork.java @@ -119,21 +119,22 @@ private void connectStaticPeers() { private void receiveMessage(NetSocket netSocket) { URI peerURI = URI.create( - "hobs://" + netSocket.remoteAddress().host() + ":" + netSocket.remoteAddress().port()); + "hob+tcp://" + + netSocket.remoteAddress().host() + + ":" + + netSocket.remoteAddress().port()); handlersMap.computeIfAbsent( peerURI, uri -> { Peer peer = new Peer(peerURI); - HobbitsSocketHandler handler = - new HobbitsSocketHandler(netSocket, userAgent, peer, chainData); - return handler; + return new HobbitsSocketHandler(netSocket, userAgent, peer, chainData); }); } @Override public Collection getPeers() { return handlersMap.values().stream() - .map(handler -> handler.peer()) + .map(HobbitsSocketHandler::peer) .collect(Collectors.toList()); } diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockBodies.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockBodies.java new file mode 100644 index 00000000000..904363f5cb8 --- /dev/null +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockBodies.java @@ -0,0 +1,80 @@ +/* + * 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.artemis.networking.p2p.hobbits; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@JsonDeserialize(using = BlockBodies.BlockBodiesDeserializer.class) +final class BlockBodies { + + static class BlockBodiesDeserializer extends StdDeserializer { + + protected BlockBodiesDeserializer() { + super(BlockBodies.class); + } + + @Override + public BlockBodies deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + Iterator iterator = node.iterator(); + List elts = new ArrayList<>(); + while (iterator.hasNext()) { + JsonNode child = iterator.next(); + elts.add(new BlockBody()); + } + + return new BlockBodies(elts); + } + } + + static class BlockBody { + + /* + 'randao_reveal': 'bytes96', + 'eth1_data': Eth1Data, + 'proposer_slashings': [ProposerSlashing], + 'attester_slashings': [AttesterSlashing], + 'attestations': [Attestation], + 'deposits': [Deposit], + 'voluntary_exits': [VoluntaryExit], + 'transfers': [Transfer], + */ + + BlockBody() { + // TODO fill body with the right info + } + } + + private final List bodies; + + BlockBodies(List rootsAndSlots) { + this.bodies = rootsAndSlots; + } + + @JsonValue + public List bodies() { + return bodies; + } +} diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockHeaders.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockHeaders.java new file mode 100644 index 00000000000..9be7500bfa6 --- /dev/null +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockHeaders.java @@ -0,0 +1,118 @@ +/* + * 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.artemis.networking.p2p.hobbits; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import net.consensys.cava.bytes.Bytes; +import net.consensys.cava.bytes.Bytes32; + +@JsonDeserialize(using = BlockHeaders.BlockHeadersDeserializer.class) +final class BlockHeaders { + + static class BlockHeadersDeserializer extends StdDeserializer { + + protected BlockHeadersDeserializer() { + super(BlockHeaders.class); + } + + @Override + public BlockHeaders deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + Iterator iterator = node.iterator(); + List elts = new ArrayList<>(); + while (iterator.hasNext()) { + JsonNode child = iterator.next(); + elts.add( + new BlockHeader( + child.get("slot").asLong(), + Bytes32.fromHexString(child.get("previous_block_root").asText()), + Bytes32.fromHexString(child.get("state_root").asText()), + Bytes32.fromHexString(child.get("block_body_root").asText()), + Bytes.fromHexString(child.get("signature").asText()))); + } + + return new BlockHeaders(elts); + } + } + + static class BlockHeader { + + private final long slot; + private final Bytes32 previousBlockRoot; + private final Bytes32 stateRoot; + private final Bytes32 blockBodyRoot; + private final Bytes signature; + + BlockHeader( + long slot, + Bytes32 previousBlockRoot, + Bytes32 stateRoot, + Bytes32 blockBodyRoot, + Bytes signature) { + this.slot = slot; + this.previousBlockRoot = previousBlockRoot; + this.stateRoot = stateRoot; + this.blockBodyRoot = blockBodyRoot; + this.signature = signature; + } + + @JsonProperty("slot") + public long slot() { + return slot; + } + + @JsonProperty("previous_block_root") + public Bytes32 previousBlockRoot() { + return previousBlockRoot; + } + + @JsonProperty("state_root") + public Bytes32 stateRoot() { + return stateRoot; + } + + @JsonProperty("block_body_root") + public Bytes32 blockBodyRoot() { + return blockBodyRoot; + } + + @JsonProperty("signature") + public Bytes signature() { + return signature; + } + } + + private final List headers; + + BlockHeaders(List headers) { + this.headers = headers; + } + + @JsonValue + public List headers() { + return headers; + } +} diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockRoots.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockRoots.java new file mode 100644 index 00000000000..391300e4a17 --- /dev/null +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/BlockRoots.java @@ -0,0 +1,88 @@ +/* + * 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.artemis.networking.p2p.hobbits; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import net.consensys.cava.bytes.Bytes32; + +@JsonDeserialize(using = BlockRoots.BlockRootsDeserializer.class) +final class BlockRoots { + + static class BlockRootsDeserializer extends StdDeserializer { + + protected BlockRootsDeserializer() { + super(BlockRoots.class); + } + + @Override + public BlockRoots deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = jp.getCodec().readTree(jp); + Iterator iterator = node.iterator(); + List elts = new ArrayList<>(); + while (iterator.hasNext()) { + JsonNode child = iterator.next(); + elts.add( + new BlockRootAndSlot( + Bytes32.fromHexString(child.get("block_root").asText()), + child.get("slot").asLong())); + } + + return new BlockRoots(elts); + } + } + + static class BlockRootAndSlot { + + private final Bytes32 blockRoot; + private final long slot; + + BlockRootAndSlot(Bytes32 blockRoot, long slot) { + this.blockRoot = blockRoot; + this.slot = slot; + } + + @JsonProperty("block_root") + public Bytes32 blockRoot() { + return blockRoot; + } + + @JsonProperty("slot") + public long slot() { + return slot; + } + } + + private final List rootsAndSlots; + + BlockRoots(List rootsAndSlots) { + this.rootsAndSlots = rootsAndSlots; + } + + @JsonValue + public List rootsAndSlots() { + return rootsAndSlots; + } +} diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/HobbitsSocketHandler.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/HobbitsSocketHandler.java index 57010cc1909..6687f6c35f9 100644 --- a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/HobbitsSocketHandler.java +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/HobbitsSocketHandler.java @@ -16,6 +16,7 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.net.NetSocket; import java.time.Instant; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -80,6 +81,15 @@ private void handleRPCMessage(RPCMessage rpcMessage) { replyStatus(rpcMessage.requestId()); } peer.setPeerStatus(rpcMessage.bodyAs(GetStatus.class)); + } else if (RPCMethod.REQUEST_BLOCK_ROOTS.equals(rpcMessage.method())) { + // TODO provide data + sendReply(RPCMethod.BLOCK_ROOTS, new BlockRoots(new ArrayList<>()), rpcMessage.requestId()); + } else if (RPCMethod.REQUEST_BLOCK_HEADERS.equals(rpcMessage.method())) { + // TODO provide data + sendReply(RPCMethod.BLOCK_HEADERS, null, rpcMessage.requestId()); + } else if (RPCMethod.REQUEST_BLOCK_BODIES.equals(rpcMessage.method())) { + // TODO provide data + sendReply(RPCMethod.BLOCK_BODIES, null, rpcMessage.requestId()); } } diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCCodec.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCCodec.java index f7b59c57c66..5b40bb99741 100644 --- a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCCodec.java +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCCodec.java @@ -94,7 +94,7 @@ public Bytes deserialize(JsonParser p, DeserializationContext ctxt) } } - private static final class BytesModule extends SimpleModule { + static final class BytesModule extends SimpleModule { BytesModule() { super("bytes"); diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCMethod.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCMethod.java index 591a4bb0485..7d00f9fb147 100644 --- a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCMethod.java +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RPCMethod.java @@ -19,9 +19,11 @@ public enum RPCMethod { GOODBYE(1), GET_STATUS(2), REQUEST_BLOCK_ROOTS(10), - REQUEST_BLOCK_HEADERS(11), - REQUEST_BLOCK_BODIES(12), - REQUEST_STATE(13); + BLOCK_ROOTS(11), + REQUEST_BLOCK_HEADERS(12), + BLOCK_HEADERS(13), + REQUEST_BLOCK_BODIES(14), + BLOCK_BODIES(15); private int code; @@ -52,11 +54,15 @@ static RPCMethod valueOf(int code) { case 10: return REQUEST_BLOCK_ROOTS; case 11: - return REQUEST_BLOCK_HEADERS; + return BLOCK_ROOTS; case 12: - return REQUEST_BLOCK_BODIES; + return REQUEST_BLOCK_HEADERS; case 13: - return REQUEST_STATE; + return BLOCK_HEADERS; + case 14: + return REQUEST_BLOCK_BODIES; + case 15: + return BLOCK_BODIES; default: throw new IllegalArgumentException("Unsupported code " + code); } diff --git a/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RequestBlocks.java b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RequestBlocks.java new file mode 100644 index 00000000000..af36821c96a --- /dev/null +++ b/networking/p2p/src/main/java/tech/pegasys/artemis/networking/p2p/hobbits/RequestBlocks.java @@ -0,0 +1,70 @@ +/* + * 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.artemis.networking.p2p.hobbits; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import net.consensys.cava.bytes.Bytes32; + +final class RequestBlocks { + + private final Bytes32 startRoot; + private final long startSlot; + private final long max; + private final long skip; + private final boolean direction; + + @JsonCreator + RequestBlocks( + @JsonProperty("start_root") Bytes32 startRoot, + @JsonProperty("start_slot") long startSlot, + @JsonProperty("max") long max, + @JsonProperty("skip") long skip, + @JsonProperty("direction") int direction) { + this.startRoot = startRoot; + this.startSlot = startSlot; + this.max = max; + this.skip = skip; + this.direction = direction == 1; + } + + @JsonProperty("start_root") + public Bytes32 startRoot() { + return startRoot; + } + + @JsonProperty("start_slot") + public long startSlot() { + return startSlot; + } + + @JsonProperty("max") + public long max() { + return max; + } + + @JsonProperty("skip") + public long skip() { + return skip; + } + + public boolean direction() { + return direction; + } + + @JsonProperty("direction") + public int directionAsInt() { + return direction ? 1 : 0; + } +} diff --git a/networking/p2p/src/test/java/tech/pegasys/artemis/networking/p2p/hobbits/JsonRoundtripTest.java b/networking/p2p/src/test/java/tech/pegasys/artemis/networking/p2p/hobbits/JsonRoundtripTest.java index 5b455b93672..be871b1fc99 100644 --- a/networking/p2p/src/test/java/tech/pegasys/artemis/networking/p2p/hobbits/JsonRoundtripTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/artemis/networking/p2p/hobbits/JsonRoundtripTest.java @@ -15,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.Arrays; import net.consensys.cava.bytes.Bytes32; import net.consensys.cava.units.bigints.UInt64; import org.junit.jupiter.api.Test; @@ -39,4 +40,26 @@ void roundtripHello() throws Exception { assertEquals(hello.bestRoot(), read.bestRoot()); assertEquals(hello.networkId(), read.networkId()); } + + @Test + void roundtripRequestBlocks() throws Exception { + RequestBlocks requestBlocks = new RequestBlocks(Bytes32.random(), 123, 3, 2, 1); + byte[] value = RPCCodec.mapper.writerFor(RequestBlocks.class).writeValueAsBytes(requestBlocks); + RequestBlocks read = RPCCodec.mapper.readerFor(RequestBlocks.class).readValue(value); + assertEquals(requestBlocks.startRoot(), read.startRoot()); + assertEquals(requestBlocks.direction(), read.direction()); + } + + @Test + void roundtripBlockRoots() throws Exception { + BlockRoots roots = + new BlockRoots( + Arrays.asList( + new BlockRoots.BlockRootAndSlot(Bytes32.random(), 1223), + new BlockRoots.BlockRootAndSlot(Bytes32.random(), 2234))); + byte[] value = RPCCodec.mapper.writerFor(BlockRoots.class).writeValueAsBytes(roots); + BlockRoots read = RPCCodec.mapper.readerFor(BlockRoots.class).readValue(value); + assertEquals(2, read.rootsAndSlots().size()); + assertEquals(roots.rootsAndSlots().get(1).blockRoot(), read.rootsAndSlots().get(1).blockRoot()); + } }