diff --git a/src/main/java/com/limechain/chain/spec/Genesis.java b/src/main/java/com/limechain/chain/spec/Genesis.java index 19b00effb..2793be7c6 100644 --- a/src/main/java/com/limechain/chain/spec/Genesis.java +++ b/src/main/java/com/limechain/chain/spec/Genesis.java @@ -5,12 +5,10 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.google.protobuf.ByteString; import com.limechain.utils.StringUtils; -import lombok.Getter; import org.apache.tomcat.util.buf.HexUtils; import java.io.Serializable; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/src/main/java/com/limechain/exception/misc/RuntimeApiVersionException.java b/src/main/java/com/limechain/exception/misc/RuntimeApiVersionException.java new file mode 100644 index 000000000..8b67ac7c7 --- /dev/null +++ b/src/main/java/com/limechain/exception/misc/RuntimeApiVersionException.java @@ -0,0 +1,7 @@ +package com.limechain.exception.misc; + +public class RuntimeApiVersionException extends RuntimeException { + public RuntimeApiVersionException(String message) { + super(message); + } +} diff --git a/src/main/java/com/limechain/exception/transaction/TransactionValidationException.java b/src/main/java/com/limechain/exception/transaction/TransactionValidationException.java new file mode 100644 index 000000000..a0fe3a447 --- /dev/null +++ b/src/main/java/com/limechain/exception/transaction/TransactionValidationException.java @@ -0,0 +1,7 @@ +package com.limechain.exception.transaction; + +public class TransactionValidationException extends Exception { + public TransactionValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/limechain/network/Network.java b/src/main/java/com/limechain/network/Network.java index fa05159d6..be388b714 100644 --- a/src/main/java/com/limechain/network/Network.java +++ b/src/main/java/com/limechain/network/Network.java @@ -18,13 +18,11 @@ import com.limechain.network.protocol.sync.SyncService; import com.limechain.network.protocol.sync.pb.SyncMessage; import com.limechain.network.protocol.sync.pb.SyncMessage.BlockResponse; -import com.limechain.network.protocol.transactions.TransactionsService; +import com.limechain.network.protocol.transaction.TransactionsService; import com.limechain.network.protocol.warp.WarpSyncService; import com.limechain.network.protocol.warp.dto.WarpSyncResponse; -import com.limechain.rpc.server.AppBean; import com.limechain.storage.DBConstants; import com.limechain.storage.KVRepository; -import com.limechain.sync.warpsync.WarpSyncState; import com.limechain.utils.Ed25519Utils; import com.limechain.utils.StringUtils; import io.ipfs.multiaddr.MultiAddress; @@ -256,7 +254,7 @@ public void findPeers() { if (getPeersCount() >= REPLICATION) { log.log(Level.INFO, "Connections have reached replication factor(" + REPLICATION + "). " + - "No need to search for new ones yet."); + "No need to search for new ones yet."); return; } @@ -352,33 +350,27 @@ private boolean isPeerInvalid() { return false; } - public void handshakeBootNodes() { + public void blockAnnounceHandshakeBootNodes() { kademliaService.getBootNodePeerIds() .stream() .distinct() - .forEach(this::sendGrandpaHandshake); + .forEach(p -> new Thread(() -> blockAnnounceService.sendHandshake(this.host, p)).start()); } - private void sendGrandpaHandshake(PeerId peerId) { - //TODO: - // when using threads we connect to more than 10 peers, but have some unhandled exceptions, - // without them we connect to only 2 peers - new Thread(() -> - blockAnnounceService.sendHandshake(this.host, this.host.getAddressBook(), peerId) - ).start(); + public void handshakePeers() { + connectionManager.getPeerIds().forEach(peerId -> new Thread(() -> { + blockAnnounceService.sendHandshake(this.host, peerId); + grandpaService.sendHandshake(this.host, peerId); + transactionsService.sendHandshake(this.host, peerId); + }).start() + ); } @Scheduled(fixedRate = 5, initialDelay = 5, timeUnit = TimeUnit.MINUTES) - public void sendNeighbourMessages() { - if (!AppBean.getBean(WarpSyncState.class).isWarpSyncFinished()) { - return; - } - connectionManager.getPeerIds().forEach(peerId -> grandpaService.sendNeighbourMessage(this.host, peerId)); + public void sendMessagesToPeers() { + connectionManager.getPeerIds().forEach(peerId -> + grandpaService.sendNeighbourMessage(this.host, peerId)); connectionManager.getPeerIds().forEach(peerId -> transactionsService.sendTransactionsMessage(this.host, peerId)); } - - public void sendNeighbourMessage(PeerId peerId) { - grandpaService.sendNeighbourMessage(this.host, peerId); - } } diff --git a/src/main/java/com/limechain/network/kad/KademliaService.java b/src/main/java/com/limechain/network/kad/KademliaService.java index a848efd3e..bf80746bd 100644 --- a/src/main/java/com/limechain/network/kad/KademliaService.java +++ b/src/main/java/com/limechain/network/kad/KademliaService.java @@ -18,8 +18,8 @@ import org.peergos.protocol.dht.RamRecordStore; import java.util.ArrayList; -import java.util.List; import java.util.Arrays; +import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; diff --git a/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceProtocol.java b/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceProtocol.java index 49e1e6adb..8ee7dc153 100644 --- a/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceProtocol.java +++ b/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceProtocol.java @@ -11,9 +11,8 @@ import lombok.extern.java.Log; import org.jetbrains.annotations.NotNull; -import java.util.logging.Level; - import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; @Log public class BlockAnnounceProtocol extends ProtocolHandler { diff --git a/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceService.java b/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceService.java index de3d8221b..d14e0b485 100644 --- a/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceService.java +++ b/src/main/java/com/limechain/network/protocol/blockannounce/BlockAnnounceService.java @@ -1,7 +1,6 @@ package com.limechain.network.protocol.blockannounce; import com.limechain.network.protocol.NetworkService; -import io.libp2p.core.AddressBook; import io.libp2p.core.Host; import io.libp2p.core.PeerId; import lombok.extern.java.Log; @@ -12,13 +11,12 @@ public BlockAnnounceService(String protocolId) { this.protocol = new BlockAnnounce(protocolId, new BlockAnnounceProtocol()); } - public void sendHandshake(Host us, AddressBook addrs, PeerId peer) { - try{ - BlockAnnounceController controller = this.protocol.dialPeer(us, peer, addrs); + public void sendHandshake(Host us, PeerId peer) { + try { + BlockAnnounceController controller = this.protocol.dialPeer(us, peer, us.getAddressBook()); controller.sendHandshake(); - } catch (IllegalStateException e){ + } catch (IllegalStateException e) { log.warning("Error sending handshake request to peer " + peer); } - } } diff --git a/src/main/java/com/limechain/network/protocol/grandpa/GrandpaService.java b/src/main/java/com/limechain/network/protocol/grandpa/GrandpaService.java index 3254d75f1..21c63766b 100644 --- a/src/main/java/com/limechain/network/protocol/grandpa/GrandpaService.java +++ b/src/main/java/com/limechain/network/protocol/grandpa/GrandpaService.java @@ -15,6 +15,7 @@ @Log public class GrandpaService extends NetworkService { ConnectionManager connectionManager = ConnectionManager.getInstance(); + public GrandpaService(String protocolId) { this.protocol = new Grandpa(protocolId, new GrandpaProtocol()); } @@ -23,7 +24,7 @@ public GrandpaService(String protocolId) { * Sends a neighbour message to a peer. If there is no initiator stream opened with the peer, * sends a handshake instead. * - * @param us our host object + * @param us our host object * @param peerId message receiver */ public void sendNeighbourMessage(Host us, PeerId peerId) { @@ -40,8 +41,8 @@ private void sendNeighbourMessage(Stream stream) { controller.sendNeighbourMessage(); } - private void sendHandshake(Host us, PeerId peerId) { - try{ + public void sendHandshake(Host us, PeerId peerId) { + try { GrandpaController controller = this.protocol.dialPeer(us, peerId, us.getAddressBook()); controller.sendHandshake(); } catch (Exception e) { diff --git a/src/main/java/com/limechain/network/protocol/transactions/Transactions.java b/src/main/java/com/limechain/network/protocol/transaction/Transactions.java similarity index 84% rename from src/main/java/com/limechain/network/protocol/transactions/Transactions.java rename to src/main/java/com/limechain/network/protocol/transaction/Transactions.java index 260f3cc96..7a98b763b 100644 --- a/src/main/java/com/limechain/network/protocol/transactions/Transactions.java +++ b/src/main/java/com/limechain/network/protocol/transaction/Transactions.java @@ -1,4 +1,4 @@ -package com.limechain.network.protocol.transactions; +package com.limechain.network.protocol.transaction; import com.limechain.network.StrictProtocolBinding; diff --git a/src/main/java/com/limechain/network/protocol/transactions/TransactionsController.java b/src/main/java/com/limechain/network/protocol/transaction/TransactionsController.java similarity index 93% rename from src/main/java/com/limechain/network/protocol/transactions/TransactionsController.java rename to src/main/java/com/limechain/network/protocol/transaction/TransactionsController.java index 017dc2511..f85fd73f2 100644 --- a/src/main/java/com/limechain/network/protocol/transactions/TransactionsController.java +++ b/src/main/java/com/limechain/network/protocol/transaction/TransactionsController.java @@ -1,4 +1,4 @@ -package com.limechain.network.protocol.transactions; +package com.limechain.network.protocol.transaction; import io.libp2p.core.Stream; diff --git a/src/main/java/com/limechain/network/protocol/transactions/TransactionsEngine.java b/src/main/java/com/limechain/network/protocol/transaction/TransactionsEngine.java similarity index 65% rename from src/main/java/com/limechain/network/protocol/transactions/TransactionsEngine.java rename to src/main/java/com/limechain/network/protocol/transaction/TransactionsEngine.java index 57ed0b7d3..444e98e49 100644 --- a/src/main/java/com/limechain/network/protocol/transactions/TransactionsEngine.java +++ b/src/main/java/com/limechain/network/protocol/transaction/TransactionsEngine.java @@ -1,10 +1,20 @@ -package com.limechain.network.protocol.transactions; +package com.limechain.network.protocol.transaction; import com.limechain.exception.scale.ScaleEncodingException; +import com.limechain.exception.transaction.TransactionValidationException; import com.limechain.network.ConnectionManager; -import com.limechain.network.protocol.transactions.scale.TransactionsReader; -import com.limechain.network.protocol.transactions.scale.TransactionsWriter; +import com.limechain.network.protocol.transaction.scale.TransactionsReader; +import com.limechain.network.protocol.transaction.scale.TransactionsWriter; +import com.limechain.network.protocol.warp.dto.BlockHeader; +import com.limechain.rpc.server.AppBean; +import com.limechain.runtime.Runtime; +import com.limechain.storage.block.BlockState; import com.limechain.sync.warpsync.WarpSyncState; +import com.limechain.transaction.TransactionState; +import com.limechain.transaction.TransactionValidator; +import com.limechain.transaction.dto.Extrinsic; +import com.limechain.transaction.dto.ExtrinsicArray; +import com.limechain.transaction.dto.ValidTransaction; import io.emeraldpay.polkaj.scale.ScaleCodecReader; import io.emeraldpay.polkaj.scale.ScaleCodecWriter; import io.libp2p.core.PeerId; @@ -20,9 +30,18 @@ */ @Log public class TransactionsEngine { + private static final int HANDSHAKE_LENGTH = 1; - private final ConnectionManager connectionManager = ConnectionManager.getInstance(); + private final ConnectionManager connectionManager; + private final TransactionState transactionState; + private final TransactionValidator transactionValidator; + + public TransactionsEngine() { + connectionManager = ConnectionManager.getInstance(); + transactionState = AppBean.getBean(TransactionState.class); + transactionValidator = AppBean.getBean(TransactionValidator.class); + } /** * Handles an incoming request as follows: @@ -64,6 +83,7 @@ private void handleInitiatorStreamMessage(byte[] message, Stream stream) { } connectionManager.addTransactionsStream(stream); log.log(Level.INFO, "Received transactions handshake from " + peerId); + stream.writeAndFlush(new byte[]{}); //TODO Send valid transactions to the peer we received a handshake from } @@ -97,10 +117,29 @@ private void handleHandshake(PeerId peerId, Stream stream) { private void handleTransactionMessage(byte[] message, PeerId peerId) { ScaleCodecReader reader = new ScaleCodecReader(message); - byte[][] transactions = reader.read(new TransactionsReader()); - log.log(Level.INFO, "Received " + transactions.length + " transactions from Peer " + ExtrinsicArray transactions = reader.read(new TransactionsReader()); + log.log(Level.INFO, "Received " + transactions.getExtrinsics().length + " transactions from Peer " + peerId); - //TODO Add transactions to data + + for (int i = 0; i < transactions.getExtrinsics().length; i++) { + Extrinsic current = transactions.getExtrinsics()[i]; + + ValidTransaction validTransaction; + try { + validTransaction = transactionValidator.validateTransactions(current); + validTransaction.getIgnore().add(peerId); + } catch (TransactionValidationException e) { + log.warning("Error when validating transaction " + current.toString() + + " from protocol: " + e.getMessage()); + continue; + } + + if (transactionState.shouldAddToQueue(validTransaction)) { + transactionState.pushTransaction(validTransaction); + } else { + transactionState.addToPool(validTransaction); + } + } } /** @@ -124,14 +163,17 @@ public void writeHandshakeToStream(Stream stream, PeerId peerId) { */ public void writeTransactionsMessage(Stream stream, PeerId peerId) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); + //TODO Replace empty transaction messages once we have vlaidation working. try (ScaleCodecWriter writer = new ScaleCodecWriter(buf)) { - writer.write(new TransactionsWriter(), new byte[][]{new byte[]{}, new byte[]{}}); + writer.write(new TransactionsWriter(), new ExtrinsicArray(new Extrinsic[]{ + new Extrinsic(new byte[]{}), new Extrinsic(new byte[]{}) + })); } catch (IOException e) { throw new ScaleEncodingException(e); } log.log(Level.INFO, "Sending transaction message to peer " + peerId); - //TODO Send our transaction message + //TODO send transaction message containing non repetitive transactions for peer. } private boolean isHandshake(byte[] message) { diff --git a/src/main/java/com/limechain/network/protocol/transactions/TransactionsProtocol.java b/src/main/java/com/limechain/network/protocol/transaction/TransactionsProtocol.java similarity index 97% rename from src/main/java/com/limechain/network/protocol/transactions/TransactionsProtocol.java rename to src/main/java/com/limechain/network/protocol/transaction/TransactionsProtocol.java index 692fddedf..0c58bf6e6 100644 --- a/src/main/java/com/limechain/network/protocol/transactions/TransactionsProtocol.java +++ b/src/main/java/com/limechain/network/protocol/transaction/TransactionsProtocol.java @@ -1,4 +1,4 @@ -package com.limechain.network.protocol.transactions; +package com.limechain.network.protocol.transaction; import com.limechain.network.ConnectionManager; import com.limechain.network.encoding.Leb128LengthFrameDecoder; @@ -93,7 +93,6 @@ public void onException(Throwable cause) { connectionManager.closeTransactionsStream(stream); if (cause != null) { log.log(Level.WARNING, "Transactions Exception: " + cause.getMessage()); - cause.printStackTrace(); } else { log.log(Level.WARNING, "Transactions Exception with unknown cause"); } diff --git a/src/main/java/com/limechain/network/protocol/transactions/TransactionsService.java b/src/main/java/com/limechain/network/protocol/transaction/TransactionsService.java similarity index 88% rename from src/main/java/com/limechain/network/protocol/transactions/TransactionsService.java rename to src/main/java/com/limechain/network/protocol/transaction/TransactionsService.java index 95360240a..2f0a116c1 100644 --- a/src/main/java/com/limechain/network/protocol/transactions/TransactionsService.java +++ b/src/main/java/com/limechain/network/protocol/transaction/TransactionsService.java @@ -1,4 +1,4 @@ -package com.limechain.network.protocol.transactions; +package com.limechain.network.protocol.transaction; import com.limechain.network.ConnectionManager; import com.limechain.network.protocol.NetworkService; @@ -25,6 +25,7 @@ public TransactionsService(String protocolId) { * @param peerId message receiver */ public void sendTransactionsMessage(Host us, PeerId peerId) { + // TODO Network improvements Keep track of peers that we've notified about each transaction Optional.ofNullable(connectionManager.getPeerInfo(peerId)) .map(p -> p.getTransactionsStreams().getInitiator()) .ifPresentOrElse( @@ -37,7 +38,7 @@ private void sendTransactions(Stream stream) { //TODO Send transaction messages } - private void sendHandshake(Host us, PeerId peerId) { + public void sendHandshake(Host us, PeerId peerId) { try { TransactionsController controller = this.protocol.dialPeer(us, peerId, us.getAddressBook()); controller.sendHandshake(); diff --git a/src/main/java/com/limechain/network/protocol/transaction/scale/TransactionsReader.java b/src/main/java/com/limechain/network/protocol/transaction/scale/TransactionsReader.java new file mode 100644 index 000000000..26696b152 --- /dev/null +++ b/src/main/java/com/limechain/network/protocol/transaction/scale/TransactionsReader.java @@ -0,0 +1,19 @@ +package com.limechain.network.protocol.transaction.scale; + +import com.limechain.transaction.dto.Extrinsic; +import com.limechain.transaction.dto.ExtrinsicArray; +import io.emeraldpay.polkaj.scale.ScaleCodecReader; +import io.emeraldpay.polkaj.scale.ScaleReader; + +public class TransactionsReader implements ScaleReader { + + @Override + public ExtrinsicArray read(ScaleCodecReader reader) { + int size = reader.readCompactInt(); + Extrinsic[] transactions = new Extrinsic[size]; + for (int i = 0; i < size; i++) { + transactions[i] = new Extrinsic(reader.readByteArray()); + } + return new ExtrinsicArray(transactions); + } +} diff --git a/src/main/java/com/limechain/network/protocol/transaction/scale/TransactionsWriter.java b/src/main/java/com/limechain/network/protocol/transaction/scale/TransactionsWriter.java new file mode 100644 index 000000000..d9f1f4255 --- /dev/null +++ b/src/main/java/com/limechain/network/protocol/transaction/scale/TransactionsWriter.java @@ -0,0 +1,19 @@ +package com.limechain.network.protocol.transaction.scale; + +import com.limechain.transaction.dto.ExtrinsicArray; +import io.emeraldpay.polkaj.scale.ScaleCodecWriter; +import io.emeraldpay.polkaj.scale.ScaleWriter; + +import java.io.IOException; + +public class TransactionsWriter implements ScaleWriter { + + @Override + public void write(ScaleCodecWriter writer, ExtrinsicArray holder) throws IOException { + int length = holder.getExtrinsics().length; + writer.writeCompact(length); + for (int i = 0; i < length; i++) { + writer.writeAsList(holder.getExtrinsics()[i].getData()); + } + } +} diff --git a/src/main/java/com/limechain/network/protocol/transactions/scale/TransactionsReader.java b/src/main/java/com/limechain/network/protocol/transactions/scale/TransactionsReader.java deleted file mode 100644 index a96da543a..000000000 --- a/src/main/java/com/limechain/network/protocol/transactions/scale/TransactionsReader.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.limechain.network.protocol.transactions.scale; - -import io.emeraldpay.polkaj.scale.ScaleCodecReader; -import io.emeraldpay.polkaj.scale.ScaleReader; - -public class TransactionsReader implements ScaleReader { - @Override - public byte[][] read(ScaleCodecReader reader) { - int size = reader.readCompactInt(); - byte[][] transactions = new byte[size][]; - for (int i = 0; i < size; i++) { - transactions[i] = reader.readByteArray(); - } - return transactions; - } -} diff --git a/src/main/java/com/limechain/network/protocol/transactions/scale/TransactionsWriter.java b/src/main/java/com/limechain/network/protocol/transactions/scale/TransactionsWriter.java deleted file mode 100644 index 6013d6e64..000000000 --- a/src/main/java/com/limechain/network/protocol/transactions/scale/TransactionsWriter.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.limechain.network.protocol.transactions.scale; - -import io.emeraldpay.polkaj.scale.ScaleCodecWriter; -import io.emeraldpay.polkaj.scale.ScaleWriter; - -import java.io.IOException; - -public class TransactionsWriter implements ScaleWriter { - - @Override - public void write(ScaleCodecWriter writer, byte[][] transactions) throws IOException { - writer.writeCompact(transactions.length); - for (int i = 0; i < transactions.length; i++) { - writer.writeAsList(transactions[i]); - } - } -} diff --git a/src/main/java/com/limechain/network/protocol/warp/WarpSyncProtocol.java b/src/main/java/com/limechain/network/protocol/warp/WarpSyncProtocol.java index 839d10514..4c2fe0bba 100644 --- a/src/main/java/com/limechain/network/protocol/warp/WarpSyncProtocol.java +++ b/src/main/java/com/limechain/network/protocol/warp/WarpSyncProtocol.java @@ -1,11 +1,11 @@ package com.limechain.network.protocol.warp; +import com.limechain.exception.scale.ScaleEncodingException; import com.limechain.network.encoding.Leb128LengthFrameDecoder; import com.limechain.network.encoding.Leb128LengthFrameEncoder; import com.limechain.network.protocol.warp.dto.WarpSyncRequest; import com.limechain.network.protocol.warp.dto.WarpSyncResponse; import com.limechain.network.protocol.warp.encoding.WarpSyncResponseDecoder; -import com.limechain.exception.scale.ScaleEncodingException; import com.limechain.network.protocol.warp.scale.writer.WarpSyncRequestWriter; import io.emeraldpay.polkaj.scale.ScaleCodecWriter; import io.libp2p.core.Stream; diff --git a/src/main/java/com/limechain/network/protocol/warp/dto/BlockBody.java b/src/main/java/com/limechain/network/protocol/warp/dto/BlockBody.java index d11203166..9a7d9f4a4 100644 --- a/src/main/java/com/limechain/network/protocol/warp/dto/BlockBody.java +++ b/src/main/java/com/limechain/network/protocol/warp/dto/BlockBody.java @@ -1,6 +1,7 @@ package com.limechain.network.protocol.warp.dto; import com.limechain.network.protocol.warp.scale.writer.BlockBodyWriter; +import com.limechain.transaction.dto.Extrinsic; import com.limechain.utils.HashUtils; import com.limechain.utils.scale.ScaleUtils; import lombok.Data; @@ -10,11 +11,11 @@ @Data public class BlockBody { - private final List extrinsics; + private final List extrinsics; public byte[][] getExtrinsicsAsByteArray() { return extrinsics.stream() - .map(Extrinsics::getExtrinsic) + .map(Extrinsic::getData) .toArray(byte[][]::new); } diff --git a/src/main/java/com/limechain/network/protocol/warp/dto/Extrinsics.java b/src/main/java/com/limechain/network/protocol/warp/dto/Extrinsics.java deleted file mode 100644 index bd4c6bb73..000000000 --- a/src/main/java/com/limechain/network/protocol/warp/dto/Extrinsics.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.limechain.network.protocol.warp.dto; - -import com.limechain.exception.scale.ScaleEncodingException; -import com.limechain.utils.HashUtils; -import io.emeraldpay.polkaj.scale.ScaleCodecWriter; -import lombok.Data; -import org.apache.tomcat.util.buf.HexUtils; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -@Data -public class Extrinsics { - - private final byte[] extrinsic; - - @Override - public String toString() { - return HexUtils.toHexString(extrinsic); - } - - public byte[] getHash() { - try (ByteArrayOutputStream buf = new ByteArrayOutputStream(); - ScaleCodecWriter writer = new ScaleCodecWriter(buf)) { - writer.writeAsList(extrinsic); - return HashUtils.hashWithBlake2b(buf.toByteArray()); - } catch (IOException e) { - throw new ScaleEncodingException(e); - } - } - -} diff --git a/src/main/java/com/limechain/network/protocol/warp/scale/reader/BlockBodyReader.java b/src/main/java/com/limechain/network/protocol/warp/scale/reader/BlockBodyReader.java index 5a84ca3fe..4c9e219d2 100644 --- a/src/main/java/com/limechain/network/protocol/warp/scale/reader/BlockBodyReader.java +++ b/src/main/java/com/limechain/network/protocol/warp/scale/reader/BlockBodyReader.java @@ -1,7 +1,7 @@ package com.limechain.network.protocol.warp.scale.reader; import com.limechain.network.protocol.warp.dto.BlockBody; -import com.limechain.network.protocol.warp.dto.Extrinsics; +import com.limechain.transaction.dto.Extrinsic; import io.emeraldpay.polkaj.scale.ScaleCodecReader; import io.emeraldpay.polkaj.scale.ScaleReader; import lombok.AccessLevel; @@ -21,12 +21,12 @@ public static BlockBodyReader getInstance() { @Override public BlockBody read(ScaleCodecReader reader) { - List extrinsics = new LinkedList<>(); + List extrinsics = new LinkedList<>(); int extrinsicsCount = reader.readCompactInt(); for (int i = 0; i < extrinsicsCount; i++) { byte[] extrinsic = reader.readByteArray(); - extrinsics.add(new Extrinsics(extrinsic)); + extrinsics.add(new Extrinsic(extrinsic)); } return new BlockBody(extrinsics); diff --git a/src/main/java/com/limechain/network/protocol/warp/scale/reader/JustificationReader.java b/src/main/java/com/limechain/network/protocol/warp/scale/reader/JustificationReader.java index 5814a0a3a..90df6b76f 100644 --- a/src/main/java/com/limechain/network/protocol/warp/scale/reader/JustificationReader.java +++ b/src/main/java/com/limechain/network/protocol/warp/scale/reader/JustificationReader.java @@ -1,8 +1,8 @@ package com.limechain.network.protocol.warp.scale.reader; import com.limechain.network.protocol.warp.dto.BlockHeader; -import com.limechain.network.protocol.warp.dto.Precommit; import com.limechain.network.protocol.warp.dto.Justification; +import com.limechain.network.protocol.warp.dto.Precommit; import io.emeraldpay.polkaj.scale.ScaleCodecReader; import io.emeraldpay.polkaj.scale.ScaleReader; import io.emeraldpay.polkaj.scale.reader.UInt64Reader; diff --git a/src/main/java/com/limechain/network/protocol/warp/scale/reader/WarpSyncFragmentReader.java b/src/main/java/com/limechain/network/protocol/warp/scale/reader/WarpSyncFragmentReader.java index 3ff2c8ce7..d78a8afb5 100644 --- a/src/main/java/com/limechain/network/protocol/warp/scale/reader/WarpSyncFragmentReader.java +++ b/src/main/java/com/limechain/network/protocol/warp/scale/reader/WarpSyncFragmentReader.java @@ -1,8 +1,6 @@ package com.limechain.network.protocol.warp.scale.reader; import com.limechain.network.protocol.warp.dto.WarpSyncFragment; -import com.limechain.network.protocol.warp.scale.reader.BlockHeaderReader; -import com.limechain.network.protocol.warp.scale.reader.JustificationReader; import io.emeraldpay.polkaj.scale.ScaleCodecReader; import io.emeraldpay.polkaj.scale.ScaleReader; diff --git a/src/main/java/com/limechain/network/protocol/warp/scale/writer/BlockBodyWriter.java b/src/main/java/com/limechain/network/protocol/warp/scale/writer/BlockBodyWriter.java index feb075da8..5cf5dd086 100644 --- a/src/main/java/com/limechain/network/protocol/warp/scale/writer/BlockBodyWriter.java +++ b/src/main/java/com/limechain/network/protocol/warp/scale/writer/BlockBodyWriter.java @@ -1,7 +1,7 @@ package com.limechain.network.protocol.warp.scale.writer; import com.limechain.network.protocol.warp.dto.BlockBody; -import com.limechain.network.protocol.warp.dto.Extrinsics; +import com.limechain.transaction.dto.Extrinsic; import io.emeraldpay.polkaj.scale.ScaleCodecWriter; import io.emeraldpay.polkaj.scale.ScaleWriter; import lombok.AccessLevel; @@ -21,11 +21,11 @@ public static BlockBodyWriter getInstance() { @Override public void write(ScaleCodecWriter writer, BlockBody blockBody) throws IOException { - List extrinsics = blockBody.getExtrinsics(); + List extrinsics = blockBody.getExtrinsics(); writer.writeCompact(extrinsics.size()); - for (Extrinsics extrinsic : extrinsics) { - writer.writeAsList(extrinsic.getExtrinsic()); + for (Extrinsic extrinsic : extrinsics) { + writer.writeAsList(extrinsic.getData()); } } } diff --git a/src/main/java/com/limechain/rpc/methods/chain/ChainRPCImpl.java b/src/main/java/com/limechain/rpc/methods/chain/ChainRPCImpl.java index da05e9fdf..081e19c64 100644 --- a/src/main/java/com/limechain/rpc/methods/chain/ChainRPCImpl.java +++ b/src/main/java/com/limechain/rpc/methods/chain/ChainRPCImpl.java @@ -1,12 +1,12 @@ package com.limechain.rpc.methods.chain; +import com.limechain.exception.storage.BlockNotFoundException; +import com.limechain.exception.storage.BlockStorageGenericException; +import com.limechain.exception.storage.HeaderNotFoundException; import com.limechain.network.protocol.warp.dto.Block; import com.limechain.network.protocol.warp.dto.BlockHeader; import com.limechain.network.protocol.warp.dto.HeaderDigest; import com.limechain.storage.block.BlockState; -import com.limechain.exception.storage.BlockNotFoundException; -import com.limechain.exception.storage.BlockStorageGenericException; -import com.limechain.exception.storage.HeaderNotFoundException; import com.limechain.utils.StringUtils; import io.emeraldpay.polkaj.types.Hash256; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/limechain/rpc/server/RpcApp.java b/src/main/java/com/limechain/rpc/server/RpcApp.java index 721b1bf62..469748dc3 100644 --- a/src/main/java/com/limechain/rpc/server/RpcApp.java +++ b/src/main/java/com/limechain/rpc/server/RpcApp.java @@ -18,12 +18,13 @@ */ @SpringBootApplication @ComponentScan(basePackages = { - "com.limechain.babe", - "com.limechain.rpc.config", - "com.limechain.rpc.methods", - "com.limechain.rpc.server", - "com.limechain.storage", - "com.limechain.runtime.builder", + "com.limechain.babe", + "com.limechain.rpc.config", + "com.limechain.rpc.methods", + "com.limechain.rpc.server", + "com.limechain.storage", + "com.limechain.runtime.builder", + "com.limechain.transaction" }) public class RpcApp { diff --git a/src/main/java/com/limechain/rpc/server/UnsafeInterceptor.java b/src/main/java/com/limechain/rpc/server/UnsafeInterceptor.java index 0ab4b818d..26bb2be37 100644 --- a/src/main/java/com/limechain/rpc/server/UnsafeInterceptor.java +++ b/src/main/java/com/limechain/rpc/server/UnsafeInterceptor.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.googlecode.jsonrpc4j.JsonRpcInterceptor; import com.limechain.cli.CliArguments; -import com.limechain.rpc.config.UnsafeRpcMethod; import com.limechain.exception.rpc.UnsafeAccessException; +import com.limechain.rpc.config.UnsafeRpcMethod; import java.lang.reflect.Method; import java.util.List; diff --git a/src/main/java/com/limechain/rpc/subscriptions/chainhead/ChainHeadRpcImpl.java b/src/main/java/com/limechain/rpc/subscriptions/chainhead/ChainHeadRpcImpl.java index 28159724e..47034bd06 100644 --- a/src/main/java/com/limechain/rpc/subscriptions/chainhead/ChainHeadRpcImpl.java +++ b/src/main/java/com/limechain/rpc/subscriptions/chainhead/ChainHeadRpcImpl.java @@ -1,12 +1,12 @@ package com.limechain.rpc.subscriptions.chainhead; -import com.limechain.exception.rpc.InvalidURIException; import com.limechain.exception.global.ThreadInterruptedException; +import com.limechain.exception.rpc.InvalidURIException; +import com.limechain.rpc.client.SubscriptionRpcClient; import com.limechain.rpc.config.SubscriptionName; import com.limechain.rpc.pubsub.Topic; import com.limechain.rpc.pubsub.publisher.PublisherImpl; import com.limechain.rpc.subscriptions.utils.Utils; -import com.limechain.rpc.client.SubscriptionRpcClient; import org.springframework.stereotype.Service; import java.net.URI; diff --git a/src/main/java/com/limechain/rpc/subscriptions/transaction/TransactionRpcImpl.java b/src/main/java/com/limechain/rpc/subscriptions/transaction/TransactionRpcImpl.java index 10b642bd5..7444e0b5b 100644 --- a/src/main/java/com/limechain/rpc/subscriptions/transaction/TransactionRpcImpl.java +++ b/src/main/java/com/limechain/rpc/subscriptions/transaction/TransactionRpcImpl.java @@ -1,12 +1,12 @@ package com.limechain.rpc.subscriptions.transaction; -import com.limechain.exception.rpc.InvalidURIException; import com.limechain.exception.global.ThreadInterruptedException; +import com.limechain.exception.rpc.InvalidURIException; +import com.limechain.rpc.client.SubscriptionRpcClient; import com.limechain.rpc.config.SubscriptionName; import com.limechain.rpc.pubsub.Topic; import com.limechain.rpc.pubsub.publisher.PublisherImpl; import com.limechain.rpc.subscriptions.utils.Utils; -import com.limechain.rpc.client.SubscriptionRpcClient; import java.net.URI; import java.net.URISyntaxException; diff --git a/src/main/java/com/limechain/runtime/Context.java b/src/main/java/com/limechain/runtime/Context.java index 9ed35ef39..43d7435c0 100644 --- a/src/main/java/com/limechain/runtime/Context.java +++ b/src/main/java/com/limechain/runtime/Context.java @@ -1,9 +1,9 @@ package com.limechain.runtime; import com.limechain.runtime.hostapi.dto.OffchainNetworkState; -import com.limechain.storage.offchain.OffchainStorages; import com.limechain.runtime.version.RuntimeVersion; import com.limechain.storage.crypto.KeyStore; +import com.limechain.storage.offchain.OffchainStorages; import com.limechain.trie.TrieAccessor; import lombok.AccessLevel; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/limechain/runtime/Runtime.java b/src/main/java/com/limechain/runtime/Runtime.java index 6b096c414..ecfa258a7 100644 --- a/src/main/java/com/limechain/runtime/Runtime.java +++ b/src/main/java/com/limechain/runtime/Runtime.java @@ -31,6 +31,7 @@ public class Runtime { /** * Calls an exported runtime function with no parameters. + * * @param function the name Runtime function to call * @return the SCALE encoded response */ @@ -41,7 +42,8 @@ public byte[] call(RuntimeEndpoint function) { /** * Calls an exported runtime function with parameters. - * @param function the name Runtime function to call + * + * @param function the name Runtime function to call * @param parameter the SCALE encoded tuple of parameters * @return the SCALE encoded response */ @@ -55,7 +57,7 @@ private byte[] callInner(RuntimeEndpoint function, RuntimePointerSize parameterP String functionName = function.getName(); log.log(Level.FINE, "Making a runtime call: " + functionName); Object[] response = instance.exports.getFunction(functionName) - .apply(parameterPtrSize.pointer(), parameterPtrSize.size()); + .apply(parameterPtrSize.pointer(), parameterPtrSize.size()); if (response == null) { return null; @@ -86,6 +88,7 @@ RuntimeVersion callCoreVersion() { /** * Executes the block by calling `Core_execute_block`. + * * @param block the block to execute * @return the SCALE encoded result of the runtime call */ @@ -96,8 +99,8 @@ public byte[] executeBlock(Block block) { private static byte[] serializeExecuteBlockParameter(Block block) { byte[] encodedUnsealedHeader = ScaleUtils.Encode.encode( - BlockHeaderScaleWriter.getInstance()::writeUnsealed, - block.getHeader() + BlockHeaderScaleWriter.getInstance()::writeUnsealed, + block.getHeader() ); byte[] encodedBody = ScaleUtils.Encode.encode(BlockBodyWriter.getInstance(), block.getBody()); @@ -106,7 +109,8 @@ private static byte[] serializeExecuteBlockParameter(Block block) { /** * Checks whether the provided inherents are valid for the block by calling `BlockBuilder_Check_inherents - * @param block the block to check against + * + * @param block the block to check against * @param inherentData inherents to check for validity * @return the SCALE encoded result of the runtime call */ diff --git a/src/main/java/com/limechain/runtime/RuntimeEndpoint.java b/src/main/java/com/limechain/runtime/RuntimeEndpoint.java index c4a3bbbd5..7f66c056b 100644 --- a/src/main/java/com/limechain/runtime/RuntimeEndpoint.java +++ b/src/main/java/com/limechain/runtime/RuntimeEndpoint.java @@ -5,6 +5,7 @@ /** * Used to identify runtime API endpoints by their names as listed in the spec. + * * @see the spec */ // NOTE: Add here whatever endpoints necessary during development. @@ -17,7 +18,8 @@ public enum RuntimeEndpoint { BLOCKBUILDER_CHECK_INHERENTS("BlockBuilder_check_inherents"), METADATA_METADATA("Metadata_metadata"), SESSION_KEYS_GENERATE_SESSION_KEYS("SessionKeys_generate_session_keys"), - SESSION_KEYS_DECODE_SESSION_KEYS("SessionKeys_decode_session_keys") + SESSION_KEYS_DECODE_SESSION_KEYS("SessionKeys_decode_session_keys"), + TRANSACTION_QUEUE_VALIDATE_TRANSACTION("TaggedTransactionQueue_validate_transaction"), ; private final String name; diff --git a/src/main/java/com/limechain/runtime/allocator/Header.java b/src/main/java/com/limechain/runtime/allocator/Header.java index 4beb2cfa3..9fec4c67b 100644 --- a/src/main/java/com/limechain/runtime/allocator/Header.java +++ b/src/main/java/com/limechain/runtime/allocator/Header.java @@ -1,10 +1,10 @@ package com.limechain.runtime.allocator; +import com.limechain.runtime.memory.Memory; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; -import com.limechain.runtime.memory.Memory; /** * Allocation header preceding a memory block. diff --git a/src/main/java/com/limechain/runtime/version/ApiVersionName.java b/src/main/java/com/limechain/runtime/version/ApiVersionName.java new file mode 100644 index 000000000..d04aa7d14 --- /dev/null +++ b/src/main/java/com/limechain/runtime/version/ApiVersionName.java @@ -0,0 +1,21 @@ +package com.limechain.runtime.version; + +import com.limechain.utils.HashUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ApiVersionName { + + AURA_API("AuraApi"), + BABE_API("BabeApi"), + GRANDPA_API("GrandpaApi"), + TRANSACTION_QUEUE_API("TaggedTransactionQueue"); + + private final String name; + + public byte[] getHashedName() { + return HashUtils.hashWithBlake2bToLength(name.getBytes(), ApiVersion.NAME_HASH_LENGTH); + } +} diff --git a/src/main/java/com/limechain/runtime/version/scale/RuntimeVersionReader.java b/src/main/java/com/limechain/runtime/version/scale/RuntimeVersionReader.java index e561792af..c121421a4 100644 --- a/src/main/java/com/limechain/runtime/version/scale/RuntimeVersionReader.java +++ b/src/main/java/com/limechain/runtime/version/scale/RuntimeVersionReader.java @@ -1,8 +1,8 @@ package com.limechain.runtime.version.scale; +import com.limechain.runtime.version.ApiVersions; import com.limechain.runtime.version.RuntimeVersion; import com.limechain.runtime.version.StateVersion; -import com.limechain.runtime.version.ApiVersions; import io.emeraldpay.polkaj.scale.ScaleCodecReader; import io.emeraldpay.polkaj.scale.ScaleReader; diff --git a/src/main/java/com/limechain/storage/block/BlockStateHelper.java b/src/main/java/com/limechain/storage/block/BlockStateHelper.java index bb35d9046..ccb11b846 100644 --- a/src/main/java/com/limechain/storage/block/BlockStateHelper.java +++ b/src/main/java/com/limechain/storage/block/BlockStateHelper.java @@ -1,8 +1,8 @@ package com.limechain.storage.block; +import com.limechain.exception.scale.ScaleEncodingException; import com.limechain.network.protocol.blockannounce.scale.BlockHeaderScaleWriter; import com.limechain.network.protocol.warp.dto.BlockHeader; -import com.limechain.exception.scale.ScaleEncodingException; import com.limechain.network.protocol.warp.scale.reader.BlockHeaderReader; import com.limechain.storage.DBConstants; import io.emeraldpay.polkaj.scale.ScaleCodecReader; diff --git a/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java b/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java index abd8623f9..b8aa7a234 100644 --- a/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java +++ b/src/main/java/com/limechain/sync/fullsync/FullSyncMachine.java @@ -10,7 +10,6 @@ import com.limechain.network.protocol.warp.dto.BlockBody; import com.limechain.network.protocol.warp.dto.BlockHeader; import com.limechain.network.protocol.warp.dto.DigestType; -import com.limechain.network.protocol.warp.dto.Extrinsics; import com.limechain.network.protocol.warp.dto.HeaderDigest; import com.limechain.network.protocol.warp.scale.reader.BlockHeaderReader; import com.limechain.rpc.server.AppBean; @@ -23,6 +22,7 @@ import com.limechain.storage.block.SyncState; import com.limechain.storage.trie.TrieStorage; import com.limechain.sync.fullsync.inherents.InherentData; +import com.limechain.transaction.dto.Extrinsic; import com.limechain.trie.DiskTrieAccessor; import com.limechain.trie.TrieAccessor; import com.limechain.trie.TrieStructureFactory; @@ -79,6 +79,8 @@ public void start() { loadStateAtBlockFromPeer(lastFinalizedBlockHash); } + networkService.blockAnnounceHandshakeBootNodes(); + runtime = buildRuntimeFromState(trieAccessor); StateVersion runtimeStateVersion = runtime.getVersion().getStateVersion(); BabeApiConfiguration babeApiConfiguration = runtime.callBabeApiConfiguration(); @@ -94,8 +96,8 @@ public void start() { blockState.storeRuntime(lastFinalizedBlockHash, runtime); int startNumber = syncState.getLastFinalizedBlockNumber() - .add(BigInteger.ONE) - .intValue(); + .add(BigInteger.ONE) + .intValue(); int blocksToFetch = 100; List receivedBlocks = requestBlocks(startNumber, blocksToFetch); @@ -103,12 +105,13 @@ public void start() { while (!receivedBlocks.isEmpty()) { executeBlocks(receivedBlocks, trieAccessor); log.info("Executed blocks from " + receivedBlocks.getFirst().getHeader().getBlockNumber() - + " to " + receivedBlocks.getLast().getHeader().getBlockNumber()); + + " to " + receivedBlocks.getLast().getHeader().getBlockNumber()); startNumber += blocksToFetch; receivedBlocks = requestBlocks(startNumber, blocksToFetch); } blockState.setFullSyncFinished(true); + networkService.handshakePeers(); } private TrieStructure loadStateAtBlockFromPeer(Hash256 lastFinalizedBlockHash) { @@ -169,18 +172,18 @@ private List requestBlocks(int start, int amount) { final int BODY = 0b0000_0010; final int JUSTIFICATION = 0b0001_0000; SyncMessage.BlockResponse response = networkService.makeBlockRequest(new BlockRequestDto( - HEADER | BODY | JUSTIFICATION, - null, // no hash, number instead - start, - SyncMessage.Direction.Ascending, - amount + HEADER | BODY | JUSTIFICATION, + null, // no hash, number instead + start, + SyncMessage.Direction.Ascending, + amount )); List blockDatas = response.getBlocksList(); return blockDatas.stream() - .map(FullSyncMachine::protobufDecodeBlock) - .toList(); + .map(FullSyncMachine::protobufDecodeBlock) + .toList(); } catch (Exception ex) { log.info("Error while fetching blocks, trying to fetch again"); if (!this.networkService.updateCurrentSelectedPeerWithNextBootnode()) { @@ -197,10 +200,10 @@ private static Block protobufDecodeBlock(SyncMessage.BlockData blockData) { BlockHeader blockHeader = ScaleUtils.Decode.decode(encodedHeader, new BlockHeaderReader()); // Protobuf decode the block body - List extrinsicsList = blockData.getBodyList().stream() - .map(bs -> ScaleUtils.Decode.decode(bs.toByteArray(), ScaleCodecReader::readByteArray)) - .map(Extrinsics::new) - .toList(); + List extrinsicsList = blockData.getBodyList().stream() + .map(bs -> ScaleUtils.Decode.decode(bs.toByteArray(), ScaleCodecReader::readByteArray)) + .map(Extrinsic::new) + .toList(); BlockBody blockBody = new BlockBody(extrinsicsList); @@ -250,8 +253,8 @@ private void executeBlocks(List receivedBlockDatas, TrieAccessor trieAcce } boolean blockUpdatedRuntime = Arrays.stream(blockHeader.getDigest()) - .map(HeaderDigest::getType) - .anyMatch(type -> type.equals(DigestType.RUN_ENV_UPDATED)); + .map(HeaderDigest::getType) + .anyMatch(type -> type.equals(DigestType.RUN_ENV_UPDATED)); if (blockUpdatedRuntime) { log.info("Runtime updated, updating the runtime code"); @@ -264,9 +267,9 @@ private void executeBlocks(List receivedBlockDatas, TrieAccessor trieAcce private Runtime buildRuntimeFromState(TrieAccessor trieAccessor) { return trieAccessor - .findStorageValue(Nibbles.fromBytes(":code".getBytes())) - .map(wasm -> runtimeBuilder.buildRuntime(wasm, trieAccessor)) - .orElseThrow(() -> new RuntimeException("Runtime code not found in the trie")); + .findStorageValue(Nibbles.fromBytes(":code".getBytes())) + .map(wasm -> runtimeBuilder.buildRuntime(wasm, trieAccessor)) + .orElseThrow(() -> new RuntimeException("Runtime code not found in the trie")); } private boolean checkInherents(Block block) { @@ -286,13 +289,13 @@ private boolean checkInherents(Block block) { */ private static boolean isBlockGoodToExecute(byte[] checkInherentsOutput) { var data = ScaleUtils.Decode.decode( - ArrayUtils.subarray(checkInherentsOutput, 2, checkInherentsOutput.length), - new ListReader<>( - new PairReader<>( - scr -> new String(scr.readByteArray(8)), - scr -> new String(scr.readByteArray()) + ArrayUtils.subarray(checkInherentsOutput, 2, checkInherentsOutput.length), + new ListReader<>( + new PairReader<>( + scr -> new String(scr.readByteArray(8)), + scr -> new String(scr.readByteArray()) + ) ) - ) ); boolean goodToExecute; diff --git a/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java b/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java index a241b1aa5..607e55eb1 100644 --- a/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java +++ b/src/main/java/com/limechain/sync/warpsync/WarpSyncMachine.java @@ -100,7 +100,6 @@ public void stop() { private void finishWarpSync() { this.warpState.setWarpSyncFinished(true); - this.networkService.handshakeBootNodes(); this.syncState.persistState(); BlockState.getInstance().initializeAfterWarpSync( diff --git a/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java b/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java index 1297b5756..839e10212 100644 --- a/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java +++ b/src/main/java/com/limechain/sync/warpsync/WarpSyncState.java @@ -149,7 +149,7 @@ private void updateState(CommitMessage commitMessage) { } syncState.finalizedCommitMessage(commitMessage); - log.log(Level.INFO, "Reached block #" + lastFinalizedBlockNumber); + log.log(Level.INFO, "Reached block #" + syncState.getLastFinalizedBlockNumber()); if (warpSyncFinished && scheduledRuntimeUpdateBlocks.contains(lastFinalizedBlockNumber)) { new Thread(this::updateRuntime).start(); } @@ -257,7 +257,6 @@ public void loadSavedRuntimeCode() { * @param peerId sender of message */ public void syncNeighbourMessage(NeighbourMessage neighbourMessage, PeerId peerId) { - network.sendNeighbourMessage(peerId); if (warpSyncFinished && neighbourMessage.getSetId().compareTo(syncState.getSetId()) > 0) { updateSetData(neighbourMessage.getLastFinalizedBlock().add(BigInteger.ONE), peerId); } @@ -305,7 +304,7 @@ public void handleScheduledEvents() { } if (warpSyncFinished && updated) { log.log(Level.INFO, "Successfully transitioned to authority set id: " + setId); - new Thread(network::sendNeighbourMessages).start(); + new Thread(network::sendMessagesToPeers).start(); } } diff --git a/src/main/java/com/limechain/sync/warpsync/action/ChainInformationBuildAction.java b/src/main/java/com/limechain/sync/warpsync/action/ChainInformationBuildAction.java index 347ef124b..94cdcec8b 100644 --- a/src/main/java/com/limechain/sync/warpsync/action/ChainInformationBuildAction.java +++ b/src/main/java/com/limechain/sync/warpsync/action/ChainInformationBuildAction.java @@ -1,10 +1,10 @@ package com.limechain.sync.warpsync.action; import com.limechain.rpc.server.AppBean; -import com.limechain.runtime.version.ApiVersion; +import com.limechain.runtime.version.ApiVersionName; +import com.limechain.runtime.version.ApiVersions; import com.limechain.sync.warpsync.WarpSyncMachine; import com.limechain.sync.warpsync.WarpSyncState; -import com.limechain.utils.HashUtils; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.extern.java.Log; @@ -31,23 +31,19 @@ public void next(WarpSyncMachine sync) { @Override public void handle(WarpSyncMachine sync) { - byte[][] hashedApiVersions = new byte[][]{ - HashUtils.hashWithBlake2bToLength("AuraApi".getBytes(), ApiVersion.NAME_HASH_LENGTH), - HashUtils.hashWithBlake2bToLength("BabeApi".getBytes(), ApiVersion.NAME_HASH_LENGTH), - HashUtils.hashWithBlake2bToLength("GrandpaApi".getBytes(), ApiVersion.NAME_HASH_LENGTH) - }; + ApiVersions versions = warpSyncState.getRuntime().getVersion().getApis(); sync.getChainInformation().setRuntimeAuraVersion( - warpSyncState.getRuntime().getVersion().getApis().getApiVersion(hashedApiVersions[0])); + versions.getApiVersion(ApiVersionName.AURA_API.getHashedName())); sync.getChainInformation().setRuntimeBabeVersion( - warpSyncState.getRuntime().getVersion().getApis().getApiVersion(hashedApiVersions[1])); + versions.getApiVersion(ApiVersionName.BABE_API.getHashedName())); sync.getChainInformation().setRuntimeGrandpaVersion( - warpSyncState.getRuntime().getVersion().getApis().getApiVersion(hashedApiVersions[2])); + versions.getApiVersion(ApiVersionName.GRANDPA_API.getHashedName())); log.log(Level.INFO, "Aura Api version: " + sync.getChainInformation().getRuntimeAuraVersion() - + " Babe api version: " + sync.getChainInformation().getRuntimeBabeVersion() + - " Grandpa Api Version: " + sync.getChainInformation().getRuntimeGrandpaVersion()); + + " Babe api version: " + sync.getChainInformation().getRuntimeBabeVersion() + + " Grandpa Api Version: " + sync.getChainInformation().getRuntimeGrandpaVersion()); log.log(Level.INFO, "Runtime supports aura: " + sync.getChainInformation().runtimeHasAura()); log.log(Level.INFO, "Runtime babe api is v1: " + sync.getChainInformation().runtimeBabeApiIsV1()); log.log(Level.INFO, "Runtime grandpa supports current setId: " - + sync.getChainInformation().runtimeGrandpaSupportsCurrentSetId()); + + sync.getChainInformation().runtimeGrandpaSupportsCurrentSetId()); } } diff --git a/src/main/java/com/limechain/transaction/TransactionPool.java b/src/main/java/com/limechain/transaction/TransactionPool.java new file mode 100644 index 000000000..3d3922266 --- /dev/null +++ b/src/main/java/com/limechain/transaction/TransactionPool.java @@ -0,0 +1,46 @@ +package com.limechain.transaction; + +import com.limechain.transaction.dto.Extrinsic; +import com.limechain.transaction.dto.ValidTransaction; +import com.limechain.utils.HashUtils; + +import java.util.HashMap; +import java.util.Map; + +public class TransactionPool { + + private final Map transactions; + + public TransactionPool() { + transactions = new HashMap<>(); + } + + public ValidTransaction get(Extrinsic extrinsic) { + byte[] key = HashUtils.hashWithBlake2b(extrinsic.getData()); + return transactions.get(key); + } + + public ValidTransaction[] transactions() { + return transactions.values().toArray(ValidTransaction[]::new); + } + + public byte[] insert(ValidTransaction validTransaction) { + byte[] key = HashUtils.hashWithBlake2b(validTransaction.getExtrinsic().getData()); + transactions.put(key, validTransaction); + return key; + } + + public void removeExtrinsic(Extrinsic extrinsic) { + byte[] key = HashUtils.hashWithBlake2b(extrinsic.getData()); + transactions.remove(key); + } + + public int length() { + return transactions.size(); + } + + public boolean exists(Extrinsic extrinsic) { + byte[] key = HashUtils.hashWithBlake2b(extrinsic.getData()); + return transactions.containsKey(key); + } +} diff --git a/src/main/java/com/limechain/transaction/TransactionState.java b/src/main/java/com/limechain/transaction/TransactionState.java new file mode 100644 index 000000000..ce588169c --- /dev/null +++ b/src/main/java/com/limechain/transaction/TransactionState.java @@ -0,0 +1,119 @@ +package com.limechain.transaction; + +import com.limechain.transaction.dto.Extrinsic; +import com.limechain.transaction.dto.ValidTransaction; +import com.limechain.utils.ByteArrayUtils; +import lombok.extern.java.Log; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +@Log +@Component +public class TransactionState { + + private final TransactionPool transactionPool; + private final ExecutorService executor; + private final Queue transactionQueue; + + public TransactionState() { + transactionPool = new TransactionPool(); + executor = Executors.newSingleThreadExecutor(); + transactionQueue = new PriorityQueue<>(); + } + + public void pushTransaction(ValidTransaction validTransaction) { + transactionQueue.add(validTransaction); + } + + public ValidTransaction popTransaction() { + return transactionQueue.poll(); + } + + public ValidTransaction popTransactionWithTimer(long timeout) throws InterruptedException { + ValidTransaction validTransaction = popTransaction(); + if (validTransaction != null) return validTransaction; + + Future futureTransaction = popFutureTransaction(); + + try { + return futureTransaction.get(timeout, java.util.concurrent.TimeUnit.MILLISECONDS); + } catch (ExecutionException e) { + log.severe("Error while waiting for transaction: " + e.getMessage()); + } catch (TimeoutException e) { + futureTransaction.cancel(true); + } catch (InterruptedException e) { + if (Thread.interrupted()) + throw new InterruptedException(); + } + + return null; + } + + @NotNull + private Future popFutureTransaction() { + return executor.submit(() -> { + ValidTransaction transaction = null; + while (transaction == null) { + transaction = popTransaction(); + Thread.sleep(50); + } + return transaction; + }); + } + + public ValidTransaction peek() { + return transactionQueue.peek(); + } + + public ValidTransaction[] pending() { + return transactionQueue.toArray(new ValidTransaction[0]); + } + + public ValidTransaction[] pendingInPool() { + return transactionPool.transactions(); + } + + public boolean existsInQueue(Extrinsic extrinsic) { + return transactionQueue.contains(new ValidTransaction(extrinsic, null)); + } + + public boolean existsInPool(Extrinsic extrinsic) { + return transactionPool.exists(extrinsic); + } + + public boolean shouldAddToQueue(ValidTransaction validTransaction) { + Set provided = transactionQueue.stream() + .flatMap(entry -> Arrays.stream(entry.getTransactionValidity().getProvides())) + .collect(Collectors.toSet()); + + Set required = Arrays.stream(validTransaction.getTransactionValidity().getRequires()) + .collect(Collectors.toSet()); + + return ByteArrayUtils.sourceContainsAll(provided, required); + } + + public void removeExtrinsic(Extrinsic extrinsic) { + transactionPool.removeExtrinsic(extrinsic); + ValidTransaction transactionToBeRemoved = new ValidTransaction(extrinsic, null); + transactionQueue.remove(transactionToBeRemoved); + } + + public void removeExtrinsicFromPool(Extrinsic extrinsic) { + transactionPool.removeExtrinsic(extrinsic); + } + + public byte[] addToPool(ValidTransaction validTransaction) { + return transactionPool.insert(validTransaction); + } +} diff --git a/src/main/java/com/limechain/transaction/TransactionValidator.java b/src/main/java/com/limechain/transaction/TransactionValidator.java new file mode 100644 index 000000000..c71181c5b --- /dev/null +++ b/src/main/java/com/limechain/transaction/TransactionValidator.java @@ -0,0 +1,92 @@ +package com.limechain.transaction; + +import com.limechain.exception.misc.RuntimeApiVersionException; +import com.limechain.exception.transaction.TransactionValidationException; +import com.limechain.network.protocol.warp.dto.BlockHeader; +import com.limechain.runtime.Runtime; +import com.limechain.runtime.RuntimeEndpoint; +import com.limechain.runtime.version.ApiVersionName; +import com.limechain.storage.block.BlockState; +import com.limechain.transaction.dto.Extrinsic; +import com.limechain.transaction.dto.TransactionSource; +import com.limechain.transaction.dto.TransactionValidationRequest; +import com.limechain.transaction.dto.TransactionValidationResponse; +import com.limechain.transaction.dto.ValidTransaction; +import com.limechain.utils.scale.ScaleUtils; +import com.limechain.utils.scale.readers.TransactionValidationReader; +import com.limechain.utils.scale.writers.TransactionValidationWriter; +import io.emeraldpay.polkaj.types.Hash256; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.util.Objects; + +@Component +public class TransactionValidator { + + private final TransactionState transactionState; + private final BlockState blockState; + + public TransactionValidator(TransactionState transactionState) { + this.transactionState = transactionState; + this.blockState = BlockState.getInstance(); + } + + public synchronized ValidTransaction validateTransactions(Extrinsic extrinsic) + throws TransactionValidationException { + if (transactionState.existsInQueue(extrinsic) || transactionState.existsInPool(extrinsic)) { + throw new TransactionValidationException("Transaction already validated."); + } + + final BlockHeader header = blockState.bestBlockHeader(); + if (header == null) { + throw new TransactionValidationException("No best block header found while validating."); + } + + final Runtime runtime = blockState.getRuntime(header.getHash()); + if (runtime == null) { + throw new TransactionValidationException("No runtime found for block header " + header.getHash() + + " while validating."); + } + + TransactionValidationResponse response = validateTransactionInner(runtime, header.getHash(), extrinsic); + + if (!Objects.isNull(response.getTransactionValidityError())) { + throw new TransactionValidationException(response.getTransactionValidityError().toString()); + } + + return new ValidTransaction(extrinsic, response.getValidTx()); + } + + private static TransactionValidationResponse validateTransactionInner(Runtime runtime, + Hash256 hash, + Extrinsic transaction) { + byte[] scaleRequest = createScaleValidationRequest(runtime, hash, transaction); + byte[] validationResult = runtime.call( + RuntimeEndpoint.TRANSACTION_QUEUE_VALIDATE_TRANSACTION, scaleRequest); + return ScaleUtils.Decode.decode(validationResult, new TransactionValidationReader()); + } + + private static byte[] createScaleValidationRequest(Runtime runtime, Hash256 hash256, Extrinsic transaction) { + BigInteger txQueueVersion = runtime.getVersion().getApis() + .getApiVersion(ApiVersionName.TRANSACTION_QUEUE_API.getHashedName()); + + TransactionValidationRequest request = new TransactionValidationRequest(); + + switch (txQueueVersion.intValue()) { + case 1 -> request.setTransaction(transaction.getData()); + case 2 -> { + request.setSource(TransactionSource.EXTERNAL); + request.setTransaction(transaction.getData()); + } + case 3 -> { + request.setSource(TransactionSource.EXTERNAL); + request.setTransaction(transaction.getData()); + request.setParentBlockHash(hash256); + } + default -> throw new RuntimeApiVersionException("Invalid transaction queue version: " + txQueueVersion); + } + + return ScaleUtils.Encode.encode(new TransactionValidationWriter(), request); + } +} diff --git a/src/main/java/com/limechain/transaction/dto/Extrinsic.java b/src/main/java/com/limechain/transaction/dto/Extrinsic.java new file mode 100644 index 000000000..1a09f3e55 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/Extrinsic.java @@ -0,0 +1,15 @@ +package com.limechain.transaction.dto; + +import lombok.Value; +import org.apache.tomcat.util.buf.HexUtils; + +@Value +public class Extrinsic { + + byte[] data; + + @Override + public String toString() { + return HexUtils.toHexString(data); + } +} diff --git a/src/main/java/com/limechain/transaction/dto/ExtrinsicArray.java b/src/main/java/com/limechain/transaction/dto/ExtrinsicArray.java new file mode 100644 index 000000000..1e02933de --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/ExtrinsicArray.java @@ -0,0 +1,11 @@ +package com.limechain.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ExtrinsicArray { + + private Extrinsic[] extrinsics; +} diff --git a/src/main/java/com/limechain/transaction/dto/InvalidTransactionType.java b/src/main/java/com/limechain/transaction/dto/InvalidTransactionType.java new file mode 100644 index 000000000..a6c35091f --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/InvalidTransactionType.java @@ -0,0 +1,40 @@ +package com.limechain.transaction.dto; + +import org.springframework.lang.Nullable; + +import java.util.Arrays; + +public enum InvalidTransactionType implements TransactionValidityError { + + CALL_NOT_EXPECTED(0, true), + INABILITY_TO_PAY_FEES(1, true), + TRANSACTION_NOT_YET_VALID(2, false), + TRANSACTION_OUTDATED(3, true), + INVALID_PROOF(4, true), + ANCIENT_BIRTH_BLOCK(5, true), + EXHAUST_BLOCK_RESOURCES(6, false), + UNKNOWN_ERROR(7, true), + MANDATORY_DISPATCH_ERROR(8, true), + INVALID_MANDATORY_DISPATCH(9, true); + + private final int id; + private final boolean shouldReject; + + InvalidTransactionType(int id, boolean shouldReject) { + this.id = id; + this.shouldReject = shouldReject; + } + + @Nullable + public static TransactionValidityError getFromInt(int intValue) { + return Arrays.stream(values()) + .filter(v -> v.id == intValue) + .findFirst() + .orElse(null); + } + + @Override + public boolean shouldReject() { + return shouldReject; + } +} diff --git a/src/main/java/com/limechain/transaction/dto/TransactionSource.java b/src/main/java/com/limechain/transaction/dto/TransactionSource.java new file mode 100644 index 000000000..af27bf359 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/TransactionSource.java @@ -0,0 +1,33 @@ +package com.limechain.transaction.dto; + +/** + * The source of the transaction. + */ +public enum TransactionSource { + /** + * Transaction is already included in a block. + *

+ * This means that we can't really tell where the transaction is coming from, + * since it's already in the received block. Note that the custom validation logic + * using either `Local` or `External` should most likely just allow `InBlock` + * transactions as well. + */ + IN_BLOCK, + + /** + * Transaction is coming from a local source. + *

+ * This means that the transaction was produced internally by the node + * (for instance an Off-Chain Worker or an Off-Chain Call), as opposed + * to being received over the network. + */ + LOCAL, + + /** + * Transaction has been received externally. + *

+ * This means the transaction has been received from (usually) an "untrusted" source, + * for instance received over the network or RPC. + */ + EXTERNAL; +} diff --git a/src/main/java/com/limechain/transaction/dto/TransactionValidationRequest.java b/src/main/java/com/limechain/transaction/dto/TransactionValidationRequest.java new file mode 100644 index 000000000..9c4b4eee4 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/TransactionValidationRequest.java @@ -0,0 +1,17 @@ +package com.limechain.transaction.dto; + +import io.emeraldpay.polkaj.types.Hash256; +import lombok.Data; +import org.jetbrains.annotations.Nullable; + +@Data +public class TransactionValidationRequest { + + private byte[] transaction; + + @Nullable + private TransactionSource source; + + @Nullable + private Hash256 parentBlockHash; +} diff --git a/src/main/java/com/limechain/transaction/dto/TransactionValidationResponse.java b/src/main/java/com/limechain/transaction/dto/TransactionValidationResponse.java new file mode 100644 index 000000000..7e63c92b0 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/TransactionValidationResponse.java @@ -0,0 +1,14 @@ +package com.limechain.transaction.dto; + +import lombok.Data; +import org.jetbrains.annotations.Nullable; + +@Data +public class TransactionValidationResponse { + + @Nullable + TransactionValidity validTx; + + @Nullable + TransactionValidityError transactionValidityError; +} diff --git a/src/main/java/com/limechain/transaction/dto/TransactionValidity.java b/src/main/java/com/limechain/transaction/dto/TransactionValidity.java new file mode 100644 index 000000000..ec94a29d0 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/TransactionValidity.java @@ -0,0 +1,25 @@ +package com.limechain.transaction.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigInteger; + +/** + * Represents the data returned by the runtime "validate_transaction" call. It provides various data needed when adding + * the transaction to either the transaction queue or the pool. The simplest example is the priority field. The runtime + * returns a priority value that is compared to other priorities in a reverse natural order fashion (higher number + * value is added on top of the queue). + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class TransactionValidity { + + private BigInteger priority; + private byte[][] requires; + private byte[][] provides; + private BigInteger longevity; + private Boolean propagate; +} diff --git a/src/main/java/com/limechain/transaction/dto/TransactionValidityError.java b/src/main/java/com/limechain/transaction/dto/TransactionValidityError.java new file mode 100644 index 000000000..dd6b3caac --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/TransactionValidityError.java @@ -0,0 +1,6 @@ +package com.limechain.transaction.dto; + +public interface TransactionValidityError { + + boolean shouldReject(); +} diff --git a/src/main/java/com/limechain/transaction/dto/UnknownTransactionType.java b/src/main/java/com/limechain/transaction/dto/UnknownTransactionType.java new file mode 100644 index 000000000..316d30872 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/UnknownTransactionType.java @@ -0,0 +1,33 @@ +package com.limechain.transaction.dto; + +import org.springframework.lang.Nullable; + +import java.util.Arrays; + +public enum UnknownTransactionType implements TransactionValidityError { + + METADATA_SEARCH_FAILURE(0, true), + NO_VALIDATOR_FOUND(1, true), + UNKNOWN_VALIDITY(2, false); + + private final int id; + private final boolean shouldReject; + + UnknownTransactionType(int id, boolean shouldReject) { + this.id = id; + this.shouldReject = shouldReject; + } + + @Nullable + public static TransactionValidityError getFromInt(int intValue) { + return Arrays.stream(values()) + .filter(v -> v.id == intValue) + .findFirst() + .orElse(null); + } + + @Override + public boolean shouldReject() { + return shouldReject; + } +} diff --git a/src/main/java/com/limechain/transaction/dto/ValidTransaction.java b/src/main/java/com/limechain/transaction/dto/ValidTransaction.java new file mode 100644 index 000000000..cf2b7a412 --- /dev/null +++ b/src/main/java/com/limechain/transaction/dto/ValidTransaction.java @@ -0,0 +1,27 @@ +package com.limechain.transaction.dto; + +import io.libp2p.core.PeerId; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; + +@Value +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class ValidTransaction implements Comparable { + + @EqualsAndHashCode.Include + Extrinsic extrinsic; + + @Nullable + TransactionValidity transactionValidity; + + HashSet ignore = new HashSet<>(); + + public int compareTo(@NotNull ValidTransaction transaction) { + return transaction.transactionValidity.getPriority() + .compareTo(this.transactionValidity.getPriority()); + } +} diff --git a/src/main/java/com/limechain/trie/decoded/Node.java b/src/main/java/com/limechain/trie/decoded/Node.java index 4dfc5a4d7..a8d4abe8c 100644 --- a/src/main/java/com/limechain/trie/decoded/Node.java +++ b/src/main/java/com/limechain/trie/decoded/Node.java @@ -1,7 +1,7 @@ package com.limechain.trie.decoded; -import com.limechain.trie.decoded.encoder.TrieEncoder; import com.limechain.exception.trie.TrieEncoderException; +import com.limechain.trie.decoded.encoder.TrieEncoder; import com.limechain.utils.HashUtils; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/limechain/trie/dto/node/DecodedNode.java b/src/main/java/com/limechain/trie/dto/node/DecodedNode.java index bac7046cf..9ffd4bf5f 100644 --- a/src/main/java/com/limechain/trie/dto/node/DecodedNode.java +++ b/src/main/java/com/limechain/trie/dto/node/DecodedNode.java @@ -1,8 +1,8 @@ package com.limechain.trie.dto.node; -import com.limechain.trie.decoded.NodeVariant; import com.limechain.exception.trie.NodeDecodingException; import com.limechain.exception.trie.NodeEncodingException; +import com.limechain.trie.decoded.NodeVariant; import com.limechain.trie.structure.nibble.BytesToNibbles; import com.limechain.trie.structure.nibble.Nibble; import com.limechain.trie.structure.nibble.Nibbles; diff --git a/src/main/java/com/limechain/utils/ByteArrayUtils.java b/src/main/java/com/limechain/utils/ByteArrayUtils.java index 879b62195..b40e90286 100644 --- a/src/main/java/com/limechain/utils/ByteArrayUtils.java +++ b/src/main/java/com/limechain/utils/ByteArrayUtils.java @@ -2,6 +2,9 @@ import lombok.experimental.UtilityClass; +import java.util.Arrays; +import java.util.Collection; + @UtilityClass public class ByteArrayUtils { @@ -19,7 +22,7 @@ public class ByteArrayUtils { */ public static int commonPrefixLength(byte[] a, byte[] b) { int minLength = Math.min(a.length, b.length); - if(minLength == 0) { + if (minLength == 0) { return 0; } int length; @@ -100,4 +103,20 @@ public static byte[] concatenate(byte[] prefix, byte[] suffix) { System.arraycopy(suffix, 0, result, prefix.length, suffix.length); return result; } + + /** + * Checks if all the byte arrays in the target collection are present in the source collection. + * + * @param source the collection of byte arrays that may contain the target arrays + * @param target the collection of byte arrays to check for presence in the source + * @return {@code true} if all byte arrays in the target collection are present in the source + * collection, {@code false} otherwise + */ + public boolean sourceContainsAll(Collection source, Collection target) { + return target.stream() + .allMatch(targetArray -> + source.stream() + .anyMatch(sourceArray -> Arrays.equals(sourceArray, targetArray)) + ); + } } diff --git a/src/main/java/com/limechain/utils/scale/ScaleUtils.java b/src/main/java/com/limechain/utils/scale/ScaleUtils.java index 6639a41d4..091417e52 100644 --- a/src/main/java/com/limechain/utils/scale/ScaleUtils.java +++ b/src/main/java/com/limechain/utils/scale/ScaleUtils.java @@ -28,6 +28,17 @@ @UtilityClass public class ScaleUtils { + /** + * A utility method that returns true if the scale decoded result is successful. See + * Results section. + * + * @param reader a reader with preloaded byte data. + * @return true if result byte is 0, false otherwise. + */ + public boolean isScaleResultSuccessful(ScaleCodecReader reader) { + return reader.readUByte() == 0; + } + @UtilityClass public class Decode { @@ -51,9 +62,9 @@ public T decode(byte[] encodedData, ScaleReader reader) { /** * Decodes a byte array representing a list of items into a List using the provided ScaleReader for the list items. * - * @param encodedData The byte array containing the encoded list. + * @param encodedData The byte array containing the encoded list. * @param listItemReader The ScaleReader implementation for decoding individual list items. - * @param The type of objects in the list. + * @param The type of objects in the list. * @return The decoded List of items. * @throws ScaleDecodingException If an error occurs during decoding. */ @@ -68,25 +79,25 @@ public class Encode { /** * Encodes a list of pairs into SCALE format using the provided serializers for the key and value types. * - * @param pairs The list of pairs to encode. + * @param pairs The list of pairs to encode. * @param fstSerializer The serializer function for the first element of each pair. * @param sndSerializer The serializer function for the second element of each pair. - * @param The type of the first element in the pairs. - * @param The type of the second element in the pairs. + * @param The type of the first element in the pairs. + * @param The type of the second element in the pairs. * @return The encoded byte array representing the list of pairs. */ public byte[] encodeListOfPairs( - List> pairs, - Function fstSerializer, - Function sndSerializer + List> pairs, + Function fstSerializer, + Function sndSerializer ) { return encodeListOfPairs( - pairs.stream() - .map(p -> - new Pair<>( - fstSerializer.apply(p.getValue0()), - sndSerializer.apply(p.getValue1()))) - .toList()); + pairs.stream() + .map(p -> + new Pair<>( + fstSerializer.apply(p.getValue0()), + sndSerializer.apply(p.getValue1()))) + .toList()); } /** @@ -97,11 +108,11 @@ public byte[] encodeListOfPairs( */ public byte[] encodeListOfPairs(List> pairs) { return encode( - new ListWriter<>( - new PairWriter<>( - ScaleCodecWriter::writeAsList, - ScaleCodecWriter::writeAsList)), - pairs); + new ListWriter<>( + new PairWriter<>( + ScaleCodecWriter::writeAsList, + ScaleCodecWriter::writeAsList)), + pairs); } /** @@ -159,16 +170,17 @@ public byte[] encode(ScaleWriter writer, T value) { * Scale encodes a nullable value as an optional. * If the value is null, it is encoded as an empty optional. * If the value is not null, it is encoded as an optional with a present value. + * * @param writer The ScaleWriter for encoding the value, if not null. - * @param value The nullable object to encode. - * @return The encoded optional value. + * @param value The nullable object to encode. * @param + * @return The encoded optional value. * @throws ScaleEncodingException If an unexpected error occurs during encoding. */ public byte[] encodeOptional(ScaleWriter writer, @Nullable T value) { return ScaleUtils.Encode.encode( - (scaleCodecWriter, val) -> scaleCodecWriter.writeOptional(writer, val), - value + (scaleCodecWriter, val) -> scaleCodecWriter.writeOptional(writer, val), + value ); } } diff --git a/src/main/java/com/limechain/utils/scale/readers/PairReader.java b/src/main/java/com/limechain/utils/scale/readers/PairReader.java index 24ebd18c8..f2e01f95c 100644 --- a/src/main/java/com/limechain/utils/scale/readers/PairReader.java +++ b/src/main/java/com/limechain/utils/scale/readers/PairReader.java @@ -2,8 +2,8 @@ import io.emeraldpay.polkaj.scale.ScaleCodecReader; import io.emeraldpay.polkaj.scale.ScaleReader; -import org.javatuples.Pair; import lombok.AllArgsConstructor; +import org.javatuples.Pair; @AllArgsConstructor public class PairReader implements ScaleReader> { diff --git a/src/main/java/com/limechain/utils/scale/readers/TransactionValidationReader.java b/src/main/java/com/limechain/utils/scale/readers/TransactionValidationReader.java new file mode 100644 index 000000000..e4abf13fe --- /dev/null +++ b/src/main/java/com/limechain/utils/scale/readers/TransactionValidationReader.java @@ -0,0 +1,56 @@ +package com.limechain.utils.scale.readers; + +import com.limechain.transaction.dto.InvalidTransactionType; +import com.limechain.transaction.dto.TransactionValidationResponse; +import com.limechain.transaction.dto.TransactionValidity; +import com.limechain.transaction.dto.TransactionValidityError; +import com.limechain.transaction.dto.UnknownTransactionType; +import com.limechain.utils.scale.ScaleUtils; +import io.emeraldpay.polkaj.scale.ScaleCodecReader; +import io.emeraldpay.polkaj.scale.ScaleReader; +import io.emeraldpay.polkaj.scale.reader.UInt64Reader; + +public class TransactionValidationReader implements ScaleReader { + + private static final int INVALID_TRANSACTION_TYPE = 0; + + @Override + public TransactionValidationResponse read(ScaleCodecReader reader) { + TransactionValidationResponse response = new TransactionValidationResponse(); + + if (ScaleUtils.isScaleResultSuccessful(reader)) { + TransactionValidity validity = new TransactionValidity(); + + validity.setPriority(new UInt64Reader().read(reader)); + + int requiresCount = reader.readCompactInt(); + byte[][] requires = new byte[requiresCount][]; + for (int i = 0; i < requiresCount; i++) { + requires[i] = reader.readByteArray(); + } + validity.setRequires(requires); + + int providesCount = reader.readCompactInt(); + byte[][] provides = new byte[providesCount][]; + for (int i = 0; i < providesCount; i++) { + provides[i] = reader.readByteArray(); + } + validity.setProvides(provides); + + validity.setLongevity(new UInt64Reader().read(reader)); + validity.setPropagate(reader.readUByte() != 0); + + response.setValidTx(validity); + } else { + int errorType = reader.readUByte(); + int errorInt = reader.readUByte(); + TransactionValidityError error = errorType == INVALID_TRANSACTION_TYPE + ? InvalidTransactionType.getFromInt(errorInt) + : UnknownTransactionType.getFromInt(errorInt); + + response.setTransactionValidityError(error); + } + + return response; + } +} diff --git a/src/main/java/com/limechain/utils/scale/writers/IterableWriter.java b/src/main/java/com/limechain/utils/scale/writers/IterableWriter.java index 24894ef9b..f8179693f 100644 --- a/src/main/java/com/limechain/utils/scale/writers/IterableWriter.java +++ b/src/main/java/com/limechain/utils/scale/writers/IterableWriter.java @@ -5,7 +5,6 @@ import org.apache.commons.collections4.IterableUtils; import java.io.IOException; -import java.util.Iterator; public class IterableWriter implements ScaleWriter> { private final ScaleWriter scaleWriter; diff --git a/src/main/java/com/limechain/utils/scale/writers/TransactionValidationWriter.java b/src/main/java/com/limechain/utils/scale/writers/TransactionValidationWriter.java new file mode 100644 index 000000000..e0138cc88 --- /dev/null +++ b/src/main/java/com/limechain/utils/scale/writers/TransactionValidationWriter.java @@ -0,0 +1,24 @@ +package com.limechain.utils.scale.writers; + +import com.limechain.transaction.dto.TransactionValidationRequest; +import io.emeraldpay.polkaj.scale.ScaleCodecWriter; +import io.emeraldpay.polkaj.scale.ScaleWriter; + +import java.io.IOException; +import java.util.Objects; + +public class TransactionValidationWriter implements ScaleWriter { + + @Override + public void write(ScaleCodecWriter writer, TransactionValidationRequest request) throws IOException { + if (!Objects.isNull(request.getSource())) { + writer.writeByte(request.getSource().ordinal()); + } + + writer.writeAsList(request.getTransaction()); + + if (!Objects.isNull(request.getParentBlockHash())) { + writer.writeByteArray(request.getParentBlockHash().getBytes()); + } + } +} diff --git a/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceControllerTest.java b/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceControllerTest.java index 887e8aae8..696a52623 100644 --- a/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceControllerTest.java +++ b/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceControllerTest.java @@ -1,6 +1,5 @@ package com.limechain.network.protocol.blockannounce; -import com.limechain.network.protocol.blockannounce.messages.BlockAnnounceHandshake; import com.limechain.network.protocol.blockannounce.messages.BlockAnnounceHandshakeBuilder; import io.libp2p.core.PeerId; import io.libp2p.core.Stream; @@ -9,7 +8,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.verify; diff --git a/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceServiceTest.java b/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceServiceTest.java index cfc5ff562..46e60cdda 100644 --- a/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceServiceTest.java +++ b/src/test/java/com/limechain/network/protocol/blockannounce/BlockAnnounceServiceTest.java @@ -43,6 +43,7 @@ class BlockAnnounceServiceTest { @BeforeEach public void setupEach() throws NoSuchFieldException, IllegalAccessException { + when(host.getAddressBook()).thenReturn(addressBook); setPrivateFieldOfSuperclass(blockAnnounceService, "protocol", protocol); } @@ -50,7 +51,7 @@ public void setupEach() throws NoSuchFieldException, IllegalAccessException { void sendHandshake() { when(protocol.dialPeer(host, peerId, addressBook)).thenReturn(blockAnnounceController); - blockAnnounceService.sendHandshake(host, addressBook, peerId); + blockAnnounceService.sendHandshake(host, peerId); verify(blockAnnounceController).sendHandshake(); } @@ -103,7 +104,7 @@ void receivesNotifications() { if (addr.length == 0) throw new IllegalStateException("No addresses known for peer " + peerId); - blockAnnounceService.sendHandshake(senderNode, senderNode.getAddressBook(), peerId); + blockAnnounceService.sendHandshake(senderNode, peerId); Thread.sleep(60000); } catch ( diff --git a/src/test/java/com/limechain/network/protocol/transaction/state/TransactionStateTest.java b/src/test/java/com/limechain/network/protocol/transaction/state/TransactionStateTest.java new file mode 100644 index 000000000..f299c8e7d --- /dev/null +++ b/src/test/java/com/limechain/network/protocol/transaction/state/TransactionStateTest.java @@ -0,0 +1,126 @@ +package com.limechain.network.protocol.transaction.state; + +import com.limechain.transaction.TransactionState; +import com.limechain.transaction.dto.Extrinsic; +import com.limechain.transaction.dto.TransactionValidity; +import com.limechain.transaction.dto.ValidTransaction; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TransactionStateTest { + + private static final byte[] TAG_1 = "tag1".getBytes(); + private static final byte[] TAG_2 = "tag2".getBytes(); + + private TransactionState sut; + + @BeforeEach + void setUp() { + sut = new TransactionState(); + } + + @Test + void testTransactionState() { + ValidTransaction[] validTransactions = new ValidTransaction[]{ + new ValidTransaction(new Extrinsic(new byte[]{'a'}), new TransactionValidity()), + new ValidTransaction(new Extrinsic(new byte[]{'b'}), new TransactionValidity()), + new ValidTransaction(new Extrinsic(new byte[]{'c'}), new TransactionValidity()), + new ValidTransaction(new Extrinsic(new byte[]{'d'}), new TransactionValidity()), + new ValidTransaction(new Extrinsic(new byte[]{'e'}), new TransactionValidity()) + }; + for (ValidTransaction validTransaction : + validTransactions) { + sut.addToPool(validTransaction); + } + + ValidTransaction[] pendingInPool = sut.pendingInPool(); + + Arrays.sort(pendingInPool, Comparator.comparing(a -> new String(a.getExtrinsic().getData()))); + + assertArrayEquals(validTransactions, pendingInPool); + assertNull(sut.peek()); + } + + @Test + void testShouldAddToQueue_AllRequiredProvided() { + TransactionValidity validity1 = new TransactionValidity( + BigInteger.TEN, null, new byte[][]{TAG_1, TAG_2}, BigInteger.ZERO, true); + ValidTransaction existingTransaction = new ValidTransaction(null, validity1); + + sut.pushTransaction(existingTransaction); + + TransactionValidity validity2 = new TransactionValidity( + BigInteger.ONE, new byte[][]{TAG_1, TAG_2}, null, BigInteger.ZERO, true); + ValidTransaction validTransaction = new ValidTransaction(null, validity2); + + boolean result = sut.shouldAddToQueue(validTransaction); + assertTrue(result); + } + + @Test + void testShouldAddToQueue_NotAllRequiredProvided() { + TransactionValidity validity1 = new TransactionValidity( + BigInteger.TEN, null, new byte[][]{TAG_1}, BigInteger.ZERO, true); + ValidTransaction existingTransaction = new ValidTransaction(null, validity1); + + sut.pushTransaction(existingTransaction); + + TransactionValidity validity2 = new TransactionValidity( + BigInteger.ONE, new byte[][]{TAG_1, TAG_2}, null, BigInteger.ZERO, true); + ValidTransaction validTransaction = new ValidTransaction(null, validity2); + + boolean result = sut.shouldAddToQueue(validTransaction); + assertFalse(result); + } + + @Test + void testShouldAddToQueue_EmptyTransactionQueue() { + TransactionValidity validity2 = new TransactionValidity( + BigInteger.ONE, new byte[][]{TAG_1}, null, BigInteger.ZERO, true); + ValidTransaction validTransaction = new ValidTransaction(null, validity2); + + boolean result = sut.shouldAddToQueue(validTransaction); + assertFalse(result); + } + + @Test + void testShouldAddToQueue_TransactionHasNoRequires() { + TransactionValidity validity1 = new TransactionValidity( + BigInteger.TEN, null, new byte[][]{TAG_1}, BigInteger.ZERO, true); + ValidTransaction existingTransaction = new ValidTransaction(null, validity1); + + sut.pushTransaction(existingTransaction); + + TransactionValidity validity2 = new TransactionValidity( + BigInteger.ONE, new byte[][]{}, null, BigInteger.ZERO, true); + ValidTransaction validTransaction = new ValidTransaction(null, validity2); + + boolean result = sut.shouldAddToQueue(validTransaction); + assertTrue(result); + } + + @Test + void testShouldAddToQueue_TransactionHasNoProvides() { + TransactionValidity validity1 = new TransactionValidity( + BigInteger.TEN, null, new byte[][]{}, BigInteger.ZERO, true); + ValidTransaction existingTransaction = new ValidTransaction(null, validity1); + + sut.pushTransaction(existingTransaction); + + TransactionValidity validity2 = new TransactionValidity( + BigInteger.ONE, new byte[][]{TAG_1}, null, BigInteger.ZERO, true); + ValidTransaction validTransaction = new ValidTransaction(null, validity2); + + boolean result = sut.shouldAddToQueue(validTransaction); + assertFalse(result); + } +} diff --git a/src/test/java/com/limechain/runtime/hostapi/OffchainHostFunctionsTest.java b/src/test/java/com/limechain/runtime/hostapi/OffchainHostFunctionsTest.java index f7a0e9ba7..50bbb37e2 100644 --- a/src/test/java/com/limechain/runtime/hostapi/OffchainHostFunctionsTest.java +++ b/src/test/java/com/limechain/runtime/hostapi/OffchainHostFunctionsTest.java @@ -1,12 +1,12 @@ package com.limechain.runtime.hostapi; +import com.limechain.exception.hostapi.InvalidArgumentException; import com.limechain.runtime.SharedMemory; -import com.limechain.runtime.hostapi.dto.OffchainNetworkState; -import com.limechain.storage.offchain.OffchainStorages; import com.limechain.runtime.hostapi.dto.HttpErrorType; import com.limechain.runtime.hostapi.dto.HttpStatusCode; -import com.limechain.exception.hostapi.InvalidArgumentException; +import com.limechain.runtime.hostapi.dto.OffchainNetworkState; import com.limechain.runtime.hostapi.dto.RuntimePointerSize; +import com.limechain.storage.offchain.OffchainStorages; import com.limechain.storage.offchain.OffchainStore; import com.limechain.utils.scale.ScaleUtils; import io.emeraldpay.polkaj.scale.ScaleCodecWriter; diff --git a/src/test/java/com/limechain/storage/DBInitializerTest.java b/src/test/java/com/limechain/storage/DBInitializerTest.java index 4b341889e..04a99f925 100644 --- a/src/test/java/com/limechain/storage/DBInitializerTest.java +++ b/src/test/java/com/limechain/storage/DBInitializerTest.java @@ -4,7 +4,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import sun.misc.Unsafe; import java.lang.reflect.Field; diff --git a/src/test/java/com/limechain/sync/fullsync/BlockExecutorTest.java b/src/test/java/com/limechain/sync/fullsync/BlockExecutorTest.java index 6b9fd1339..65d98777e 100644 --- a/src/test/java/com/limechain/sync/fullsync/BlockExecutorTest.java +++ b/src/test/java/com/limechain/sync/fullsync/BlockExecutorTest.java @@ -5,7 +5,7 @@ import com.limechain.network.protocol.warp.dto.Block; import com.limechain.network.protocol.warp.dto.BlockBody; import com.limechain.network.protocol.warp.dto.BlockHeader; -import com.limechain.network.protocol.warp.dto.Extrinsics; +import com.limechain.transaction.dto.Extrinsic; import com.limechain.network.protocol.warp.dto.HeaderDigest; import com.limechain.network.protocol.warp.scale.reader.HeaderDigestReader; import com.limechain.runtime.Runtime; @@ -75,7 +75,7 @@ private Block getKusamaFirstBlock() { var exts = ScaleUtils.Decode.decodeList(scaleEncodedBody, ScaleCodecReader::readByteArray); assertEquals(2, exts.size()); - BlockBody body = new BlockBody(exts.stream().map(Extrinsics::new).toList()); + BlockBody body = new BlockBody(exts.stream().map(Extrinsic::new).toList()); return new Block(header, body); } diff --git a/src/test/java/com/limechain/trie/structure/slab/SlabTest.java b/src/test/java/com/limechain/trie/structure/slab/SlabTest.java index 7cf0d6ee4..bee35d6ca 100644 --- a/src/test/java/com/limechain/trie/structure/slab/SlabTest.java +++ b/src/test/java/com/limechain/trie/structure/slab/SlabTest.java @@ -4,11 +4,11 @@ import org.javatuples.Pair; import org.junit.jupiter.api.Test; -import java.util.Set; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; -import java.util.ArrayList; import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/com/limechain/utils/ByteArrayUtilsTest.java b/src/test/java/com/limechain/utils/ByteArrayUtilsTest.java index f8c7dd118..733bc3c3e 100644 --- a/src/test/java/com/limechain/utils/ByteArrayUtilsTest.java +++ b/src/test/java/com/limechain/utils/ByteArrayUtilsTest.java @@ -2,9 +2,21 @@ import org.junit.jupiter.api.Test; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class ByteArrayUtilsTest { + + private static final byte[] ELEMENT_1 = "element1".getBytes(); + private static final byte[] ELEMENT_2 = "element2".getBytes(); + private static final byte[] ELEMENT_3 = "element3".getBytes(); + + @Test void testTargetFound() { // Test with both arrays non-null and target found @@ -48,4 +60,88 @@ void testBothNull() { // Test with both arrays null assertEquals(-1, ByteArrayUtils.indexOf(null, null)); } + + @Test + void testSourceContainsAll_AllTargetElementsInSource() { + Collection source = List.of( + ELEMENT_1, + ELEMENT_2, + ELEMENT_3 + ); + + Collection target = List.of( + ELEMENT_1, + ELEMENT_2 + ); + + boolean result = ByteArrayUtils.sourceContainsAll(source, target); + assertTrue(result); + } + + @Test + void testSourceContainsAll_SourceHasExtraElements() { + Collection source = List.of( + ELEMENT_1, + ELEMENT_2, + ELEMENT_3, + "extraElement".getBytes() + ); + + Collection target = List.of( + ELEMENT_1, + ELEMENT_2 + ); + + boolean result = ByteArrayUtils.sourceContainsAll(source, target); + assertTrue(result); + } + + @Test + void testSourceContainsAll_MissingElementInSource() { + Collection source = List.of( + ELEMENT_1, + ELEMENT_2 + ); + + Collection target = List.of( + ELEMENT_1, + ELEMENT_2, + "missingElement".getBytes() + ); + + boolean result = ByteArrayUtils.sourceContainsAll(source, target); + assertFalse(result); + } + + @Test + void testSourceContainsAll_EmptySource() { + Collection source = Collections.emptyList(); + + Collection target = List.of( + ELEMENT_1 + ); + + boolean result = ByteArrayUtils.sourceContainsAll(source, target); + assertFalse(result); + } + + @Test + void testSourceContainsAll_EmptyTarget() { + Collection source = List.of( + ELEMENT_1, + ELEMENT_2 + ); + Collection target = Collections.emptyList(); + + boolean result = ByteArrayUtils.sourceContainsAll(source, target); + assertTrue(result); + } + + @Test + void testSourceContainsAll_BothEmpty() { + Collection source = Collections.emptyList(); + Collection target = Collections.emptyList(); + boolean result = ByteArrayUtils.sourceContainsAll(source, target); + assertTrue(result); // Both empty collections should result in true + } } \ No newline at end of file