├── .checkstyle ├── .gitignore ├── .travis.yml ├── README.md ├── checkstyle.xml ├── docs ├── Omniledger.pdf ├── Technical Report.pdf ├── scaling-blockchain-transfer.pdf └── workshop.pdf ├── findbugs-exclude.xml ├── pmd-rules.xml ├── pom.xml ├── src ├── main │ ├── java │ │ ├── com │ │ │ └── github │ │ │ │ └── jtendermint │ │ │ │ └── jabci │ │ │ │ └── socket │ │ │ │ └── TSocket.java │ │ └── nl │ │ │ └── tudelft │ │ │ └── blockchain │ │ │ └── scaleoutdistributedledger │ │ │ ├── Application.java │ │ │ ├── CommunicationHelper.java │ │ │ ├── LocalStore.java │ │ │ ├── ProofConstructor.java │ │ │ ├── SimulationMain.java │ │ │ ├── TrackerHelper.java │ │ │ ├── TransactionCreator.java │ │ │ ├── TransactionSender.java │ │ │ ├── TransactionTuple.java │ │ │ ├── exceptions │ │ │ ├── NodeRegisterFailedException.java │ │ │ ├── NodeStateChangeFailedException.java │ │ │ ├── NotEnoughMoneyException.java │ │ │ └── TrackerException.java │ │ │ ├── message │ │ │ ├── BlockMessage.java │ │ │ ├── Message.java │ │ │ ├── ProofMessage.java │ │ │ ├── StartTransactingMessage.java │ │ │ ├── StopTransactingMessage.java │ │ │ ├── TransactionMessage.java │ │ │ ├── TransactionPatternMessage.java │ │ │ └── UpdateNodesMessage.java │ │ │ ├── mocks │ │ │ └── TendermintChainMock.java │ │ │ ├── model │ │ │ ├── Block.java │ │ │ ├── BlockAbstract.java │ │ │ ├── Chain.java │ │ │ ├── ChainView.java │ │ │ ├── Ed25519Key.java │ │ │ ├── LightView.java │ │ │ ├── MetaKnowledge.java │ │ │ ├── Node.java │ │ │ ├── OwnNode.java │ │ │ ├── Proof.java │ │ │ ├── Sha256Hash.java │ │ │ ├── Transaction.java │ │ │ ├── TransactionRegistration.java │ │ │ └── mainchain │ │ │ │ ├── MainChain.java │ │ │ │ └── tendermint │ │ │ │ ├── ABCIClient.java │ │ │ │ ├── ABCIServer.java │ │ │ │ └── TendermintChain.java │ │ │ ├── simulation │ │ │ ├── CancellableInfiniteRunnable.java │ │ │ ├── PoissonRandom.java │ │ │ ├── Simulation.java │ │ │ ├── SimulationState.java │ │ │ ├── tendermint │ │ │ │ └── TendermintHelper.java │ │ │ └── transactionpattern │ │ │ │ ├── ITransactionPattern.java │ │ │ │ ├── OnlyNodeZeroTransactionPattern.java │ │ │ │ ├── PoissonRandomTransactionPattern.java │ │ │ │ ├── RandomTransactionPattern.java │ │ │ │ └── UniformRandomTransactionPattern.java │ │ │ ├── sockets │ │ │ ├── SocketClient.java │ │ │ ├── SocketClientHandler.java │ │ │ ├── SocketServer.java │ │ │ └── SocketServerHandler.java │ │ │ ├── utils │ │ │ ├── AppendOnlyArrayList.java │ │ │ ├── Log.java │ │ │ ├── LogFormatter.java │ │ │ └── Utils.java │ │ │ └── validation │ │ │ ├── ProofValidationException.java │ │ │ ├── ValidationException.java │ │ │ └── Verification.java │ └── resources │ │ └── .gitignore └── test │ ├── java │ └── nl │ │ └── tudelft │ │ └── blockchain │ │ └── scaleoutdistributedledger │ │ ├── ApplicationTest.java │ │ ├── CommunicationHelperTest.java │ │ ├── LocalStoreTest.java │ │ ├── TransactionCreatorTest.java │ │ ├── message │ │ ├── MessageTest.java │ │ ├── StartTransactingMessageTest.java │ │ ├── StopTransactingMessageTest.java │ │ └── TransactionPatternMessageTest.java │ │ ├── model │ │ ├── BlockAbstractTest.java │ │ ├── BlockTest.java │ │ ├── ChainTest.java │ │ ├── ChainViewTest.java │ │ ├── Ed25519KeyTest.java │ │ ├── NodeTest.java │ │ ├── ProofTest.java │ │ ├── SerializationTest.java │ │ ├── Sha256HashTest.java │ │ └── mainchain │ │ │ └── tendermint │ │ │ ├── ABCIClientTest.java │ │ │ └── TendermintChainTest.java │ │ ├── simulation │ │ └── SimulationTest.java │ │ ├── test │ │ └── utils │ │ │ ├── SilencedTestClass.java │ │ │ └── TestHelper.java │ │ └── utils │ │ └── UtilsTest.java │ └── resources │ └── .gitignore └── tracker-server ├── .babelrc ├── .eslintrc ├── .gitignore ├── app.js ├── helpers └── sse.js ├── model ├── Node.js ├── NodeList.js ├── Transaction.js └── TransactionList.js ├── package-lock.json ├── package.json ├── public ├── images │ ├── background.png │ └── background.psd ├── javascripts │ ├── demo.js │ ├── jsnetworkx.js │ └── odometer.min.js └── stylesheets │ ├── demo.css │ └── odometer-theme-car.css ├── routes └── index.js └── views └── demo.ejs /.checkstyle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore binary files 2 | *.class 3 | bin/ 4 | target/ 5 | tmp/ 6 | logs/ 7 | build/ 8 | 9 | # Ignore system specific binaries 10 | *.so 11 | *.dll 12 | *.dylib 13 | 14 | # Ignore eclipse files 15 | .settings/ 16 | .classpath 17 | .project 18 | .pmd 19 | 20 | # Ignore windows auxilary files 21 | Thumbs.db 22 | 23 | # Ignore IntelliJ settings files 24 | *.iml 25 | .idea/ 26 | 27 | # Ignore Netbeans settings files 28 | nb-configuration.xml 29 | 30 | # Ignore log files 31 | *.log 32 | 33 | # Tendermint 34 | tendermint-nodes/ 35 | tendermint.exe 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | # NOTE: The build lifecycle on Travis.ci is something like this: 3 | # before_install 4 | # install 5 | # before_script 6 | # script 7 | # after_success or after_failure 8 | # after_script 9 | # OPTIONAL before_deploy 10 | # OPTIONAL deploy 11 | # OPTIONAL after_deploy 12 | 13 | ################################################################################ 14 | 15 | # Use ubuntu trusty (14.04) with sudo privileges. 16 | dist: trusty 17 | sudo: false 18 | language: java 19 | jdk: 20 | - oraclejdk8 21 | 22 | node_js: 23 | - '6' 24 | 25 | branches: 26 | except: 27 | - /^doc-.*$/ 28 | 29 | # Configuration variables. 30 | env: 31 | global: 32 | - CI_SOURCE_PATH=$(pwd) 33 | 34 | ################################################################################ 35 | 36 | # Start tracker server 37 | before_install: 38 | - cd tracker-server 39 | - npm install 40 | - npm start 2>&1 > /dev/null & 41 | - sleep 3 42 | - cd .. 43 | 44 | install: 45 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dcheckstyle.skip=true -Dfindbugs.skip=true -Dpmd.skip=true -Djacoco.skip=true -V -B 46 | 47 | script: 48 | - mvn test -B 49 | 50 | after_success: 51 | # - bash <(curl -s https://codecov.io/bash) 52 | - echo "== CHECKSTYLE_RESULT =="; cat "target/checkstyle-result.xml"; echo "== END_CHECKSTYLE_RESULT ==" 53 | - echo "== PMD_RESULT =="; cat "target/pmd.xml"; echo "== END_PMD_RESULT ==" 54 | - echo "== FINDBUGS_RESULT =="; cat "target/findbugsXml.xml"; echo "== END_FINDBUGS_RESULT ==" 55 | 56 | after_failure: 57 | - echo "== CHECKSTYLE_RESULT =="; cat "target/checkstyle-result.xml"; echo "== END_CHECKSTYLE_RESULT ==" 58 | - echo "== PMD_RESULT =="; cat "target/pmd.xml"; echo "== END_PMD_RESULT ==" 59 | - echo "== FINDBUGS_RESULT =="; cat "target/findbugsXml.xml"; echo "== END_FINDBUGS_RESULT ==" 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Scale-out Distributed Ledger 3 |

4 | 5 |

TU Delft Blockchain Engineering course project on scale-out distributed ledger.

6 | 7 | ## Paper 8 | This project implements the system described in the following paper: 9 | 10 | > ## A Scale-out Blockchain for Value Transfer with Spontaneous Sharding 11 | > #### By Zhijie Ren and Zekeriya Erkin [[PDF]](https://arxiv.org/abs/1801.02531) 12 | > Blockchain technology, sometimes known by its applications like cryptocurrencies, suffers from the scalability problem mainly due to the unideal throughput of Byzantine fault tolerance consensus algorithms. Recently, many blockchains have been proposed to achieve scale-out throughput, i.e., the throughput of the system grows with the number of nodes. In this paper, we propose a novel scale-out blockchain system for the most considered type of ledgers, we call Value Transferring Ledgers, in which a transaction is a transfer of positive value from one node to another. In our system, nodes commonly agree on a main chain and individually generate their own chains. We propose a locally executable validation scheme with uncompromised validity and scalable throughput. Furthermore, a smart transacting algorithm is introduced so that the system is spontaneously sharded for individual transactions and achieves scale-out throughput. 13 | 14 | An in-depth explanation of the system is available as a technical report in the `docs` folder. 15 | 16 | ## Running instructions 17 | ### Requirements 18 | - [Java](https://java.com/en/download/) 8 or newer 19 | - [NodeJS](https://nodejs.org/) 6 or newer 20 | - [Tendermint](https://tendermint.com/) 0.14.0 21 | 22 | ### Setup 23 | - Place a tendermint executable (V0.14) called `tendermint.exe` in the root folder of the project. The file-extension of the file must also be present on non-Windows systems. [Download here](https://tendermint.com/downloads) 24 | - Install the tracker server 25 | - In the folder `tracker-server` run `npm install` 26 | - Determine the master machine (when only using a single machine this must also be the master) 27 | - Determine the IP address of the tracker 28 | 29 | ### Configuration 30 | Apply these configuration steps for every machine 31 | - In the `SimulationMain`-class: 32 | - Give each machine its sets of node by changing the `LOCAL_NODES_NUMBER`, `TOTAL_NODES_NUMBER`, `NODES_FROM_NUMBER` values 33 | - Specify the parameters for the transaction sending behaviour of each node using the fields `MAX_BLOCKS_PENDING`, `INITIAL_SENDING_DELAY`, `SENDING_WAIT_TIME` and `REQUIRED_COMMITS`. 34 | - Set `IS_MASTER` to `true` for the master machine and to `false` for all the others 35 | - If the current machine is the master, also specify the simulation time in seconds. 36 | - In the `Application`-class: 37 | - Set `TRACKER_SERVER_ADDRESS` and `TRACKER_SERVER_PORT` to point to the server location 38 | 39 | ### Running 40 | - Start the tracker server 41 | - In folder `tracker-server` run `npm start` 42 | - Start the master machine by calling the main method in `SimulationMain`. 43 | - Start the other machines the same way as the master. 44 | - A live visualization of the network can be seen by opening `/demo` in a browser. 45 | 46 | ### Run Tests 47 | From the root folder run `mvn test`. 48 | -------------------------------------------------------------------------------- /docs/Omniledger.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/docs/Omniledger.pdf -------------------------------------------------------------------------------- /docs/Technical Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/docs/Technical Report.pdf -------------------------------------------------------------------------------- /docs/scaling-blockchain-transfer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/docs/scaling-blockchain-transfer.pdf -------------------------------------------------------------------------------- /docs/workshop.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/docs/workshop.pdf -------------------------------------------------------------------------------- /findbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pmd-rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Custom rules for checking JUnit test quality. 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/Application.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import java.io.IOException; 4 | import java.util.logging.Level; 5 | 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Ed25519Key; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.MainChain; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.CancellableInfiniteRunnable; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern.ITransactionPattern; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.sockets.SocketServer; 13 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 14 | 15 | import lombok.Getter; 16 | 17 | /** 18 | * Class to run a node. 19 | */ 20 | public class Application { 21 | public static final String TRACKER_SERVER_ADDRESS = "localhost"; 22 | public static final int TRACKER_SERVER_PORT = 3000; 23 | public static final int NODE_PORT = 40000; 24 | 25 | @Getter 26 | private LocalStore localStore; 27 | private Thread executor; 28 | private CancellableInfiniteRunnable transactionExecutable; 29 | private final boolean isProduction; 30 | 31 | @Getter 32 | private Thread serverThread; 33 | 34 | @Getter 35 | private TransactionSender transactionSender; 36 | 37 | /** 38 | * Creates a new application. 39 | * The application must be initialized with {@link #init(int, Block, Ed25519Key, OwnNode)} before it can be used. 40 | * @param isProduction - if this is production or testing 41 | */ 42 | public Application(boolean isProduction) { 43 | this.isProduction = isProduction; 44 | } 45 | 46 | /** 47 | * Constructor used for testing. 48 | * @param localStore - the localStore to use 49 | * @param serverThread - the serverThread to use 50 | * @param transactionSender - the transactionSender to use 51 | */ 52 | protected Application(LocalStore localStore, Thread serverThread, TransactionSender transactionSender) { 53 | this.localStore = localStore; 54 | this.serverThread = serverThread; 55 | this.transactionSender = transactionSender; 56 | this.isProduction = false; 57 | } 58 | 59 | /** 60 | * Initializes the application. 61 | * Registers to the tracker and creates the local store. 62 | * @param nodePort - the port on which the node will accept connections. Note, also port+1, 63 | * port+2 and port+3 are used (for tendermint: p2p.laddr, rpc.laddr, ABCI server). 64 | * @param genesisBlock - the genesis (initial) block for the entire system 65 | * @param key - the key 66 | * @param ownNode - the own node 67 | * @throws IOException - error while registering node 68 | */ 69 | public void init(int nodePort, Block genesisBlock, Ed25519Key key, OwnNode ownNode) throws IOException { 70 | ownNode.getChain().setGenesisBlock(genesisBlock); 71 | 72 | ownNode.setPrivateKey(key.getPrivateKey()); 73 | 74 | // Setup local store 75 | localStore = new LocalStore(ownNode, this, genesisBlock, this.isProduction); 76 | localStore.initMainChain(); 77 | 78 | serverThread = new Thread(new SocketServer(nodePort, localStore)); 79 | serverThread.start(); 80 | transactionSender = new TransactionSender(localStore); 81 | TrackerHelper.setRunning(ownNode.getId(), true); 82 | } 83 | 84 | /** 85 | * Stops this application. This means that this application no longer accepts any new 86 | * connections and that all existing connections are closed. 87 | */ 88 | public void kill() { 89 | if (serverThread.isAlive()) serverThread.interrupt(); 90 | if (transactionSender != null) transactionSender.shutdownNow(); 91 | 92 | localStore.getMainChain().stop(); 93 | } 94 | 95 | /** 96 | * @param pattern - the transaction pattern 97 | * @throws IllegalStateException - If there is already a transaction pattern running. 98 | */ 99 | public synchronized void setTransactionPattern(ITransactionPattern pattern) { 100 | if (isTransacting()) throw new IllegalStateException("There is already a transaction pattern running!"); 101 | this.transactionExecutable = pattern.getRunnable(localStore); 102 | Log.log(Level.FINE, "Node " + localStore.getOwnNode().getId() + ": Set transaction pattern " + pattern.getName()); 103 | } 104 | 105 | /** 106 | * Starts making transactions by executing the transaction pattern. 107 | * @throws IllegalStateException - If there is already a transaction pattern running. 108 | */ 109 | public synchronized void startTransacting() { 110 | if (isTransacting()) throw new IllegalStateException("There is already a transaction pattern running!"); 111 | this.executor = new Thread(this.transactionExecutable); 112 | this.executor.setUncaughtExceptionHandler((t, ex) -> 113 | Log.log(Level.SEVERE, "Node " + localStore.getOwnNode().getId() + ": Uncaught exception in transaction pattern executor!", ex) 114 | ); 115 | this.executor.start(); 116 | Log.log(Level.INFO, "Node " + localStore.getOwnNode().getId() + ": Started transacting with transaction pattern."); 117 | } 118 | 119 | /** 120 | * Stops making transactions. 121 | */ 122 | public synchronized void stopTransacting() { 123 | if (!isTransacting()) return; 124 | this.transactionExecutable.cancel(); 125 | Log.log(Level.INFO, "Node " + localStore.getOwnNode().getId() + ": Stopped transacting with transaction pattern."); 126 | } 127 | 128 | /** 129 | * @return if a transaction pattern is running 130 | */ 131 | public synchronized boolean isTransacting() { 132 | return this.executor != null && this.executor.isAlive(); 133 | } 134 | 135 | /** 136 | * @return - the main chain of this application 137 | */ 138 | public MainChain getMainChain() { 139 | return localStore.getMainChain(); 140 | } 141 | 142 | /** 143 | * Stop sending transactions and wait to finish sending. 144 | * This also marks the current node as stopped on the tracker. 145 | */ 146 | public void finishTransactionSending() { 147 | int nodeID = localStore.getOwnNode().getId(); 148 | try { 149 | transactionSender.waitUntilDone(); 150 | TrackerHelper.setRunning(nodeID, false); 151 | } catch (IOException ex) { 152 | Log.log(Level.SEVERE, "Cannot update running status to stopped for node " + nodeID); 153 | } catch (InterruptedException e) { 154 | Log.log(Level.SEVERE, "Thread interrupted, node " + nodeID + " is not marked stopped"); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/CommunicationHelper.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Proof; 4 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.validation.ValidationException; 6 | 7 | import java.util.logging.Level; 8 | 9 | /** 10 | * Helper class for communication. 11 | */ 12 | public final class CommunicationHelper { 13 | private CommunicationHelper() { 14 | throw new UnsupportedOperationException(); 15 | } 16 | 17 | /** 18 | * @param proof - the proof provided with the transaction 19 | * @param localStore - the localstore of the node 20 | * @return true if the transaction was accepted, false otherwise 21 | */ 22 | public static boolean receiveTransaction(Proof proof, LocalStore localStore) { 23 | Log.log(Level.FINE, "Received transaction: " + proof.getTransaction()); 24 | 25 | if (proof.getTransaction().getReceiver().getId() != localStore.getOwnNode().getId()) { 26 | Log.log(Level.WARNING, "Received a transaction that isn't for us: " + proof.getTransaction()); 27 | return false; 28 | } 29 | 30 | try { 31 | localStore.getVerification().validateNewMessage(proof, localStore); 32 | } catch (ValidationException ex) { 33 | Log.log(Level.WARNING, "Received an invalid transaction/proof " + proof.getTransaction() + ": " + ex.getMessage()); 34 | return false; 35 | } 36 | 37 | Log.log(Level.INFO, "Received and validated transaction: " + proof.getTransaction()); 38 | Log.log(Level.FINE, "Transaction " + proof.getTransaction() + " is valid, applying updates..."); 39 | proof.applyUpdates(localStore); 40 | TrackerHelper.registerTransaction(proof); 41 | 42 | if (proof.getTransaction().getAmount() > 0) { 43 | localStore.addUnspentTransaction(proof.getTransaction()); 44 | } 45 | 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/ProofConstructor.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.MetaKnowledge; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Proof; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 13 | 14 | /** 15 | * Class for constructing proofs. 16 | */ 17 | public class ProofConstructor { 18 | 19 | private final Transaction mainTransaction; 20 | private final Node receiver; 21 | private final Node sender; 22 | private final Map> toSend; 23 | private final Proof proof; 24 | 25 | /** 26 | * @param mainTransaction - the transaction to construct the proof for 27 | */ 28 | public ProofConstructor(Transaction mainTransaction) { 29 | this.mainTransaction = mainTransaction; 30 | this.receiver = mainTransaction.getReceiver(); 31 | this.sender = mainTransaction.getSender(); 32 | this.proof = new Proof(mainTransaction); 33 | this.toSend = proof.getChainUpdates(); 34 | } 35 | 36 | /** 37 | * @return - the constructed proof 38 | */ 39 | public synchronized Proof constructProof() { 40 | //If the proof was already constructed, return it. 41 | if (!toSend.isEmpty()) return proof; 42 | 43 | MetaKnowledge metaKnowledge = receiver.getMetaKnowledge(); 44 | int mainBlockNr = mainTransaction.getBlockNumber().getAsInt(); 45 | Block nextCommitted = sender.getChain().getBlocks().get(mainBlockNr).getNextCommittedBlock(); 46 | List ownBlocks = metaKnowledge.getBlocksToSend(sender, nextCommitted.getNumber()); 47 | 48 | //Base case: no blocks to send 49 | if (ownBlocks.isEmpty()) { 50 | return proof; 51 | } 52 | 53 | //Recursively process all the blocks 54 | processBlocks(sender, ownBlocks); 55 | return proof; 56 | } 57 | 58 | /** 59 | * Processes the given list of blocks belonging to the given owner. 60 | * The given list is expected to be non-empty. 61 | * @param owner - the owner of the blocks 62 | * @param blocks - the blocks 63 | */ 64 | protected void processBlocks(Node owner, List blocks) { 65 | List newlyAdded = addBlocksToSend(owner, blocks); 66 | for (Block block : newlyAdded) { 67 | for (Transaction transaction : block.getTransactions()) { 68 | processSources(transaction); 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Processes the sources of the given transaction. 75 | * @param transaction - the transaction to process 76 | */ 77 | protected void processSources(Transaction transaction) { 78 | for (Transaction source : transaction.getSource()) { 79 | Node owner = source.getSender(); 80 | //Skip all sources in genesis blocks, our own blocks and in receiver blocks 81 | if (owner == null || owner == this.sender || owner == this.receiver) continue; 82 | 83 | int blockNumber = source.getBlockNumber().getAsInt(); 84 | 85 | Block block = owner.getChain().getBlocks().get(blockNumber); 86 | int nextCommittedBlockNr = block.getNextCommittedBlock().getNumber(); 87 | 88 | //Determine the blocks that we would need to send. 89 | MetaKnowledge metaKnowledge = this.receiver.getMetaKnowledge(); 90 | List blocksOfSource = metaKnowledge.getBlocksToSend(owner, nextCommittedBlockNr); 91 | if (blocksOfSource.isEmpty()) continue; 92 | 93 | processBlocks(owner, blocksOfSource); 94 | } 95 | } 96 | 97 | /** 98 | * Adds the given blocks belonging to the given owner to the toSend map. 99 | * @param owner - the owner of the blocks 100 | * @param toAdd - the blocks to add 101 | * @return - all the blocks that were added (not already in the toSend map) 102 | */ 103 | protected List addBlocksToSend(Node owner, List toAdd) { 104 | List current = toSend.computeIfAbsent(owner, n -> new ArrayList<>()); 105 | if (current.isEmpty()) { 106 | current.addAll(toAdd); 107 | return toAdd; 108 | } 109 | 110 | if (current.size() >= toAdd.size()) { 111 | //Nothing new 112 | return Collections.EMPTY_LIST; 113 | } 114 | 115 | //Since the blocks we are adding have been selected through the meta knowledge, they will go from firstUnknown up to a certain block. 116 | //E.g. [0, 1, 2] or [2, 3] 117 | //Also, we skip all blocks that we have already checked, we know that the entire history is effectively linear and that we check from 118 | //low to high block numbers. This means that the given list will contain all elements that we already have + some extra. 119 | //So we can start at the index equal to what we already have. 120 | 121 | assert current.containsAll(toAdd); 122 | assert current.size() < toAdd.size(); 123 | 124 | int startBlockNr = current.size(); 125 | List added = toAdd.subList(startBlockNr, toAdd.size()); 126 | current.addAll(added); 127 | return added; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/TransactionSender.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.logging.Level; 10 | 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.message.ProofMessage; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 13 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Chain; 14 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 15 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Proof; 16 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 17 | import nl.tudelft.blockchain.scaleoutdistributedledger.sockets.SocketClient; 18 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 19 | 20 | /** 21 | * Class which handles sending of transactions. 22 | */ 23 | public class TransactionSender implements Runnable { 24 | 25 | private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 26 | private final LocalStore localStore; 27 | private final SocketClient socketClient; 28 | private final Chain chain; 29 | private int alreadySent; 30 | 31 | /** 32 | * Creates a new TransactionSender. 33 | * @param localStore - the local store 34 | */ 35 | public TransactionSender(LocalStore localStore) { 36 | this.localStore = localStore; 37 | this.socketClient = new SocketClient(); 38 | this.chain = localStore.getOwnNode().getChain(); 39 | 40 | this.executor.schedule(this, SimulationMain.INITIAL_SENDING_DELAY, TimeUnit.MILLISECONDS); 41 | } 42 | 43 | @Override 44 | public void run() { 45 | //Send all and reschedule 46 | try { 47 | sendAllBlocksThatCanBeSent(); 48 | } catch (Exception ex) { 49 | Log.log(Level.SEVERE, "Uncaught exception in transaction sender!"); 50 | } finally { 51 | executor.schedule(this, SimulationMain.SENDING_WAIT_TIME, TimeUnit.MILLISECONDS); 52 | } 53 | } 54 | 55 | /** 56 | * Sends all blocks that can be sent. 57 | */ 58 | public void sendAllBlocksThatCanBeSent() { 59 | int lastBlockNr = chain.getLastBlockNumber(); 60 | 61 | //Determine what blocks have been committed since the last batch we sent 62 | List committed = new ArrayList<>(); 63 | for (int index = alreadySent + 1; index <= lastBlockNr; index++) { 64 | Block current = chain.getBlocks().get(index); 65 | Block next = current.getNextCommittedBlock(); 66 | if (next == null || !next.isOnMainChain(localStore)) break; 67 | 68 | committed.add(next.getNumber()); 69 | index = next.getNumber(); 70 | } 71 | 72 | //Not enough commits 73 | if (committed.size() < SimulationMain.REQUIRED_COMMITS) return; 74 | 75 | //Send all the blocks that we haven't sent up to the committed block (inclusive) 76 | int lastToSend = committed.get(committed.size() - SimulationMain.REQUIRED_COMMITS); 77 | for (int blockNr = alreadySent + 1; blockNr <= lastToSend; blockNr++) { 78 | sendBlock(chain.getBlocks().get(blockNr)); 79 | } 80 | } 81 | 82 | /** 83 | * @return - the number of blocks currently waiting to be sent 84 | */ 85 | public int blocksWaiting() { 86 | Block block = chain.getLastBlock(); 87 | if (block == null) return 0; 88 | 89 | Block prev = block; 90 | while (block != null && block.getTransactions().isEmpty()) { 91 | prev = block; 92 | block = block.getPreviousBlock(); 93 | } 94 | 95 | return prev.getNumber() - alreadySent; 96 | } 97 | 98 | /** 99 | * Sleeps until all transactions have been sent. 100 | * @throws InterruptedException - If we are interrupted while waiting. 101 | */ 102 | public void waitUntilDone() throws InterruptedException { 103 | while (blocksWaiting() > 0) { 104 | Thread.sleep(1000L); 105 | } 106 | } 107 | 108 | /** 109 | * Shuts down this Transaction sender as quickly as possible. 110 | * Pending blocks will not be sent. 111 | */ 112 | public void shutdownNow() { 113 | executor.shutdownNow(); 114 | socketClient.shutdown(); 115 | } 116 | 117 | /** 118 | * Sends the transactions in the given block. 119 | * @param block - the block 120 | */ 121 | private void sendBlock(Block block) { 122 | alreadySent = block.getNumber(); 123 | for (Transaction transaction : block.getTransactions()) { 124 | try { 125 | sendTransaction(transaction); 126 | } catch (Exception ex) { 127 | Log.log(Level.SEVERE, "Unable to send transaction " + transaction, ex); 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Sends the given transaction. 134 | * Can block up to 60 seconds. 135 | * @param transaction - the transaction to send 136 | * @return - if the sending succeeded 137 | * @throws InterruptedException - If the current thread was interrupted while sending. 138 | */ 139 | private boolean sendTransaction(Transaction transaction) throws InterruptedException, IOException { 140 | Log.log(Level.FINE, "Node " + transaction.getSender().getId() + " starting sending transaction: " + transaction); 141 | long startingTime = System.currentTimeMillis(); 142 | Node to = transaction.getReceiver(); 143 | 144 | ProofConstructor proofConstructor = new ProofConstructor(transaction); 145 | Proof proof = proofConstructor.constructProof(); 146 | ProofMessage msg = new ProofMessage(proof); 147 | 148 | //Check if the proof creation took a long time and log it. 149 | long timeDelta = System.currentTimeMillis() - startingTime; 150 | if (timeDelta > 5 * 1000) { 151 | Log.log(Level.WARNING, "Proof creation took " + timeDelta + " ms for transaction: " + transaction); 152 | } 153 | 154 | Log.log(Level.FINE, "Node " + transaction.getSender().getId() + " now actually sending transaction: " + transaction); 155 | if (socketClient.sendMessage(to, msg)) { 156 | to.updateMetaKnowledge(proof); 157 | Log.log(Level.FINE, "Node " + transaction.getSender().getId() + " done sending transaction: " + transaction); 158 | return true; 159 | } 160 | 161 | return false; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/TransactionTuple.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import java.util.BitSet; 4 | import java.util.TreeSet; 5 | import java.util.stream.Collectors; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 9 | 10 | import lombok.Getter; 11 | import lombok.Setter; 12 | 13 | /** 14 | * Class which represents a multiple of transactions. 15 | */ 16 | public class TransactionTuple { 17 | private final TransactionCreator creator; 18 | 19 | @Getter 20 | private int amount; 21 | 22 | @Getter 23 | private TreeSet transactions = new TreeSet<>(); 24 | 25 | @Getter @Setter 26 | private BitSet chainsRequired; 27 | 28 | /** 29 | * @param creator - the TransactionCreator 30 | * @param transaction - the initial transaction 31 | */ 32 | public TransactionTuple(TransactionCreator creator, Transaction transaction) { 33 | this.creator = creator; 34 | addTransaction(transaction); 35 | } 36 | 37 | /** 38 | * Creates a new tuple consisting of the given tuples. 39 | * @param tuple1 - the first tuple 40 | * @param tuple2 - the second tuple 41 | * @param chainsRequired - the bitset of required chains 42 | */ 43 | public TransactionTuple(TransactionTuple tuple1, TransactionTuple tuple2, BitSet chainsRequired) { 44 | this.creator = tuple1.creator; 45 | this.chainsRequired = chainsRequired; 46 | 47 | for (Transaction transaction : tuple1.transactions) { 48 | addTransactionAndAmount(transaction); 49 | } 50 | 51 | for (Transaction transaction : tuple2.transactions) { 52 | addTransactionAndAmount(transaction); 53 | } 54 | } 55 | 56 | /** 57 | * Adds the given transaction to this tuple. 58 | * @param transaction - the transaction 59 | */ 60 | public void addTransaction(Transaction transaction) { 61 | if (!addTransactionAndAmount(transaction)) return; 62 | 63 | BitSet newChainsRequired = creator.chainsRequired(transaction); 64 | if (this.chainsRequired == null) { 65 | this.chainsRequired = newChainsRequired; 66 | } else { 67 | this.chainsRequired.or(newChainsRequired); 68 | } 69 | } 70 | 71 | /** 72 | * Adds the given transaction and the correct amount to this tuple. 73 | * @param transaction - the transaction to add 74 | * @return true if the transaction was added, false if it was already in this tuple 75 | */ 76 | private boolean addTransactionAndAmount(Transaction transaction) { 77 | Node ownNode = creator.getSender(); 78 | if (ownNode != transaction.getSender() && ownNode != transaction.getReceiver()) { 79 | throw new IllegalArgumentException("The given transaction does not involve us, so we cannot use it as a source!"); 80 | } 81 | 82 | if (!this.transactions.add(transaction)) return false; 83 | 84 | if (ownNode == transaction.getSender()) { 85 | //A transaction we sent, so use the remainder 86 | this.amount += transaction.getRemainder(); 87 | } 88 | 89 | if (ownNode == transaction.getReceiver()) { 90 | //A transaction we received, so use the amount 91 | this.amount += transaction.getAmount(); 92 | } 93 | 94 | return true; 95 | } 96 | 97 | /** 98 | * Merges the given tuple into this tuple. 99 | * 100 | * The given tuple must not share any transactions with this tuple and must have the same 101 | * chain requirements. 102 | * @param tuple - the tuple to merge 103 | * @return this transaction tuple 104 | */ 105 | public TransactionTuple mergeNonOverlappingSameChainsTuple(TransactionTuple tuple) { 106 | this.transactions.addAll(tuple.transactions); 107 | this.amount += tuple.amount; 108 | return this; 109 | } 110 | 111 | /** 112 | * @param tuple - the tuple 113 | * @return if this tuple contains all the transactions in the given tuple 114 | */ 115 | public boolean containsAll(TransactionTuple tuple) { 116 | return transactions.containsAll(tuple.transactions); 117 | } 118 | 119 | /** 120 | * @return the amount of transactions in this tuple 121 | */ 122 | public int size() { 123 | return transactions.size(); 124 | } 125 | 126 | @Override 127 | public int hashCode() { 128 | final int prime = 31; 129 | int result = 1; 130 | result = prime * result + chainsRequired.hashCode(); 131 | result = prime * result + amount; 132 | result = prime * result + transactions.hashCode(); 133 | return result; 134 | } 135 | 136 | @Override 137 | public boolean equals(Object obj) { 138 | if (this == obj) return true; 139 | if (!(obj instanceof TransactionTuple)) return false; 140 | 141 | TransactionTuple other = (TransactionTuple) obj; 142 | if (amount != other.amount) return false; 143 | if (!chainsRequired.equals(other.chainsRequired)) return false; 144 | if (!transactions.equals(other.transactions)) return false; 145 | return true; 146 | } 147 | 148 | @Override 149 | public String toString() { 150 | String sources = transactions.stream() 151 | .map(Transaction::getNumber) 152 | .map(String::valueOf) 153 | .collect(Collectors.joining(", ", "<", ">")); 154 | return "Tuple(" + sources + ", $" + amount + ", required=" + chainsRequired + ")"; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/exceptions/NodeRegisterFailedException.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.exceptions; 2 | 3 | /** 4 | * Exception for indicating that registering with the tracker failed. 5 | */ 6 | public class NodeRegisterFailedException extends TrackerException { 7 | private static final long serialVersionUID = 8271135988867023425L; 8 | 9 | /** 10 | * Exception without message. 11 | */ 12 | public NodeRegisterFailedException() { 13 | super(); 14 | } 15 | 16 | /** 17 | * @param msg - the message 18 | */ 19 | public NodeRegisterFailedException(String msg) { 20 | super(msg); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/exceptions/NodeStateChangeFailedException.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.exceptions; 2 | 3 | /** 4 | * Exception for indicating that updating the running state of a node on the tracker failed. 5 | */ 6 | public class NodeStateChangeFailedException extends RuntimeException { 7 | private static final long serialVersionUID = 1L; 8 | 9 | /** 10 | * Constructor. 11 | * @param id - the ID of the node for which it failed 12 | * @param running - the running state that it could not be updated to 13 | */ 14 | public NodeStateChangeFailedException(int id, boolean running) { 15 | super("Failed to set the running state of node " + id + "to" + running); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/exceptions/NotEnoughMoneyException.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.exceptions; 2 | 3 | /** 4 | * Exception for indicating that the user doesn't have enough money. 5 | */ 6 | public class NotEnoughMoneyException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = -5115214404574584898L; 9 | 10 | /** 11 | * Exception without message. 12 | */ 13 | public NotEnoughMoneyException() { 14 | super(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/exceptions/TrackerException.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.exceptions; 2 | 3 | /** 4 | * Exception for the tracker. 5 | */ 6 | public class TrackerException extends RuntimeException { 7 | private static final long serialVersionUID = -3346246279993022763L; 8 | 9 | /** 10 | * Exception without message. 11 | */ 12 | public TrackerException() { 13 | super(); 14 | } 15 | 16 | /** 17 | * @param msg - the message 18 | */ 19 | public TrackerException(String msg) { 20 | super(msg); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/BlockMessage.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import lombok.Getter; 4 | 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Sha256Hash; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Block message for netty. 15 | */ 16 | public class BlockMessage extends Message { 17 | private static final long serialVersionUID = 1L; 18 | 19 | @Getter 20 | private final int number; 21 | 22 | @Getter 23 | private final int previousBlockNumber; 24 | 25 | @Getter 26 | private final int ownerId; 27 | 28 | @Getter 29 | private final List transactions; 30 | 31 | @Getter 32 | private final Sha256Hash hash; 33 | 34 | /** 35 | * Constructor. 36 | * @param block - original block 37 | */ 38 | public BlockMessage(Block block) { 39 | this.number = block.getNumber(); 40 | Block prevBlock = block.getPreviousBlock(); 41 | if (prevBlock != null) { 42 | this.previousBlockNumber = prevBlock.getNumber(); 43 | } else { 44 | // It's a genesis block 45 | this.previousBlockNumber = -1; 46 | } 47 | // It's a genesis block 48 | if (block.getOwner() == null) { 49 | this.ownerId = Transaction.GENESIS_SENDER; 50 | } else { 51 | this.ownerId = block.getOwner().getId(); 52 | } 53 | this.transactions = new ArrayList<>(); 54 | for (Transaction transaction : block.getTransactions()) { 55 | this.transactions.add(new TransactionMessage(transaction)); 56 | } 57 | this.hash = block.getHash(); 58 | } 59 | 60 | @Override 61 | public void handle(LocalStore localStore) { 62 | // Do nothing. 63 | } 64 | 65 | /** 66 | * @param localStore - the local store 67 | * @return - the block that this message represents, without any sources in the transactions 68 | */ 69 | public Block toBlockWithoutSources(LocalStore localStore) { 70 | List transactions = new ArrayList<>(); 71 | for (TransactionMessage tm : this.transactions) { 72 | transactions.add(tm.toTransactionWithoutSources(localStore)); 73 | } 74 | return new Block(this.number, localStore.getNode(this.ownerId), transactions); 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | StringBuilder sb = new StringBuilder(64); 80 | sb.append("BlockMessage> chainUpdates; 35 | 36 | /** 37 | * Constructor. 38 | * @param proof - original proof 39 | */ 40 | public ProofMessage(Proof proof) { 41 | this.transactionMessage = new TransactionMessage(proof.getTransaction()); 42 | this.chainUpdates = new HashMap<>(); 43 | for (Entry> entry : proof.getChainUpdates().entrySet()) { 44 | Node node = entry.getKey(); 45 | List blockList = entry.getValue(); 46 | if (!blockList.isEmpty()) { 47 | // Convert Block to BlockMessage 48 | List blockMessageList = new ArrayList<>(); 49 | for (int i = 0; i < blockList.size(); i++) { 50 | Block block = blockList.get(i); 51 | blockMessageList.add(new BlockMessage(block)); 52 | } 53 | this.chainUpdates.put(node.getId(), blockMessageList); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public void handle(LocalStore localStore) { 60 | try { 61 | CommunicationHelper.receiveTransaction(new Proof(this, localStore), localStore); 62 | } catch (IOException e) { 63 | Log.log(Level.SEVERE, "Exception while handling proof message", e); 64 | } 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | StringBuilder sb = new StringBuilder(64); 70 | sb.append("ProofMessage\n Transaction = ").append(transactionMessage).append("\n{"); 71 | if (chainUpdates.isEmpty()) { 72 | return sb.append("}").toString(); 73 | } 74 | 75 | for (Entry> entry : chainUpdates.entrySet()) { 76 | sb.append("\n ").append(entry.getKey()).append(": ["); 77 | for (BlockMessage bm : entry.getValue()) { 78 | sb.append("\n ").append(bm); 79 | } 80 | sb.append("\n ]"); 81 | } 82 | sb.append("\n}"); 83 | return sb.toString(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/StartTransactingMessage.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import java.util.logging.Level; 4 | 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 7 | 8 | /** 9 | * Message to indicate that the receiver can start transacting. 10 | */ 11 | public class StartTransactingMessage extends Message { 12 | private static final long serialVersionUID = 1L; 13 | 14 | @Override 15 | public void handle(LocalStore localStore) { 16 | try { 17 | localStore.getApplication().startTransacting(); 18 | } catch (Exception ex) { 19 | Log.log(Level.SEVERE, "Unable to start node " + localStore.getOwnNode().getId(), ex); 20 | } 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "StartTransactingMessage"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/StopTransactingMessage.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 4 | 5 | /** 6 | * Message to indicate that the receiver should stop transacting. 7 | */ 8 | public class StopTransactingMessage extends Message { 9 | private static final long serialVersionUID = 1L; 10 | 11 | @Override 12 | public void handle(LocalStore localStore) { 13 | localStore.getApplication().stopTransacting(); 14 | } 15 | 16 | @Override 17 | public String toString() { 18 | return "StopTransactingMessage"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/TransactionMessage.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import java.io.Serializable; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import java.util.TreeSet; 8 | 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Sha256Hash; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 13 | 14 | import lombok.Getter; 15 | 16 | /** 17 | * Transaction message for netty. 18 | */ 19 | public class TransactionMessage extends Message { 20 | private static final long serialVersionUID = 1L; 21 | 22 | @Getter 23 | private final int number; 24 | 25 | @Getter 26 | private final int senderId, receiverId; 27 | 28 | @Getter 29 | private final long amount, remainder; 30 | 31 | @Getter 32 | private final Set source; 33 | 34 | @Getter 35 | private final Sha256Hash hash; 36 | 37 | @Getter 38 | private final int blockNumber; 39 | 40 | /** 41 | * Constructor. 42 | * @param transaction - the original transaction object 43 | */ 44 | public TransactionMessage(Transaction transaction) { 45 | if (!transaction.getBlockNumber().isPresent()) { 46 | throw new RuntimeException("Block number not present"); 47 | } 48 | this.number = transaction.getNumber(); 49 | // It's a genesis transaction 50 | if (transaction.getSender() == null) { 51 | this.senderId = Transaction.GENESIS_SENDER; 52 | } else { 53 | this.senderId = transaction.getSender().getId(); 54 | } 55 | this.receiverId = transaction.getReceiver().getId(); 56 | this.amount = transaction.getAmount(); 57 | this.remainder = transaction.getRemainder(); 58 | this.source = new HashSet<>(); 59 | // Optimization: categorize each transaction already known (or not) by the receiver 60 | for (Transaction sourceTransaction : transaction.getSource()) { 61 | Node sourceSender = sourceTransaction.getSender(); 62 | if (sourceTransaction.getBlockNumber().isPresent()) { 63 | if (sourceSender == null) { 64 | // Genesis transaction 65 | this.source.add(new TransactionSource( 66 | sourceTransaction.getReceiver().getId(), 67 | sourceTransaction.getBlockNumber().getAsInt(), 68 | sourceTransaction.getNumber())); 69 | } else { 70 | this.source.add(new TransactionSource( 71 | sourceSender.getId(), 72 | sourceTransaction.getBlockNumber().getAsInt(), 73 | sourceTransaction.getNumber())); 74 | } 75 | } else { 76 | throw new IllegalStateException("Transaction without blocknumber found"); 77 | } 78 | } 79 | this.hash = transaction.getHash(); 80 | this.blockNumber = transaction.getBlockNumber().getAsInt(); 81 | } 82 | 83 | /** 84 | * Converts this message into a transaction without any sources. 85 | * @param localStore - the local store 86 | * @return - the transaction represented by this message, without sources 87 | */ 88 | public Transaction toTransactionWithoutSources(LocalStore localStore) { 89 | Transaction tx = new Transaction(this.number, localStore.getNode(this.senderId), 90 | localStore.getNode(this.receiverId), this.amount, this.remainder, new TreeSet<>()); 91 | tx.setMessage(this); 92 | return tx; 93 | } 94 | 95 | @Override 96 | public void handle(LocalStore localStore) { 97 | // Do nothing 98 | } 99 | 100 | @Override 101 | public boolean equals(Object obj) { 102 | if (this == obj) return true; 103 | if (!(obj instanceof TransactionMessage)) return false; 104 | 105 | TransactionMessage other = (TransactionMessage) obj; 106 | if (number != other.number) return false; 107 | if (senderId != other.senderId) return false; 108 | if (receiverId != other.receiverId) return false; 109 | if (amount != other.amount) return false; 110 | if (remainder != other.remainder) return false; 111 | if (blockNumber != other.blockNumber) return false; 112 | if (!source.equals(other.source)) return false; 113 | return hash.equals(other.hash); 114 | } 115 | 116 | @Override 117 | public int hashCode() { 118 | final int prime = 89; 119 | int result = 3; 120 | result = prime * result + this.number; 121 | result = prime * result + this.senderId; 122 | result = prime * result + this.receiverId; 123 | result = prime * result + (int) (this.amount ^ (this.amount >>> 32)); 124 | result = prime * result + (int) (this.remainder ^ (this.remainder >>> 32)); 125 | result = prime * result + Objects.hashCode(this.source); 126 | result = prime * result + Objects.hashCode(this.hash); 127 | result = prime * result + this.blockNumber; 128 | return result; 129 | } 130 | 131 | @Override 132 | public String toString() { 133 | StringBuilder sb = new StringBuilder(128); 134 | sb.append("TransactionMessage"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/UpdateNodesMessage.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import java.util.logging.Level; 4 | 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 7 | 8 | /** 9 | * Message to indicate that the receiver should update their node list. 10 | */ 11 | public class UpdateNodesMessage extends Message { 12 | private static final long serialVersionUID = 1L; 13 | 14 | @Override 15 | public void handle(LocalStore localStore) { 16 | try { 17 | localStore.updateNodes(); 18 | } catch (Exception ex) { 19 | Log.log(Level.SEVERE, "Unable to update node list of " + localStore.getOwnNode().getId(), ex); 20 | } 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "UpdateNodesMessage"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/mocks/TendermintChainMock.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.mocks; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.BlockAbstract; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Sha256Hash; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.MainChain; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 13 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 14 | 15 | /** 16 | * Mock for TendermintChain. 17 | */ 18 | public class TendermintChainMock implements MainChain { 19 | @Override 20 | public void init() {} 21 | 22 | @Override 23 | public Sha256Hash commitAbstract(BlockAbstract abs) { 24 | // Generate a deterministic hash, just in case 25 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 26 | try { 27 | outputStream.write(Utils.intToByteArray(abs.getBlockNumber())); 28 | outputStream.write(Utils.intToByteArray(abs.getOwnerNodeId())); 29 | } catch (IOException ex) { 30 | Logger.getLogger(TendermintChainMock.class.getName()).log(Level.SEVERE, null, ex); 31 | Log.log(Level.SEVERE, "Unexpected error while making hash of abstract"); 32 | } 33 | byte[] hash = outputStream.toByteArray(); 34 | 35 | // Update abstract 36 | abs.setAbstractHash(Sha256Hash.withHash(hash)); 37 | return Sha256Hash.withHash(hash); 38 | } 39 | 40 | @Override 41 | public boolean isPresent(Sha256Hash hash) { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean isPresent(Block block) { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean isInCache(Block block) { 52 | return true; 53 | } 54 | 55 | @Override 56 | public void stop() {} 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/BlockAbstract.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 7 | import org.apache.commons.lang3.SerializationException; 8 | import org.apache.commons.lang3.SerializationUtils; 9 | 10 | import java.io.IOException; 11 | import java.io.ObjectInputStream; 12 | import java.io.ObjectOutputStream; 13 | import java.io.Serializable; 14 | import java.io.ByteArrayOutputStream; 15 | import java.security.SignatureException; 16 | import java.util.Arrays; 17 | import java.util.Objects; 18 | import java.util.logging.Level; 19 | 20 | /** 21 | * BlockAbstract class. 22 | */ 23 | public class BlockAbstract implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | @Getter 28 | private int ownerNodeId; 29 | 30 | @Getter 31 | private int blockNumber; 32 | 33 | @Getter 34 | private Sha256Hash blockHash; 35 | 36 | @Getter 37 | private byte[] signature; 38 | 39 | @Setter @Getter 40 | private Sha256Hash abstractHash; 41 | 42 | /** 43 | * Constructor. 44 | * 45 | * @param ownerNodeId - the id of the owner of the block this abstract is for. 46 | * @param blockNumber - the number of the block this abstract is for. 47 | * @param blockHash - the hash of the block this abstract is for. 48 | * @param signature - the signature for the block by the owner. 49 | */ 50 | public BlockAbstract(int ownerNodeId, int blockNumber, Sha256Hash blockHash, byte[] signature) { 51 | this.ownerNodeId = ownerNodeId; 52 | this.blockNumber = blockNumber; 53 | this.blockHash = blockHash; 54 | this.signature = signature; 55 | } 56 | 57 | /** 58 | * Convert this abstract to a byte array. 59 | * Performs the inverse of {@link BlockAbstract#fromBytes(byte[])}. 60 | * 61 | * @return - the byte array conversion; or null if serialization fails. 62 | */ 63 | public byte[] toBytes() { 64 | byte[] ret; 65 | try { 66 | ret = SerializationUtils.serialize(this); 67 | } catch (SerializationException e) { 68 | Log.log(Level.WARNING, "Could not serialize the BlockAbstract to bytes", e); 69 | ret = null; 70 | } 71 | return ret; 72 | } 73 | 74 | /** 75 | * Construct a {@link BlockAbstract} from a byte array. 76 | * Performs the inverse of {@link BlockAbstract#toBytes()}. 77 | * 78 | * @param bytes - the data to construct from 79 | * @return - the abstract represented by the bytes; null if the deserialization fails. 80 | */ 81 | public static BlockAbstract fromBytes(byte[] bytes) { 82 | BlockAbstract block; 83 | try { 84 | block = SerializationUtils.deserialize(bytes); 85 | } catch (SerializationException | ClassCastException e) { 86 | Log.log(Level.WARNING, "Could not deserialize BlockAbstract from bytes", e); 87 | block = null; 88 | } 89 | return block; 90 | } 91 | 92 | /** 93 | * Checks if the given blocks corresponds with the blockHash in this abstract. 94 | * 95 | * @param block - the block to check 96 | * @return - boolean identifying if the blockhash was correct or not. 97 | */ 98 | public boolean checkBlockHash(Block block) { 99 | return this.blockHash.equals(block.getHash()); 100 | } 101 | 102 | /** 103 | * Checks if the signature included in this abstract is valid. 104 | * 105 | * @param signatureKey - the key that we want to check signature with (public key of owner) 106 | * @return - boolean identifying if the signature is valid. 107 | */ 108 | public boolean checkSignature(byte[] signatureKey) { 109 | try { 110 | byte[] attrInBytes = BlockAbstract.calculateBytesForSignature(this.ownerNodeId, this.blockNumber, this.blockHash); 111 | 112 | return Ed25519Key.verify(attrInBytes, this.signature, signatureKey); 113 | } catch (SignatureException e) { 114 | return false; 115 | } 116 | } 117 | 118 | /** 119 | * Convert attributes of abstract into an array of bytes, for the signature. 120 | * Important to keep the order of writings. 121 | * @param ownerId - id of the owner 122 | * @param blockNumber - number of the block 123 | * @param hash - hash of the block 124 | * @return array of bytes 125 | */ 126 | public static byte[] calculateBytesForSignature(int ownerId, int blockNumber, Sha256Hash hash) { 127 | byte[] attrInBytes; 128 | try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { 129 | outputStream.write(Utils.intToByteArray(ownerId)); 130 | outputStream.write(Utils.intToByteArray(blockNumber)); 131 | outputStream.write(hash.getBytes()); 132 | attrInBytes = outputStream.toByteArray(); 133 | } catch (IOException ex) { 134 | throw new IllegalStateException("Unable to write to outputstream", ex); 135 | } 136 | 137 | return attrInBytes; 138 | } 139 | 140 | private void writeObject(ObjectOutputStream stream) throws IOException { 141 | stream.defaultWriteObject(); 142 | } 143 | 144 | private void readObject(ObjectInputStream stream) throws IOException, 145 | ClassNotFoundException { 146 | stream.defaultReadObject(); 147 | } 148 | 149 | @Override 150 | public int hashCode() { 151 | int hash = 7; 152 | hash = 47 * hash + this.ownerNodeId; 153 | hash = 47 * hash + this.blockNumber; 154 | hash = 47 * hash + Objects.hashCode(this.blockHash); 155 | hash = 47 * hash + Arrays.hashCode(this.signature); 156 | hash = 47 * hash + Objects.hashCode(this.abstractHash); 157 | return hash; 158 | } 159 | 160 | @Override 161 | public boolean equals(Object obj) { 162 | if (obj == this) return true; 163 | if (!(obj instanceof BlockAbstract)) return false; 164 | 165 | BlockAbstract other = (BlockAbstract) obj; 166 | if (ownerNodeId != other.ownerNodeId) return false; 167 | if (blockNumber != other.blockNumber) return false; 168 | if (!blockHash.equals(other.blockHash)) return false; 169 | if (!Arrays.equals(signature, other.signature)) return false; 170 | if (this.abstractHash == null) { 171 | if (other.abstractHash != null) return false; 172 | } else if (other.abstractHash == null || this.abstractHash.equals(other.abstractHash)) return false; 173 | 174 | return true; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/Chain.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.AppendOnlyArrayList; 10 | 11 | /** 12 | * Chain class. 13 | */ 14 | public class Chain { 15 | 16 | @Getter 17 | private final Node owner; 18 | 19 | @Getter 20 | private final AppendOnlyArrayList blocks; 21 | 22 | @Getter 23 | private Transaction genesisTransaction; 24 | 25 | private Block lastCommittedBlock; 26 | 27 | /** 28 | * Constructor. 29 | * @param owner - the owner of this chain. 30 | */ 31 | public Chain(Node owner) { 32 | this.owner = owner; 33 | this.blocks = new AppendOnlyArrayList<>(); 34 | } 35 | 36 | /** 37 | * Updates this chain with the given updates. 38 | * This method is used for updating a chain belonging to a different node. 39 | * @param updates - the new blocks to append 40 | * @param localStore - the localStore 41 | * @throws UnsupportedOperationException - If this chain is owned by us. 42 | */ 43 | public void update(List updates, LocalStore localStore) { 44 | if (owner instanceof OwnNode) throw new UnsupportedOperationException("You cannot use update to update your own chain"); 45 | 46 | if (updates.isEmpty()) return; 47 | 48 | Block lastCommitted = updates.get(updates.size() - 1); 49 | synchronized (this) { 50 | //Figure out where to start updating 51 | int nextNr; 52 | Block previousBlock; 53 | if (blocks.isEmpty()) { 54 | //Should start at 0, there is no previous block 55 | previousBlock = null; 56 | nextNr = 0; 57 | } else { 58 | //Should start with the first block after our last block. 59 | previousBlock = blocks.get(blocks.size() - 1); 60 | nextNr = previousBlock.getNumber() + 1; 61 | } 62 | 63 | //Actually apply the updates 64 | ArrayList toAdd = new ArrayList<>(); 65 | int lastBlockNr = nextNr - 1; 66 | for (Block block : updates) { 67 | //Skip any overlap 68 | if (block.getNumber() != nextNr) continue; 69 | block.setPreviousBlock(previousBlock); 70 | lastBlockNr = fixNextCommitted(block, lastCommitted.getNumber(), lastBlockNr, localStore); 71 | toAdd.add(block); 72 | nextNr++; 73 | previousBlock = block; 74 | } 75 | 76 | blocks.addAll(toAdd); 77 | } 78 | 79 | //The last block in the updates must be a committed block 80 | setLastCommittedBlock(lastCommitted); 81 | } 82 | 83 | /** 84 | * @param block - the block 85 | * @param lastUpdateBlockNr - the number of the last block in the list of updates 86 | * @param lastBlockNr - the number of the last checked block that was actually committed 87 | * @param localStore - the local store 88 | * @return - the new lastBlockNr 89 | */ 90 | private int fixNextCommitted(Block block, int lastUpdateBlockNr, int lastBlockNr, LocalStore localStore) { 91 | if (block.getNumber() != lastUpdateBlockNr && !localStore.getMainChain().isInCache(block)) return lastBlockNr; 92 | 93 | Block prev = block; 94 | while (prev != null && prev.getNumber() > lastBlockNr) { 95 | prev.setNextCommittedBlock(block); 96 | prev = prev.getPreviousBlock(); 97 | } 98 | 99 | return block.getNumber(); 100 | } 101 | 102 | /** 103 | * @return - the genesis block 104 | */ 105 | public Block getGenesisBlock() { 106 | if (blocks.isEmpty()) return null; 107 | 108 | return blocks.get(0); 109 | } 110 | 111 | /** 112 | * Sets the genesis block. 113 | * @param genesisBlock - the genesis block 114 | * @throws IllegalStateException - If this chain already has a genesis block. 115 | */ 116 | public synchronized void setGenesisBlock(Block genesisBlock) { 117 | if (!blocks.isEmpty()) throw new IllegalStateException("Adding genesis block to non-empty chain"); 118 | 119 | blocks.add(genesisBlock); 120 | setLastCommittedBlock(genesisBlock); 121 | genesisTransaction = findGenesisTransaction(owner, genesisBlock); 122 | if (genesisTransaction != null) { 123 | genesisTransaction.setReceiver(owner); 124 | } 125 | } 126 | 127 | /** 128 | * @return the last block in this chain 129 | */ 130 | public Block getLastBlock() { 131 | if (blocks.isEmpty()) return null; 132 | 133 | return blocks.get(blocks.size() - 1); 134 | } 135 | 136 | /** 137 | * @return - the number of the last block 138 | */ 139 | public int getLastBlockNumber() { 140 | return blocks.size() - 1; 141 | } 142 | 143 | /** 144 | * @return - the last block that was committed to the main chain 145 | */ 146 | public Block getLastCommittedBlock() { 147 | return lastCommittedBlock; 148 | } 149 | 150 | /** 151 | * Sets the last committed block to the given block. 152 | * If the given block is before the current last committed block then this method has no effect. 153 | * @param block - the block 154 | */ 155 | public synchronized void setLastCommittedBlock(Block block) { 156 | if (lastCommittedBlock == null) { 157 | lastCommittedBlock = block; 158 | } else if (block.getNumber() > lastCommittedBlock.getNumber()) { 159 | lastCommittedBlock = block; 160 | } 161 | } 162 | 163 | /** 164 | * Creates a new block and appends it to this chain. 165 | * @return - the newly appended block 166 | * @throws UnsupportedOperationException - If this chain is not owned by us. 167 | * @throws IllegalStateException - If there is no genesis block in this chain. 168 | */ 169 | public synchronized Block appendNewBlock() { 170 | if (!(owner instanceof OwnNode)) throw new UnsupportedOperationException("You cannot append blocks to a chain that is not yours!"); 171 | 172 | Block last = getLastBlock(); 173 | if (last == null) throw new IllegalStateException("There is no genesis block!"); 174 | 175 | Block newBlock = new Block(last, this.owner); 176 | blocks.add(newBlock); 177 | return newBlock; 178 | } 179 | 180 | /** 181 | * Finds the genesis transaction of the given node. 182 | * @param node - the node 183 | * @param genesisBlock - the genesis block 184 | * @return - the genesis transaction of the given node, or null if there is none 185 | */ 186 | private static Transaction findGenesisTransaction(Node node, Block genesisBlock) { 187 | return genesisBlock.getTransactions() 188 | .stream() 189 | .filter(t -> t.getReceiver().getId() == node.getId()) 190 | .findFirst() 191 | .orElse(null); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/Ed25519Key.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.KeyPair; 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.security.PrivateKey; 8 | import java.security.PublicKey; 9 | import java.security.Signature; 10 | import java.security.SignatureException; 11 | import java.util.Arrays; 12 | import lombok.Getter; 13 | import net.i2p.crypto.eddsa.EdDSAEngine; 14 | import net.i2p.crypto.eddsa.EdDSAPrivateKey; 15 | import net.i2p.crypto.eddsa.EdDSAPublicKey; 16 | import net.i2p.crypto.eddsa.KeyPairGenerator; 17 | import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; 18 | import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; 19 | import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; 20 | import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; 21 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 22 | 23 | /** 24 | * Class to wrap a ED25519 key pair + Utils to handle ED25519 keys. 25 | */ 26 | public class Ed25519Key { 27 | 28 | private static EdDSAParameterSpec specification = EdDSANamedCurveTable.getByName("Ed25519"); 29 | 30 | @Getter 31 | private final byte[] privateKey; 32 | 33 | @Getter 34 | private final byte[] publicKey; 35 | 36 | /** 37 | * Constructor. 38 | */ 39 | public Ed25519Key() { 40 | KeyPair keyPair = generateKeys(); 41 | this.privateKey = new byte[64]; 42 | byte[] seedByte = ((EdDSAPrivateKey) keyPair.getPrivate()).getSeed(); 43 | byte[] aByte = ((EdDSAPrivateKey) keyPair.getPrivate()).getAbyte(); 44 | System.arraycopy(seedByte, 0, this.privateKey, 0, seedByte.length); 45 | System.arraycopy(aByte, 0, this.privateKey, aByte.length, aByte.length); 46 | this.publicKey = aByte; 47 | } 48 | 49 | /** 50 | * Constructor. 51 | * @param privateKey - ED25519 private key 52 | * @param publicKey - ED25519 public key 53 | */ 54 | public Ed25519Key(byte[] privateKey, byte[] publicKey) { 55 | this.privateKey = privateKey; 56 | this.publicKey = publicKey; 57 | } 58 | 59 | /** 60 | * Generate a random ED25519 key pair. 61 | * @return key pair of ED25519 keys 62 | */ 63 | public static KeyPair generateKeys() { 64 | KeyPairGenerator generator = new KeyPairGenerator(); 65 | return generator.generateKeyPair(); 66 | } 67 | 68 | /** 69 | * Verify an array of bytes with signature and public key. 70 | * @param message - array of bytes of the message 71 | * @param signature - signature of the message 72 | * @param publicKey - public ED25519 key 73 | * @return whether is correct or not 74 | * @throws SignatureException - exception while verifying 75 | */ 76 | public static boolean verify(byte[] message, byte[] signature, byte[] publicKey) throws SignatureException { 77 | try { 78 | EdDSAPublicKeySpec publicKeySpec = new EdDSAPublicKeySpec(publicKey, specification); 79 | PublicKey publicKeyObject = new EdDSAPublicKey(publicKeySpec); 80 | Signature publicSignature = new EdDSAEngine(MessageDigest.getInstance(specification.getHashAlgorithm())); 81 | publicSignature.initVerify(publicKeyObject); 82 | publicSignature.update(message); 83 | return publicSignature.verify(signature); 84 | } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException ex) { 85 | throw new SignatureException(ex); 86 | } 87 | } 88 | 89 | /** 90 | * Verify an array of bytes with signature and public key. 91 | * @param message - array of bytes of the message 92 | * @param signature - signature of the message 93 | * @return whether is correct or not 94 | * @throws SignatureException - exception while verifying 95 | */ 96 | public boolean verify(byte[] message, byte[] signature) throws SignatureException { 97 | return verify(message, signature, this.publicKey); 98 | } 99 | 100 | /** 101 | * Sign an array of bytes with a private key. 102 | * @param message - array of bytes of the message 103 | * @param privateKey - private ED25519 key 104 | * @return signature of the message 105 | * @throws java.security.SignatureException - invalid signature 106 | * @throws java.security.InvalidKeyException - invalid key 107 | * @throws java.security.NoSuchAlgorithmException - no algorithm found for Ed25519 108 | */ 109 | public static byte[] sign(byte[] message, byte[] privateKey) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException { 110 | // Get seed 111 | byte[] seed = Arrays.copyOf(privateKey, 32); 112 | // Sign 113 | EdDSAPrivateKeySpec privateKeySpec = new EdDSAPrivateKeySpec(seed, specification); 114 | PrivateKey privateKeyObject = new EdDSAPrivateKey(privateKeySpec); 115 | Signature privateSignature = new EdDSAEngine(MessageDigest.getInstance(specification.getHashAlgorithm())); 116 | privateSignature.initSign(privateKeyObject); 117 | privateSignature.update(message); 118 | return privateSignature.sign(); 119 | } 120 | 121 | /** 122 | * Sign an array of bytes with a private key. 123 | * @param message - array of bytes of the message 124 | * @return signature of the message 125 | * @throws java.security.SignatureException - invalid signature 126 | * @throws java.security.InvalidKeyException - invalid key 127 | * @throws java.security.NoSuchAlgorithmException - no algorithm found for Ed25519 128 | */ 129 | public byte[] sign(byte[] message) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException { 130 | return sign(message, this.privateKey); 131 | } 132 | 133 | @Override 134 | public boolean equals(Object other) { 135 | if (other == this) return true; 136 | if (!(other instanceof Ed25519Key)) return false; 137 | return Arrays.equals(this.publicKey, ((Ed25519Key) other).publicKey) 138 | && Arrays.equals(this.privateKey, ((Ed25519Key) other).privateKey); 139 | } 140 | 141 | @Override 142 | public int hashCode() { 143 | int hash = 7; 144 | hash = 37 * hash + Arrays.hashCode(this.privateKey); 145 | hash = 37 * hash + Arrays.hashCode(this.publicKey); 146 | return hash; 147 | } 148 | 149 | @Override 150 | public String toString() { 151 | StringBuilder stringBuffer = new StringBuilder(32 + this.publicKey.length * 2 + this.privateKey.length * 2); 152 | stringBuffer.append("Public Key: \n") 153 | .append(Utils.bytesToHexString(this.publicKey)) 154 | .append("\nPrivate Key: \n") 155 | .append(Utils.bytesToHexString(this.privateKey)); 156 | return stringBuffer.toString(); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/LightView.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Class which provides an easy way of getting blocks. 8 | */ 9 | public class LightView { 10 | private Chain chain; 11 | private List updates; 12 | 13 | /** 14 | * @param chain - the chain 15 | * @param updates - the blocks of this chain that were sent with the proof 16 | */ 17 | public LightView(Chain chain, List updates) { 18 | this.chain = chain; 19 | this.updates = updates; 20 | if (this.updates == null) this.updates = new ArrayList<>(); 21 | } 22 | 23 | /** 24 | * @param number - the number 25 | * @return - the block with the given number 26 | * @throws IndexOutOfBoundsException - If the block with the given number does not exist (yet). 27 | */ 28 | public Block getBlock(int number) { 29 | if (number < chain.getBlocks().size()) { 30 | return chain.getBlocks().get(number); 31 | } else { 32 | int index = number - updates.get(0).getNumber(); 33 | return updates.get(index); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/MetaKnowledge.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | 7 | import lombok.Getter; 8 | 9 | /** 10 | * A class for representing meta knowledge (what we know that they know). 11 | */ 12 | public class MetaKnowledge extends HashMap { 13 | private static final long serialVersionUID = 1L; 14 | 15 | /** 16 | * @return - the node that this meta knowledge belongs to 17 | */ 18 | @Getter 19 | private final Node owner; 20 | 21 | /** 22 | * @param owner - the owner of this meta knowledge 23 | */ 24 | public MetaKnowledge(Node owner) { 25 | this.owner = owner; 26 | } 27 | 28 | /** 29 | * Determines the blocks that we have to send to owner in order to update them to the given 30 | * end block number. 31 | * 32 | * This method returns {@link Collections#emptyList()} if the given node is the owner or if 33 | * owner already knows about all the blocks up till the given block number. 34 | * @param node - the node to send blocks of 35 | * @param endBlockNr - the number of the last block we want to send 36 | * @return - the list of blocks to send 37 | * @throws IndexOutOfBoundsException - If the given node does not have a block with endBlockNr. 38 | */ 39 | public List getBlocksToSend(Node node, int endBlockNr) { 40 | if (node == owner) return Collections.emptyList(); 41 | 42 | //Determine the first block they don't know 43 | int firstUnknown = getFirstUnknownBlockNumber(node); 44 | 45 | //They already know everything we want to send 46 | if (firstUnknown > endBlockNr) return Collections.emptyList(); 47 | 48 | //Get a sublist of the blocks on the chain. We need + 1 on endBlock because the end is exclusive. 49 | return node.getChain().getBlocks().subList(firstUnknown, endBlockNr + 1); 50 | } 51 | 52 | /** 53 | * The number returned by this method is the number of the last block that {@link #getOwner()} 54 | * knows from node {@code node}. 55 | * 56 | * This method returns -1 if the owner of this meta knowledge does not know about this block. 57 | * @param node - the node 58 | * @return - the number of the last block from the given node that is known by owner 59 | */ 60 | public int getLastKnownBlockNumber(Node node) { 61 | return getLastKnownBlockNumber(node.getId()); 62 | } 63 | 64 | /** 65 | * The number returned by this method is the number of the last block that {@link #getOwner()} 66 | * knows from the node with id {@code nodeId}. 67 | * 68 | * This method returns -1 if the owner of this meta knowledge does not know about this block. 69 | * @param nodeId - the id of the node 70 | * @return - the number of the last block from the given node that is known by owner 71 | */ 72 | public int getLastKnownBlockNumber(int nodeId) { 73 | if (nodeId == owner.getId()) return owner.getChain().getLastBlockNumber(); 74 | return getOrDefault(nodeId, -1); 75 | } 76 | 77 | /** 78 | * @param node - the node 79 | * @return - the number of the first block from the given node that is unknown by owner 80 | */ 81 | public int getFirstUnknownBlockNumber(Node node) { 82 | return getFirstUnknownBlockNumber(node.getId()); 83 | } 84 | 85 | /** 86 | * @param nodeId - the id of the node 87 | * @return - the number of the first block from the given node that is unknown by owner 88 | */ 89 | public int getFirstUnknownBlockNumber(int nodeId) { 90 | return getLastKnownBlockNumber(nodeId) + 1; 91 | } 92 | 93 | /** 94 | * Updates the last known block number of the given node to the given blockNumber. 95 | * If the given block number is lower than the current last known block number, then this 96 | * method does nothing. 97 | * @param node - the node 98 | * @param blockNumber - the block number 99 | */ 100 | public void updateLastKnownBlockNumber(Node node, int blockNumber) { 101 | updateLastKnownBlockNumber(node.getId(), blockNumber); 102 | } 103 | 104 | /** 105 | * Updates the last known block number of the given node to the given blockNumber. 106 | * If the given block number is lower than the current last known block number, then this 107 | * method does nothing. 108 | * @param nodeId - the id of the node 109 | * @param blockNumber - the block number 110 | */ 111 | public synchronized void updateLastKnownBlockNumber(int nodeId, int blockNumber) { 112 | merge(nodeId, blockNumber, (oldNr, newNr) -> Math.max(oldNr, newNr)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/Node.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.security.SignatureException; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Map.Entry; 10 | 11 | /** 12 | * Node class. 13 | */ 14 | public class Node { 15 | 16 | @Getter 17 | private final int id; 18 | 19 | @Getter 20 | private final Chain chain; 21 | 22 | @Getter @Setter 23 | private byte[] publicKey; 24 | 25 | @Getter @Setter 26 | private String address; 27 | 28 | @Getter @Setter 29 | private int port; 30 | 31 | @Getter 32 | private MetaKnowledge metaKnowledge = new MetaKnowledge(this); 33 | 34 | /** 35 | * Constructor. 36 | * @param id - the id of this node. 37 | */ 38 | public Node(int id) { 39 | this.id = id; 40 | this.chain = new Chain(this); 41 | } 42 | 43 | /** 44 | * Constructor. 45 | * @param id - the id of this node. 46 | * @param publicKey - the public key of this node. 47 | * @param address - the address of this node. 48 | * @param port = the port of this node. 49 | */ 50 | public Node(int id, byte[] publicKey, String address, int port) { 51 | this.id = id; 52 | this.publicKey = publicKey; 53 | this.address = address; 54 | this.port = port; 55 | this.chain = new Chain(this); 56 | } 57 | 58 | /** 59 | * Verify the signature of a message made by this node. 60 | * @param message - message to be verified 61 | * @param signature - signature of the message 62 | * @return - the signature 63 | * @throws SignatureException - See {@link Ed25519Key#verify(byte[], byte[], byte[])} 64 | */ 65 | public boolean verify(byte[] message, byte[] signature) throws SignatureException { 66 | return Ed25519Key.verify(message, signature, this.publicKey); 67 | } 68 | 69 | /** 70 | * Updates the knowledge that we have about what this node knows with the information in the 71 | * given proof. 72 | * @param proof - the proof to update with 73 | */ 74 | public void updateMetaKnowledge(Proof proof) { 75 | Map> updates = proof.getChainUpdates(); 76 | for (Entry> entry : updates.entrySet()) { 77 | //Don't include self 78 | if (entry.getKey() == this) continue; 79 | 80 | int lastBlockNr = getLastBlockNumber(entry.getValue()); 81 | if (lastBlockNr == -1) continue; 82 | metaKnowledge.updateLastKnownBlockNumber(entry.getKey(), lastBlockNr); 83 | } 84 | } 85 | 86 | /** 87 | * @param blocks - the list of blocks 88 | * @return the number of the last block in the given list 89 | */ 90 | private static int getLastBlockNumber(List blocks) { 91 | if (blocks.isEmpty()) return -1; 92 | 93 | Block lastBlock = blocks.get(blocks.size() - 1); 94 | return lastBlock.getNumber(); 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | return this.id; 100 | } 101 | 102 | @Override 103 | public boolean equals(Object obj) { 104 | if (obj == this) return true; 105 | if (!(obj instanceof Node)) return false; 106 | 107 | Node other = (Node) obj; 108 | return this.getId() == other.getId(); 109 | } 110 | 111 | @Override 112 | public String toString() { 113 | return this.id + " at " + this.address + ":" + this.port; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/OwnNode.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * Class to represent our own node. 8 | */ 9 | public class OwnNode extends Node { 10 | 11 | /** 12 | * Only used by the node himself. 13 | * @return private key 14 | */ 15 | @Getter @Setter 16 | private transient byte[] privateKey; 17 | 18 | /** 19 | * @param id - the id of our node 20 | */ 21 | public OwnNode(int id) { 22 | super(id); 23 | } 24 | 25 | /** 26 | * @param id - the id of our node 27 | * @param publicKey - the public key 28 | * @param address - the address 29 | * @param port - the port 30 | */ 31 | public OwnNode(int id, byte[] publicKey, String address, int port) { 32 | super(id, publicKey, address, port); 33 | } 34 | 35 | /** 36 | * @param message - the message to sign 37 | * @return - the signed message 38 | * @throws Exception - See {@link Ed25519Key#sign(byte[], byte[])} 39 | */ 40 | public byte[] sign(byte[] message) throws Exception { 41 | return Ed25519Key.sign(message, this.privateKey); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "(OwnNode) " + super.toString(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/Sha256Hash.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 4 | 5 | import java.io.Serializable; 6 | import java.nio.charset.StandardCharsets; 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.util.Arrays; 10 | import java.util.logging.Level; 11 | import lombok.Getter; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 13 | 14 | /** 15 | * Class to wrap a SHA256 hash. 16 | */ 17 | public class Sha256Hash implements Serializable { 18 | private static final long serialVersionUID = 1L; 19 | 20 | @Getter 21 | private byte[] bytes; 22 | 23 | /** 24 | * Constructor. 25 | * @param bytesAux - the array of bytes to be hashed 26 | */ 27 | public Sha256Hash(byte[] bytesAux) { 28 | try { 29 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 30 | this.bytes = digest.digest(bytesAux); 31 | } catch (NoSuchAlgorithmException ex) { 32 | Log.log(Level.SEVERE, null, ex); 33 | } 34 | } 35 | 36 | /** 37 | * Constructor. 38 | * @param message - the string to be hashed 39 | */ 40 | public Sha256Hash(String message) { 41 | byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8); 42 | try { 43 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 44 | this.bytes = digest.digest(messageBytes); 45 | } catch (NoSuchAlgorithmException ex) { 46 | Log.log(Level.SEVERE, null, ex); 47 | } 48 | } 49 | 50 | /** 51 | * Get a {@link Sha256Hash} with the given hash. 52 | * 53 | * @param hash - the hash of the resulting object 54 | * @return - an object with the given hash 55 | */ 56 | public static Sha256Hash withHash(byte[] hash) { 57 | Sha256Hash res = new Sha256Hash(new byte[0]); 58 | res.bytes = hash; 59 | return res; 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return Arrays.hashCode(this.bytes); 65 | } 66 | 67 | @Override 68 | public boolean equals(Object other) { 69 | if (other == this) return true; 70 | if (!(other instanceof Sha256Hash)) return false; 71 | 72 | return Arrays.equals(this.bytes, ((Sha256Hash) other).bytes); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return Utils.bytesToHexString(this.bytes); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/TransactionRegistration.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * Class to store all information needed to register a transaction. 8 | */ 9 | public class TransactionRegistration { 10 | 11 | @Getter @Setter 12 | private Transaction transaction; 13 | 14 | @Getter @Setter 15 | private int numberOfChains; 16 | 17 | @Getter @Setter 18 | private int numberOfBlocks; 19 | 20 | /** 21 | * Constructor. 22 | * @param transaction - the traansactions of this registration. 23 | * @param numberOfChains - the number of chains used in the proof. 24 | * @param numberOfBlocks - the number of blocks used in the proof. 25 | */ 26 | public TransactionRegistration(Transaction transaction, int numberOfChains, int numberOfBlocks) { 27 | this.transaction = transaction; 28 | this.numberOfChains = numberOfChains; 29 | this.numberOfBlocks = numberOfBlocks; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/mainchain/MainChain.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 4 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.BlockAbstract; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Sha256Hash; 6 | 7 | /** 8 | * Interface for main chain inplementations. 9 | */ 10 | public interface MainChain { 11 | /** 12 | * Commit an abstract to the main chain. 13 | * 14 | * @param abs - the abstract to commit 15 | * @return - the hash given to the transaction on commit 16 | */ 17 | public Sha256Hash commitAbstract(BlockAbstract abs); 18 | 19 | /** 20 | * Check whether the given hash is on the main chain (in a form of BlockAbstract). 21 | * @param hash the hash of the block to check 22 | * @return true if there is a block abstract of the given hash, false otherwise. 23 | */ 24 | public boolean isPresent(Sha256Hash hash); 25 | 26 | /** 27 | * Check whether the block (from a local chain) is on the main chain (in a form of BlockAbstract). 28 | * @param block the block to check 29 | * @return true if there is a block abstract of the given block, false otherwise. 30 | */ 31 | public boolean isPresent(Block block); 32 | 33 | /** 34 | * @param block - the block to check 35 | * @return - true if the given block is in the cache, false otherwise 36 | */ 37 | public boolean isInCache(Block block); 38 | 39 | /** 40 | * Initializes the tendermint chain. 41 | */ 42 | public void init(); 43 | 44 | /** 45 | * Stop the main chain. 46 | */ 47 | public void stop(); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/mainchain/tendermint/ABCIServer.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.tendermint; 2 | 3 | import com.github.jtendermint.jabci.api.ABCIAPI; 4 | import com.github.jtendermint.jabci.types.Types.CodeType; 5 | import com.github.jtendermint.jabci.types.Types.RequestBeginBlock; 6 | import com.github.jtendermint.jabci.types.Types.RequestCheckTx; 7 | import com.github.jtendermint.jabci.types.Types.RequestCommit; 8 | import com.github.jtendermint.jabci.types.Types.RequestDeliverTx; 9 | import com.github.jtendermint.jabci.types.Types.RequestEcho; 10 | import com.github.jtendermint.jabci.types.Types.RequestEndBlock; 11 | import com.github.jtendermint.jabci.types.Types.RequestFlush; 12 | import com.github.jtendermint.jabci.types.Types.RequestInfo; 13 | import com.github.jtendermint.jabci.types.Types.RequestInitChain; 14 | import com.github.jtendermint.jabci.types.Types.RequestQuery; 15 | import com.github.jtendermint.jabci.types.Types.RequestSetOption; 16 | import com.github.jtendermint.jabci.types.Types.ResponseBeginBlock; 17 | import com.github.jtendermint.jabci.types.Types.ResponseCheckTx; 18 | import com.github.jtendermint.jabci.types.Types.ResponseCommit; 19 | import com.github.jtendermint.jabci.types.Types.ResponseDeliverTx; 20 | import com.github.jtendermint.jabci.types.Types.ResponseEcho; 21 | import com.github.jtendermint.jabci.types.Types.ResponseEndBlock; 22 | import com.github.jtendermint.jabci.types.Types.ResponseFlush; 23 | import com.github.jtendermint.jabci.types.Types.ResponseInfo; 24 | import com.github.jtendermint.jabci.types.Types.ResponseInitChain; 25 | import com.github.jtendermint.jabci.types.Types.ResponseQuery; 26 | import com.github.jtendermint.jabci.types.Types.ResponseSetOption; 27 | import com.google.protobuf.ByteString; 28 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 29 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.BlockAbstract; 30 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 31 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 32 | 33 | import java.util.logging.Level; 34 | 35 | /** 36 | * An implementation of a Tendermint ABCI server. 37 | * The server implements a number of callbacks for events received from Tendermint. 38 | * @see Tendermint ABCI message type documentation 39 | */ 40 | public class ABCIServer implements ABCIAPI { 41 | private final TendermintChain chain; 42 | private final Block genesisBlock; 43 | 44 | /** 45 | * @param chain - the main chain this server is part of 46 | * @param genesisBlock - the genesis (initial) block for the entire system 47 | */ 48 | public ABCIServer(TendermintChain chain, Block genesisBlock) { 49 | this.chain = chain; 50 | this.genesisBlock = genesisBlock; 51 | if (genesisBlock != null) { 52 | chain.addToCache(genesisBlock.getHash()); 53 | } 54 | } 55 | 56 | @Override 57 | public ResponseBeginBlock requestBeginBlock(RequestBeginBlock requestBeginBlock) { 58 | Log.log(Level.FINER, "[TENDERMINT] New block started"); 59 | return ResponseBeginBlock.newBuilder().build(); 60 | } 61 | 62 | @Override 63 | public ResponseCheckTx requestCheckTx(RequestCheckTx requestCheckTx) { 64 | Log.log(Level.FINER, "[TENDERMINT] New transaction proposed"); 65 | 66 | // Comment the next line when using a mock chain 67 | BlockAbstract abs = BlockAbstract.fromBytes(requestCheckTx.getTx().toByteArray()); 68 | byte[] publicKey = chain.getApp().getLocalStore().getNode(abs.getOwnerNodeId()).getPublicKey(); 69 | boolean valid = abs.checkSignature(publicKey); 70 | if (valid) { 71 | return ResponseCheckTx.newBuilder().setCode(CodeType.OK).build(); 72 | } else { 73 | String log = "signature on the abstract was invalid. Public key used:" + Utils.bytesToHexString(publicKey); 74 | Log.log(Level.INFO, "[TENDERMINT] Proposed block rejected because " + log); 75 | return ResponseCheckTx.newBuilder().setCode(CodeType.BadNonce).setLog(log).build(); 76 | } 77 | } 78 | 79 | @Override 80 | public ResponseCommit requestCommit(RequestCommit requestCommit) { 81 | Log.log(Level.FINER, "[TENDERMINT] Finalize commit request"); 82 | ResponseCommit.Builder responseCommit = ResponseCommit.newBuilder(); 83 | return responseCommit.build(); 84 | } 85 | 86 | @Override 87 | public ResponseDeliverTx receivedDeliverTx(RequestDeliverTx requestDeliverTx) { 88 | Log.log(Level.FINER, "[TENDERMINT] requestDeliverTx " + requestDeliverTx.toString()); 89 | return ResponseDeliverTx.newBuilder().setCode(CodeType.OK).build(); 90 | } 91 | 92 | @Override 93 | public ResponseEcho requestEcho(RequestEcho requestEcho) { 94 | Log.log(Level.FINER, "[TENDERMINT] Echo " + requestEcho.getMessage()); 95 | return ResponseEcho.newBuilder().setMessage(requestEcho.getMessage()).build(); 96 | } 97 | 98 | @Override 99 | public ResponseEndBlock requestEndBlock(RequestEndBlock requestEndBlock) { 100 | Log.log(Level.FINER, "[TENDERMINT] requestEndBlock " + requestEndBlock.toString()); 101 | long height = requestEndBlock.getHeight(); 102 | if (height > 0) { 103 | Log.log(Level.FINE, "[TENDERMINT] Block #" + height + " ended, going to update the cache"); 104 | chain.updateCache(height); 105 | } 106 | return ResponseEndBlock.newBuilder().build(); 107 | } 108 | 109 | @Override 110 | public ResponseFlush requestFlush(RequestFlush requestFlush) { 111 | Log.log(Level.FINER, "[TENDERMINT] requestFlush " + requestFlush.toString()); 112 | return ResponseFlush.newBuilder().build(); 113 | } 114 | 115 | @Override 116 | public ResponseInfo requestInfo(RequestInfo requestInfo) { 117 | Log.log(Level.FINER, "[TENDERMINT] requestInfo " + requestInfo.toString()); 118 | ResponseInfo.Builder responseInfo = ResponseInfo.newBuilder(); 119 | byte[] initialAppHash = genesisBlock.getHash().getBytes(); 120 | //TODO: this should return the actual last block hash - now it's only used for genesis block 121 | responseInfo.setLastBlockAppHash(ByteString.copyFrom(initialAppHash)); 122 | responseInfo.setLastBlockHeight(chain.getCurrentHeight()); 123 | Log.log(Level.FINER, "[TENDERMINT] responseInfo " + responseInfo.toString()); 124 | return responseInfo.build(); 125 | } 126 | 127 | @Override 128 | public ResponseInitChain requestInitChain(RequestInitChain requestInitChain) { 129 | Log.log(Level.FINER, "[TENDERMINT] requestInitChain " + requestInitChain.toString()); 130 | return ResponseInitChain.newBuilder().build(); 131 | } 132 | 133 | @Override 134 | public ResponseQuery requestQuery(RequestQuery requestQuery) { 135 | Log.log(Level.FINER, "[TENDERMINT] Chain queried"); 136 | return ResponseQuery.newBuilder().setCode(CodeType.OK).build(); 137 | } 138 | 139 | @Override 140 | public ResponseSetOption requestSetOption(RequestSetOption requestSetOption) { 141 | Log.log(Level.FINER, "[TENDERMINT] requestSetOption " + requestSetOption.toString()); 142 | return ResponseSetOption.newBuilder().build(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/CancellableInfiniteRunnable.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.ToLongFunction; 5 | import java.util.logging.Level; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 8 | 9 | /** 10 | * Runnable implementation which will run repeatedly until it is cancelled. 11 | * @param - the type of elements to accept 12 | */ 13 | public class CancellableInfiniteRunnable implements Runnable { 14 | private Thread runner; 15 | private boolean cancelled; 16 | private final T t; 17 | private final InterruptibleConsumer action; 18 | private final ToLongFunction sleepFunction; 19 | private final Consumer onStop; 20 | 21 | /** 22 | * @param t - the parameter to pass 23 | * @param action - the action that is repeated 24 | * @param sleepFunction - a function to determine the time to sleep 25 | * @param onStop - the consumer to execute when stopping 26 | */ 27 | public CancellableInfiniteRunnable(T t, InterruptibleConsumer action, ToLongFunction sleepFunction, Consumer onStop) { 28 | this.t = t; 29 | this.action = action; 30 | this.sleepFunction = sleepFunction; 31 | this.onStop = onStop; 32 | } 33 | 34 | /** 35 | * Cancels this runnable. 36 | */ 37 | public void cancel() { 38 | if (cancelled) return; 39 | cancelled = true; 40 | } 41 | 42 | /** 43 | * @return if this runnable has been cancelled 44 | */ 45 | public boolean isCancelled() { 46 | return cancelled; 47 | } 48 | 49 | /** 50 | * Stops this runnable as quickly as possible by interrupting the executing thread. 51 | */ 52 | public void stopNow() { 53 | cancel(); 54 | synchronized (this) { 55 | if (runner != null) { 56 | runner.interrupt(); 57 | } 58 | } 59 | } 60 | 61 | @Override 62 | public void run() { 63 | synchronized (this) { 64 | runner = Thread.currentThread(); 65 | } 66 | 67 | try { 68 | while (!isCancelled()) { 69 | //Do action 70 | try { 71 | action.accept(t); 72 | } catch (InterruptedException ex) { 73 | continue; 74 | } catch (Exception ex) { 75 | Log.log(Level.SEVERE, "Uncaught exception in action", ex); 76 | } 77 | 78 | //Sleep 79 | try { 80 | Thread.sleep(sleepFunction.applyAsLong(t)); 81 | } catch (InterruptedException ex) { 82 | continue; 83 | } catch (Exception ex) { 84 | Log.log(Level.SEVERE, "Uncaught exception in sleep function", ex); 85 | } 86 | } 87 | } finally { 88 | if (onStop != null) { 89 | try { 90 | onStop.accept(t); 91 | } catch (Exception ex) { 92 | Log.log(Level.SEVERE, "Uncaught exception in onStop", ex); 93 | } 94 | } 95 | 96 | synchronized (this) { 97 | runner = null; 98 | } 99 | } 100 | } 101 | 102 | /** 103 | * Interruptible consumer interface. 104 | * @param - the type to accept 105 | */ 106 | @FunctionalInterface 107 | public interface InterruptibleConsumer { 108 | /** 109 | * Performs this operation on the given argument. 110 | * @param t - the input argument 111 | * @throws InterruptedException - when the method is interrupted 112 | */ 113 | public void accept(T t) throws InterruptedException; 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/PoissonRandom.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Extension of Random which adds a method to get a poisson number. 7 | */ 8 | public class PoissonRandom extends Random { 9 | private static final long serialVersionUID = -8301097576667444995L; 10 | 11 | private static final double STEP = 500; 12 | 13 | /** 14 | * Creates a new PoissonRandom. 15 | */ 16 | public PoissonRandom() {} 17 | 18 | /** 19 | * Creates a new PoissonRandom with the given seed. 20 | * @param seed - the seed 21 | */ 22 | public PoissonRandom(long seed) { 23 | super(seed); 24 | } 25 | 26 | /** 27 | * @param lambda - the poisson parameter lambda 28 | * @return a number generated according to a poisson distribution 29 | */ 30 | public int nextPoisson(double lambda) { 31 | //Implementation of the second algorithm on https://en.wikipedia.org/wiki/Poisson_distribution#Generating_Poisson-distributed_random_variables 32 | double lambdaLeft = lambda; 33 | int k = 0; 34 | double p = 1; 35 | 36 | do { 37 | k++; 38 | p *= nextDouble(); 39 | if (p < Math.E && lambdaLeft > 0) { 40 | if (lambdaLeft > STEP) { 41 | p *= Math.exp(STEP); 42 | lambdaLeft -= STEP; 43 | } else { 44 | p *= Math.exp(lambdaLeft); 45 | lambdaLeft = -1; 46 | } 47 | } 48 | } while (p > 1); 49 | return k - 1; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/SimulationState.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation; 2 | 3 | /** 4 | * The state of the simulation. 5 | */ 6 | public enum SimulationState { 7 | INITIALIZED, 8 | RUNNING, 9 | STOPPED 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/transactionpattern/ITransactionPattern.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 4 | import nl.tudelft.blockchain.scaleoutdistributedledger.SimulationMain; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.TransactionCreator; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Chain; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.CancellableInfiniteRunnable; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 13 | 14 | import java.io.Serializable; 15 | import java.util.logging.Level; 16 | 17 | /** 18 | * Interface for a transaction pattern. 19 | */ 20 | public interface ITransactionPattern extends Serializable { 21 | /** 22 | * @return the name of this transaction pattern 23 | */ 24 | public String getName(); 25 | 26 | /** 27 | * @return - the number of blocks to create before committing to the main chain 28 | */ 29 | public int getCommitEvery(); 30 | 31 | /** 32 | * Selects the node to send money to. 33 | * @param localStore - the local store 34 | * @return - the selected node 35 | */ 36 | public Node selectNode(LocalStore localStore); 37 | 38 | /** 39 | * Selects the amount of money to send. 40 | * @param localStore - the local store 41 | * @return - the selected amount or -1 if not enough money 42 | */ 43 | public long selectAmount(LocalStore localStore); 44 | 45 | /** 46 | * Perform one or more actions. 47 | * @param localStore - the local store 48 | * @throws InterruptedException - if the action is interrupted 49 | */ 50 | public default void doAction(LocalStore localStore) throws InterruptedException { 51 | Log.log(Level.FINER, "Start doAction", localStore.getOwnNode().getId()); 52 | OwnNode ownNode = localStore.getOwnNode(); 53 | int ownNodeId = ownNode.getId(); 54 | 55 | // Make sure we have some room 56 | if (localStore.getApplication().getTransactionSender().blocksWaiting() >= SimulationMain.MAX_BLOCKS_PENDING) { 57 | Log.log(Level.INFO, "Too many blocks pending, skipping transaction creation!", ownNodeId); 58 | return; 59 | } 60 | 61 | //Select receiver and amount 62 | long amount = selectAmount(localStore); 63 | if (amount == -1) { 64 | Log.log(Level.INFO, "Not enough money to make transaction!", ownNodeId); 65 | return; 66 | } 67 | 68 | Node receiver = selectNode(localStore); 69 | Log.log(Level.FINE, "Going to make transaction: $ " + amount + " from " + ownNodeId + " -> " + receiver.getId()); 70 | 71 | //Create the transaction 72 | TransactionCreator creator = new TransactionCreator(localStore, receiver, amount); 73 | Transaction transaction = creator.createTransaction(); 74 | 75 | //Add block to local chain 76 | Block newBlock = ownNode.getChain().appendNewBlock(); 77 | newBlock.addTransaction(transaction); 78 | Log.log(Level.FINE, "Node " + ownNodeId + " added transaction " + transaction.getNumber() + " in block " + newBlock.getNumber()); 79 | 80 | //Check if we want to commit the new block, and commit it if we do. 81 | commitBlocks(localStore, false); 82 | } 83 | 84 | /** 85 | * @param lastBlock - the last block in the chain 86 | * @param lastCommitted - the last committed block 87 | * @return - true if we should commit now 88 | */ 89 | public default boolean shouldCommitBlocks(Block lastBlock, Block lastCommitted) { 90 | int uncommitted = lastBlock.getNumber() - lastCommitted.getNumber(); 91 | return uncommitted >= getCommitEvery(); 92 | } 93 | 94 | /** 95 | * Commits blocks to the main chain if necessary. 96 | * @param localStore - the local store 97 | * @param force - if true, then committing is forced 98 | * @throws InterruptedException - if sending transactions is interrupted 99 | */ 100 | public default void commitBlocks(LocalStore localStore, boolean force) throws InterruptedException { 101 | Chain ownChain = localStore.getOwnNode().getChain(); 102 | Block lastBlock = ownChain.getLastBlock(); 103 | Block lastCommitted = ownChain.getLastCommittedBlock(); 104 | 105 | //Don't commit if we don't have anything to commit 106 | if (lastBlock == lastCommitted) return; 107 | 108 | if (force || shouldCommitBlocks(lastBlock, lastCommitted)) { 109 | lastBlock.commit(localStore); 110 | } 111 | } 112 | 113 | /** 114 | * Commits extra empty blocks. 115 | * @param localStore - the local store 116 | */ 117 | public default void commitExtraEmpty(LocalStore localStore) { 118 | Chain ownChain = localStore.getOwnNode().getChain(); 119 | for (int i = 0; i < SimulationMain.REQUIRED_COMMITS + 1; i++) { 120 | Block block = ownChain.appendNewBlock(); 121 | block.commit(localStore); 122 | } 123 | } 124 | 125 | /** 126 | * @param localStore - the local store 127 | * @return the time until the next action 128 | */ 129 | public long timeUntilNextAction(LocalStore localStore); 130 | 131 | /** 132 | * Called once whenever we stop running. 133 | * @param localStore - the local store 134 | */ 135 | public default void onStop(LocalStore localStore) { 136 | try { 137 | commitBlocks(localStore, true); 138 | commitExtraEmpty(localStore); 139 | } catch (InterruptedException ex) { 140 | Log.log(Level.SEVERE, "Interrupted while committing blocks!"); 141 | } catch (Exception ex) { 142 | Log.log(Level.SEVERE, "Unable to commit blocks onStop: ", ex); 143 | } finally { 144 | localStore.getApplication().finishTransactionSending(); 145 | } 146 | } 147 | 148 | /** 149 | * Creates a CancellableInfiniteRunnable for executing this transaction pattern. 150 | * @param localStore - the local store 151 | * @return - the runnable 152 | */ 153 | public default CancellableInfiniteRunnable getRunnable(LocalStore localStore) { 154 | return new CancellableInfiniteRunnable( 155 | localStore, 156 | this::doAction, 157 | this::timeUntilNextAction, 158 | this::onStop 159 | ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/transactionpattern/OnlyNodeZeroTransactionPattern.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 4 | 5 | /** 6 | * Transaction pattern where only node 0 makes transactions. 7 | * Only used for debugging. 8 | */ 9 | public class OnlyNodeZeroTransactionPattern extends UniformRandomTransactionPattern { 10 | private static final long serialVersionUID = 1L; 11 | 12 | /** 13 | * @param minAmount - the minimum amount of money to send 14 | * @param maxAmount - the maximum amount of money to send 15 | * @param minWaitTime - the minimum wait time between making transactions 16 | * @param maxWaitTime - the maximum wait time between making transactions 17 | * @param commitEvery - commit every x blocks 18 | */ 19 | public OnlyNodeZeroTransactionPattern(int minAmount, int maxAmount, int minWaitTime, int maxWaitTime, int commitEvery) { 20 | super(minAmount, maxAmount, minWaitTime, maxWaitTime, commitEvery); 21 | } 22 | 23 | @Override 24 | public long selectAmount(LocalStore localStore) { 25 | //Only let node 0 make transactions 26 | if (localStore.getOwnNode().getId() != 0) return -1; 27 | 28 | return super.selectAmount(localStore); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/transactionpattern/PoissonRandomTransactionPattern.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern; 2 | 3 | import java.io.IOException; 4 | import java.io.ObjectInputStream; 5 | import java.io.ObjectOutputStream; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.PoissonRandom; 10 | 11 | /** 12 | * Random transaction pattern that uses a poisson distribution for the wait time and the amount 13 | * of money to send. 14 | */ 15 | public class PoissonRandomTransactionPattern extends RandomTransactionPattern { 16 | private static final long serialVersionUID = 1L; 17 | 18 | private double lambdaWaitTime; 19 | private double lambdaAmount; 20 | private transient PoissonRandom random; 21 | 22 | /** 23 | * @param lambdaAmount - the lambda transaction amount 24 | * @param lambdaWaitTime - the lambda time to wait 25 | * @param commitEvery - commit to the main chain after this amount of blocks 26 | */ 27 | public PoissonRandomTransactionPattern(double lambdaAmount, double lambdaWaitTime, int commitEvery) { 28 | super(commitEvery); 29 | this.lambdaAmount = lambdaAmount; 30 | this.lambdaWaitTime = lambdaWaitTime; 31 | this.random = new PoissonRandom(); 32 | } 33 | 34 | @Override 35 | public void setSeed(long seed) { 36 | this.seed = seed; 37 | this.random.setSeed(seed); 38 | } 39 | 40 | @Override 41 | public String getName() { 42 | return "Poisson Random"; 43 | } 44 | 45 | @Override 46 | public Node selectNode(LocalStore localStore) { 47 | int amount = localStore.getNodes().size(); 48 | if (amount <= 1) throw new IllegalStateException("There is only 1 node!"); 49 | 50 | //TODO do we want to select nodes to send to uniformly or also poisson distributed? 51 | int selected; 52 | Node node; 53 | do { 54 | selected = random.nextInt(amount); 55 | node = localStore.getNode(selected); 56 | } while (node == localStore.getOwnNode()); 57 | return node; 58 | } 59 | 60 | @Override 61 | public long selectAmount(LocalStore localStore) { 62 | long available = localStore.getAvailableMoney(); 63 | if (available == 0) return -1; 64 | 65 | long amount = random.nextPoisson(lambdaAmount); 66 | return Math.min(amount, available); 67 | } 68 | 69 | @Override 70 | public long timeUntilNextAction(LocalStore localStore) { 71 | return random.nextPoisson(lambdaWaitTime); 72 | } 73 | 74 | private void writeObject(ObjectOutputStream stream) throws IOException { 75 | stream.defaultWriteObject(); 76 | } 77 | 78 | private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 79 | stream.defaultReadObject(); 80 | this.random = new PoissonRandom(); 81 | if (this.seed != null) { 82 | this.random.setSeed(this.seed); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/transactionpattern/RandomTransactionPattern.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern; 2 | 3 | /** 4 | * Random transaction pattern. 5 | */ 6 | public abstract class RandomTransactionPattern implements ITransactionPattern { 7 | private static final long serialVersionUID = 6328897872068347550L; 8 | 9 | protected final int commitEvery; 10 | protected Long seed; 11 | 12 | /** 13 | * @param commitEvery - commit to the main chain after this amount of blocks 14 | */ 15 | public RandomTransactionPattern(int commitEvery) { 16 | this.commitEvery = commitEvery; 17 | } 18 | 19 | @Override 20 | public int getCommitEvery() { 21 | return commitEvery; 22 | } 23 | 24 | /** 25 | * Sets the seed that is used for random number generation. 26 | * @param seed - the seed 27 | */ 28 | public abstract void setSeed(long seed); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/simulation/transactionpattern/UniformRandomTransactionPattern.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern; 2 | 3 | import java.io.IOException; 4 | import java.io.ObjectInputStream; 5 | import java.io.ObjectOutputStream; 6 | import java.util.Random; 7 | 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 10 | 11 | /** 12 | * Random transaction pattern that uses a uniform distribution. 13 | */ 14 | public class UniformRandomTransactionPattern extends RandomTransactionPattern { 15 | private static final long serialVersionUID = 1L; 16 | 17 | private int minWaitTime; 18 | private int maxWaitTime; 19 | private int minAmount; 20 | private int maxAmount; 21 | private transient Random random; 22 | 23 | /** 24 | * @param minAmount - the minimal transaction amount 25 | * @param maxAmount - the maximal transaction amount 26 | * @param minWaitTime - the minimum time to wait 27 | * @param maxWaitTime - the maximum time to wait 28 | * @param commitEvery - commit to the main chain after this amount of blocks 29 | */ 30 | public UniformRandomTransactionPattern(int minAmount, int maxAmount, int minWaitTime, int maxWaitTime, int commitEvery) { 31 | super(commitEvery); 32 | this.minAmount = minAmount; 33 | this.maxAmount = maxAmount; 34 | this.minWaitTime = minWaitTime; 35 | this.maxWaitTime = maxWaitTime; 36 | this.random = new Random(); 37 | } 38 | 39 | @Override 40 | public void setSeed(long seed) { 41 | this.seed = seed; 42 | this.random.setSeed(seed); 43 | } 44 | 45 | @Override 46 | public String getName() { 47 | return "Uniform Random"; 48 | } 49 | 50 | @Override 51 | public long timeUntilNextAction(LocalStore localStore) { 52 | if (maxWaitTime == minWaitTime) { 53 | return minWaitTime; 54 | } else { 55 | return minWaitTime + random.nextInt(maxWaitTime - minWaitTime); 56 | } 57 | } 58 | 59 | @Override 60 | public Node selectNode(LocalStore localStore) { 61 | int amount = localStore.getNodes().size(); 62 | if (amount <= 1) throw new IllegalStateException("There is only 1 node!"); 63 | 64 | int selected; 65 | Node node; 66 | do { 67 | selected = random.nextInt(amount); 68 | node = localStore.getNode(selected); 69 | } while (node == localStore.getOwnNode()); 70 | return node; 71 | } 72 | 73 | @Override 74 | public long selectAmount(LocalStore localStore) { 75 | long available = localStore.getAvailableMoney(); 76 | if (available == 0 || minAmount > available) return -1; 77 | long amount; 78 | if (maxAmount == minAmount) { 79 | amount = minAmount; 80 | } else { 81 | amount = (long) minAmount + random.nextInt(maxAmount - minAmount); 82 | } 83 | return Math.min(amount, available); 84 | } 85 | 86 | private void writeObject(ObjectOutputStream stream) throws IOException { 87 | stream.defaultWriteObject(); 88 | } 89 | 90 | private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { 91 | stream.defaultReadObject(); 92 | this.random = new Random(); 93 | if (this.seed != null) { 94 | this.random.setSeed(this.seed); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/sockets/SocketClient.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.sockets; 2 | 3 | import java.util.HashMap; 4 | import java.util.logging.Level; 5 | 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 8 | 9 | import io.netty.bootstrap.Bootstrap; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelFuture; 12 | import io.netty.channel.ChannelFutureListener; 13 | import io.netty.channel.ChannelInitializer; 14 | import io.netty.channel.ChannelPipeline; 15 | import io.netty.channel.EventLoopGroup; 16 | import io.netty.channel.nio.NioEventLoopGroup; 17 | import io.netty.channel.socket.SocketChannel; 18 | import io.netty.channel.socket.nio.NioSocketChannel; 19 | import io.netty.handler.codec.serialization.ClassResolvers; 20 | import io.netty.handler.codec.serialization.ObjectDecoder; 21 | import io.netty.handler.codec.serialization.ObjectEncoder; 22 | 23 | /** 24 | * Socket client. 25 | */ 26 | public class SocketClient { 27 | 28 | private HashMap connections; 29 | 30 | private Bootstrap bootstrap; 31 | 32 | private EventLoopGroup group; 33 | 34 | /** 35 | * Constructor. 36 | */ 37 | public SocketClient() { 38 | this.connections = new HashMap<>(); 39 | this.initSocketClient(); 40 | } 41 | 42 | /** 43 | * Init the client. 44 | * Note: one client can be used to send to multiple servers, this just sets the settings and pipeline. 45 | */ 46 | private void initSocketClient() { 47 | group = new NioEventLoopGroup(); 48 | bootstrap = new Bootstrap(); 49 | bootstrap.group(group) 50 | .channel(NioSocketChannel.class) 51 | .handler(new ChannelInitializer() { 52 | @Override 53 | protected void initChannel(SocketChannel socketChannel) throws Exception { 54 | ChannelPipeline p = socketChannel.pipeline(); 55 | p.addLast(new ObjectEncoder(), 56 | new ObjectDecoder(ClassResolvers.cacheDisabled(null)), 57 | new SocketClientHandler()); 58 | } 59 | }); 60 | } 61 | 62 | /** 63 | * Shuts down the client server. 64 | */ 65 | public void shutdown() { 66 | Log.log(Level.INFO, "Shutting down socket client..."); 67 | group.shutdownGracefully(); 68 | } 69 | 70 | /** 71 | * Send object to specified host. 72 | * Is blocking until message is actually sent, or failed (up to 60 seconds) 73 | * @param node - the node to send the message to. 74 | * @param msg - the message object to send 75 | * @return - whether the message was sent successfully 76 | * @throws InterruptedException - If message sending is interrupted. 77 | */ 78 | public boolean sendMessage(Node node, Object msg) throws InterruptedException { 79 | Channel channel = connections.get(node); 80 | if (channel == null || !channel.isOpen()) { 81 | Log.log(Level.FINE, "No open connection found, connecting..."); 82 | ChannelFuture future = bootstrap.connect(node.getAddress(), node.getPort()); 83 | if (!future.await().isSuccess()) { 84 | // Could not connect 85 | Log.log(Level.SEVERE, "Unable to connect to " + node.getAddress() + ":" + node.getPort(), future.cause()); 86 | return false; 87 | } 88 | assert future.isDone(); 89 | channel = future.channel(); 90 | future.channel().closeFuture().addListener((ChannelFutureListener) channelFuture -> Log.log(Level.FINE, "Client detected channel close")); 91 | Log.log(Level.FINE, "Client connected to server!"); 92 | } 93 | 94 | ChannelFuture future = channel.writeAndFlush(msg); 95 | Log.log(Level.FINE, "Message sent by client"); 96 | 97 | this.connections.put(node, future.channel()); 98 | 99 | future.await(); 100 | 101 | if (!future.isSuccess()) { 102 | Log.log(Level.SEVERE, "Failed to send message", future.cause()); 103 | return false; 104 | } 105 | 106 | return true; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/sockets/SocketClientHandler.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.sockets; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 6 | 7 | import java.util.logging.Level; 8 | 9 | /** 10 | * Socket handler for the client. 11 | */ 12 | public class SocketClientHandler extends ChannelInboundHandlerAdapter { 13 | 14 | @Override 15 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 16 | Log.log(Level.SEVERE, "Client socket error", cause); 17 | ctx.close(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/sockets/SocketServer.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.sockets; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelInitializer; 5 | import io.netty.channel.ChannelPipeline; 6 | import io.netty.channel.EventLoopGroup; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.handler.codec.serialization.ClassResolvers; 11 | import io.netty.handler.codec.serialization.ObjectDecoder; 12 | import io.netty.handler.codec.serialization.ObjectEncoder; 13 | import io.netty.handler.logging.LogLevel; 14 | import io.netty.handler.logging.LoggingHandler; 15 | import io.netty.handler.timeout.IdleStateHandler; 16 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 17 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 18 | 19 | import java.util.logging.Level; 20 | 21 | /** 22 | * Socket server. 23 | */ 24 | public class SocketServer implements Runnable { 25 | 26 | // In seconds, time connections are kept open after messages. 27 | private static final int CHANNEL_TIMEOUT = 30; 28 | // The maximum message size in bytes. 29 | private static final int MAX_MESSAGE_SIZE = 5 * 1024 * 1024; 30 | 31 | private int port; 32 | private LocalStore localStore; 33 | 34 | /** 35 | * Constructor. 36 | * @param port - the port to listen on. 37 | * @param localStore - the localstore of the node. 38 | */ 39 | public SocketServer(int port, LocalStore localStore) { 40 | this.port = port; 41 | this.localStore = localStore; 42 | } 43 | 44 | /** 45 | * Init the socket serer. 46 | */ 47 | public void initServer() { 48 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 49 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 50 | 51 | try { 52 | ServerBootstrap b = new ServerBootstrap(); 53 | b.group(bossGroup, workerGroup) 54 | .channel(NioServerSocketChannel.class) 55 | .handler(new LoggingHandler(LogLevel.DEBUG)) 56 | .childHandler(new ChannelInitializer() { 57 | @Override 58 | protected void initChannel(SocketChannel socketChannel) throws Exception { 59 | ChannelPipeline p = socketChannel.pipeline(); 60 | p.addLast(new IdleStateHandler(0, 0, CHANNEL_TIMEOUT), 61 | new ObjectEncoder(), 62 | new ObjectDecoder(MAX_MESSAGE_SIZE, ClassResolvers.cacheDisabled(null)), 63 | new SocketServerHandler(localStore)); 64 | } 65 | }); 66 | 67 | Log.log(Level.INFO, "Starting socket server on port " + this.port); 68 | b.bind(port).sync().channel().closeFuture().sync(); 69 | } catch (InterruptedException e) { 70 | Log.log(Level.INFO, "Socket server was interrupted, shutting down..."); 71 | } catch (Exception ex) { 72 | Log.log(Level.SEVERE, "Exception in socket server", ex); 73 | } finally { 74 | bossGroup.shutdownGracefully(); 75 | workerGroup.shutdownGracefully(); 76 | } 77 | } 78 | 79 | @Override 80 | public void run() { 81 | this.initServer(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/sockets/SocketServerHandler.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.sockets; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.handler.timeout.IdleState; 6 | import io.netty.handler.timeout.IdleStateEvent; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.message.Message; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 10 | 11 | import java.util.logging.Level; 12 | 13 | /** 14 | * Handler for socket server. 15 | */ 16 | public class SocketServerHandler extends ChannelInboundHandlerAdapter { 17 | 18 | private LocalStore localStore; 19 | 20 | /** 21 | * Constructor. 22 | * @param localStore - the localstor of the node 23 | */ 24 | public SocketServerHandler(LocalStore localStore) { 25 | this.localStore = localStore; 26 | } 27 | 28 | @Override 29 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 30 | if (msg instanceof Message) { 31 | ((Message) msg).handle(localStore); 32 | } else { 33 | Log.log(Level.SEVERE, "Invalid message, not a message instance"); 34 | } 35 | } 36 | 37 | @Override 38 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 39 | Log.log(Level.SEVERE, "Node " + localStore.getOwnNode().getId() + " Server: socket error", cause); 40 | ctx.close(); 41 | } 42 | 43 | @Override 44 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 45 | if (evt instanceof IdleStateEvent) { 46 | IdleStateEvent e = (IdleStateEvent) evt; 47 | if (e.state() == IdleState.ALL_IDLE) { 48 | Log.log(Level.FINE, "Node " + localStore.getOwnNode().getId() + " Server: detected idle channel, closing connection!"); 49 | ctx.close(); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/utils/Log.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.utils; 2 | 3 | import java.util.logging.ConsoleHandler; 4 | import java.util.logging.Handler; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | /** 9 | * Class for logging functions. 10 | */ 11 | public final class Log { 12 | public static final Level LEVEL = Level.INFO; 13 | public static final String FORMAT = "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$-6s %3$s %5$s%6$s%n"; 14 | public static final String DEBUG_FORMAT = "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$-6s %2$s %5$s%6$s%n"; 15 | 16 | public static final Logger PARENT_LOGGER = Logger.getLogger(Log.class.getName()).getParent(); 17 | public static final Logger DEBUG_LOGGER = Logger.getLogger("DEBUG"); 18 | 19 | private Log() { 20 | throw new UnsupportedOperationException(); 21 | } 22 | 23 | static { 24 | //Set the level and the format string. 25 | PARENT_LOGGER.setLevel(LEVEL); 26 | 27 | LogFormatter formatter = new LogFormatter(FORMAT); 28 | for (Handler handler : PARENT_LOGGER.getHandlers()) { 29 | if (handler instanceof ConsoleHandler) { 30 | handler.setFormatter(formatter); 31 | handler.setLevel(LEVEL); 32 | } 33 | } 34 | 35 | //Create debug console handler 36 | ConsoleHandler debugHandler = new ConsoleHandler(); 37 | debugHandler.setLevel(Level.ALL); 38 | debugHandler.setFormatter(new LogFormatter(DEBUG_FORMAT)); 39 | 40 | //Setup debug logger 41 | DEBUG_LOGGER.setUseParentHandlers(false); 42 | DEBUG_LOGGER.addHandler(debugHandler); 43 | DEBUG_LOGGER.setLevel(LEVEL); 44 | } 45 | 46 | /** 47 | * Change the log level. 48 | * @param level - the level to change to. 49 | */ 50 | public static void setLogLevel(Level level) { 51 | PARENT_LOGGER.setLevel(level); 52 | } 53 | 54 | /** 55 | * Get the current log level. 56 | * @return - the current log level 57 | */ 58 | public static Level getLogLevel() { 59 | return PARENT_LOGGER.getLevel(); 60 | } 61 | 62 | /** 63 | * Handle logging of an exception. 64 | * @param level - level of the exception 65 | * @param str - message for the logs 66 | * @param throwable - the object of the exception 67 | */ 68 | public static void log(Level level, String str, Throwable throwable) { 69 | // By default 70 | Logger.getLogger(getCallerClassName()).log(level, str, throwable); 71 | } 72 | 73 | /** 74 | * Logs the given message. 75 | * @param level - level to log at 76 | * @param str - message to log 77 | */ 78 | public static void log(Level level, String str) { 79 | Logger.getLogger(getCallerClassName()).log(level, str); 80 | } 81 | 82 | /** 83 | * Logs the given message for the given node id. 84 | * @param level - level to log at 85 | * @param str - message to log 86 | * @param nodeId - the id of the node 87 | */ 88 | public static void log(Level level, String str, int nodeId) { 89 | Logger.getLogger(getCallerClassName()).log(level, "[" + nodeId + "] " + str); 90 | } 91 | 92 | /** 93 | * Logs the given debug message. 94 | * @param msg - the message 95 | * @param params - the parameters 96 | */ 97 | public static void debug(String msg, Object... params) { 98 | StackTraceElement ste = getCallerStackTrace(3); 99 | if (ste == null) { 100 | DEBUG_LOGGER.logp(Level.INFO, null, null, msg, params); 101 | } else { 102 | String className = ste.getClassName(); 103 | className = className.substring(className.lastIndexOf('.') + 1); 104 | String sourceMethod = ste.getMethodName() + ":" + ste.getLineNumber(); 105 | DEBUG_LOGGER.logp(Level.INFO, className, sourceMethod, msg, params); 106 | } 107 | } 108 | 109 | /** 110 | * Get the name of the last class that added to the stack. 111 | * @return class name 112 | */ 113 | private static String getCallerClassName() { 114 | StackTraceElement ste = getCallerStackTrace(4); 115 | if (ste == null) return null; 116 | String className = ste.getClassName(); 117 | return className.substring(className.lastIndexOf('.') + 1); 118 | } 119 | 120 | /** 121 | * Determines the stack trace element of the caller. 122 | * @return - the stack trace element of the caller 123 | */ 124 | private static StackTraceElement getCallerStackTrace(int start) { 125 | StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); 126 | for (int i = start; i < stackTraceElements.length; i++) { 127 | StackTraceElement ste = stackTraceElements[i]; 128 | if (ste.getClassName().indexOf("java.lang.Thread") != 0) { 129 | return ste; 130 | } 131 | } 132 | return null; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/utils/LogFormatter.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.utils; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | import java.util.Date; 6 | import java.util.logging.Formatter; 7 | import java.util.logging.LogRecord; 8 | 9 | /** 10 | * Formatter for the log. This class is mostly a clone of SimpleFormatter but with a few small adjustments. 11 | */ 12 | public class LogFormatter extends Formatter { 13 | private final String format; 14 | private final Date dat = new Date(); 15 | 16 | /** 17 | * Creates a new log formatter with the given format. 18 | *
19 | 	 * %1 = date
20 | 	 * %2 = source class
21 | 	 * %3 = logger name
22 | 	 * %4 = level
23 | 	 * %5 = message
24 | 	 * %6 = exception
25 | 	 * 
26 | * @param format - the format string 27 | */ 28 | public LogFormatter(String format) { 29 | this.format = format; 30 | } 31 | 32 | @Override 33 | public synchronized String format(LogRecord record) { 34 | dat.setTime(record.getMillis()); 35 | String source; 36 | if (record.getSourceClassName() != null) { 37 | source = record.getSourceClassName(); 38 | if (record.getSourceMethodName() != null) { 39 | source += " " + record.getSourceMethodName(); 40 | } 41 | } else { 42 | source = record.getLoggerName(); 43 | } 44 | String message = formatMessage(record); 45 | String throwable; 46 | if (record.getThrown() != null) { 47 | StringWriter sw = new StringWriter(); 48 | try (PrintWriter pw = new PrintWriter(sw)) { 49 | pw.println(); 50 | record.getThrown().printStackTrace(pw); 51 | } 52 | throwable = sw.toString(); 53 | } else { 54 | throwable = ""; 55 | } 56 | 57 | return String.format(format, dat, source, record.getLoggerName(), record.getLevel().getLocalizedName(), message, throwable); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.utils; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.Base64; 5 | import java.util.logging.Level; 6 | 7 | /** 8 | * Class for helper functions. 9 | */ 10 | public final class Utils { 11 | private static final char[] HEX = { 12 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 13 | }; 14 | 15 | private Utils() { 16 | throw new UnsupportedOperationException(); 17 | } 18 | 19 | /** 20 | * Convert a integer into a byte array. 21 | * @param number - the number to convert 22 | * @return byte array 23 | */ 24 | public static byte[] intToByteArray(int number) { 25 | return new byte[] { 26 | (byte) (number >>> 24), 27 | (byte) (number >>> 16), 28 | (byte) (number >>> 8), 29 | (byte) number 30 | }; 31 | } 32 | 33 | /** 34 | * Convert a long into a byte array. 35 | * @param number - the number to convert 36 | * @return byte array 37 | */ 38 | public static byte[] longToByteArray(long number) { 39 | return new byte[] { 40 | (byte) (number >> 56), 41 | (byte) (number >> 48), 42 | (byte) (number >> 40), 43 | (byte) (number >> 32), 44 | (byte) (number >> 24), 45 | (byte) (number >> 16), 46 | (byte) (number >> 8), 47 | (byte) number 48 | }; 49 | } 50 | 51 | /** 52 | * Converts a byte array into a hexadecimal string. 53 | * @param bytes - array of bytes to be converted 54 | * @return hexadecimal string 55 | */ 56 | public static String bytesToHexString(byte[] bytes) { 57 | StringBuilder buffer = new StringBuilder(bytes.length * 2); 58 | for (byte b : bytes) { 59 | buffer.append(HEX[(b >> 4) & 0xF]); 60 | buffer.append(HEX[b & 0xF]); 61 | } 62 | return buffer.toString(); 63 | } 64 | 65 | /** 66 | * Converts a hexadecimal String to a byte array. 67 | * @param hex - the hexadecimal string 68 | * @return the byte array 69 | */ 70 | public static byte[] hexStringToBytes(String hex) { 71 | int strLen = hex.length(); 72 | if (strLen % 2 != 0) throw new IllegalArgumentException("Length must be even!"); 73 | 74 | byte[] bytes = new byte[strLen / 2]; 75 | for (int i = 0; i < strLen; i += 2) { 76 | int high = hexToBin(hex.charAt(i)); 77 | int low = hexToBin(hex.charAt(i + 1)); 78 | bytes[i / 2] = (byte) ((high << 4) + low); 79 | } 80 | 81 | return bytes; 82 | } 83 | 84 | /** 85 | * @param ch - the hex character to convert 86 | * @return the binary value of the given hex character 87 | */ 88 | private static int hexToBin(char ch) { 89 | if ('0' <= ch && ch <= '9') { 90 | return ch - '0'; 91 | } 92 | if ('A' <= ch && ch <= 'F') { 93 | return ch - 'A' + 10; 94 | } 95 | if ('a' <= ch && ch <= 'f') { 96 | return ch - 'a' + 10; 97 | } 98 | throw new IllegalArgumentException("'" + ch + "' is not a hexadecimal character!"); 99 | } 100 | 101 | /** 102 | * Convert a base 64 string to a byte array. 103 | * @param data - The base 64 string to convert 104 | * @return - The resulting byte array, or an empty one if the conversion fails 105 | */ 106 | public static byte[] base64StringToBytes(String data) { 107 | try { 108 | return Base64.getDecoder().decode(data.getBytes("UTF-8")); 109 | } catch (UnsupportedEncodingException e) { 110 | Log.log(Level.SEVERE, "This encoding should always be present", e); 111 | return new byte[0]; 112 | } 113 | } 114 | 115 | /** 116 | * Convert a byte array to a base 64 string. 117 | * @param bytes - The bytes to convert 118 | * @return - The resulting base 64 string, or an empty one if the conversion fails 119 | */ 120 | public static String bytesToBas64String(byte[] bytes) { 121 | return Base64.getEncoder().encodeToString(bytes); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/validation/ProofValidationException.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.validation; 2 | 3 | /** 4 | * Validation exception for proofs. 5 | */ 6 | public class ProofValidationException extends ValidationException { 7 | private static final long serialVersionUID = 1L; 8 | 9 | /** 10 | * @param reason - the reason 11 | */ 12 | public ProofValidationException(String reason) { 13 | super(reason); 14 | } 15 | 16 | /** 17 | * @param reason - the reason 18 | * @param cause - the validation exception that caused this one 19 | */ 20 | public ProofValidationException(String reason, ValidationException cause) { 21 | super(reason, cause); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/nl/tudelft/blockchain/scaleoutdistributedledger/validation/ValidationException.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.validation; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 4 | 5 | /** 6 | * Exception for representing validation going wrong. 7 | */ 8 | public class ValidationException extends RuntimeException { 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * @param reason - the reason 13 | */ 14 | public ValidationException(String reason) { 15 | super(reason); 16 | } 17 | 18 | /** 19 | * @param reason - the reason 20 | * @param cause - the validation exception that caused this one 21 | */ 22 | public ValidationException(String reason, ValidationException cause) { 23 | super(reason, cause); 24 | } 25 | 26 | /** 27 | * Creates a validation exception with the following message. 28 | *
Transaction [transaction] is invalid: [reason]
29 | * @param transaction - the transaction 30 | * @param reason - the reason 31 | */ 32 | public ValidationException(Transaction transaction, String reason) { 33 | super("Transaction " + transaction + " is invalid: " + reason); 34 | } 35 | 36 | /** 37 | * Creates a validation exception with the following message. 38 | *
Transaction [transaction] is invalid: [reason]
39 | * @param transaction - the transaction 40 | * @param reason - the reason 41 | * @param cause - the validation exception that caused this one 42 | */ 43 | public ValidationException(Transaction transaction, String reason, ValidationException cause) { 44 | super("Transaction " + transaction + " is invalid: " + reason, cause); 45 | } 46 | 47 | @Override 48 | public String getMessage() { 49 | if (this.getCause() != null) { 50 | return super.getMessage() + " caused by " + this.getCause().getMessage(); 51 | } else { 52 | return super.getMessage(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/resources/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/src/main/resources/.gitignore -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.mocks.TendermintChainMock; 4 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.MainChain; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.CancellableInfiniteRunnable; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern.ITransactionPattern; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | import static org.mockito.Mockito.*; 13 | 14 | /** 15 | * Class to test {@link Application}. 16 | */ 17 | public class ApplicationTest { 18 | 19 | private Application instance; 20 | private Thread serverMock; 21 | private TransactionSender transactionSenderMock; 22 | private LocalStore localStoreMock; 23 | 24 | /** 25 | * Setup method. 26 | */ 27 | @Before 28 | public void setUp() { 29 | this.localStoreMock = mock(LocalStore.class); 30 | this.serverMock = mock(Thread.class); 31 | this.transactionSenderMock = mock(TransactionSender.class); 32 | 33 | this.instance = new Application(this.localStoreMock, this.serverMock, this.transactionSenderMock); 34 | when(this.localStoreMock.getMainChain()).thenReturn(spy(new TendermintChainMock())); 35 | when(this.localStoreMock.getOwnNode()).thenReturn(new OwnNode(0)); 36 | } 37 | 38 | /** 39 | * Test for {@link Application#kill()}. 40 | */ 41 | @Test 42 | public void testKill() { 43 | this.instance.kill(); 44 | 45 | verify(this.transactionSenderMock, times(1)).shutdownNow(); 46 | verify(this.instance.getMainChain(), times(1)).stop(); 47 | } 48 | 49 | /** 50 | * Test for {@link Application#startTransacting()}. 51 | * @throws java.lang.InterruptedException - interrupted while sleeping 52 | */ 53 | @Test 54 | public void testStartTransacting() throws InterruptedException { 55 | CancellableInfiniteRunnable runnableMock = this.setTransactionPattern(); 56 | 57 | this.instance.startTransacting(); 58 | // Wait for thread to start 59 | verify(runnableMock, timeout(2000).times(1)).run(); 60 | } 61 | 62 | /** 63 | * Test for {@link Application#getMainChain()}. 64 | */ 65 | @Test 66 | public void testGetMainChain() { 67 | MainChain chain = mock(MainChain.class); 68 | when(this.localStoreMock.getMainChain()).thenReturn(chain); 69 | assertEquals(chain, this.instance.getMainChain()); 70 | } 71 | 72 | /** 73 | * Test for {@link Application#finishTransactionSending()}. 74 | * @throws InterruptedException - interrupted while sleeping 75 | */ 76 | @Test 77 | public void testFinishTransactionSending() throws InterruptedException { 78 | this.instance.finishTransactionSending(); 79 | 80 | // verify(this.transactionSenderMock, times(1)).stop(); 81 | verify(this.transactionSenderMock, times(1)).waitUntilDone(); 82 | } 83 | 84 | /** 85 | * Set a transaction pattern. 86 | * @return cancellable infinite runnable 87 | */ 88 | private CancellableInfiniteRunnable setTransactionPattern() { 89 | ITransactionPattern transactionPatternMock = mock(ITransactionPattern.class); 90 | CancellableInfiniteRunnable runnableMock = mock(CancellableInfiniteRunnable.class); 91 | when(transactionPatternMock.getRunnable(any(LocalStore.class))).thenReturn(runnableMock); 92 | this.instance.setTransactionPattern(transactionPatternMock); 93 | return runnableMock; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/CommunicationHelperTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import java.util.*; 4 | 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Proof; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.test.utils.TestHelper; 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import static org.mockito.Mockito.mock; 16 | 17 | /** 18 | * Class to test {@link CommunicationHelper}. 19 | */ 20 | public class CommunicationHelperTest { 21 | 22 | private static int transactionId = 11; 23 | 24 | private Block genesisBlock; 25 | 26 | private OwnNode ownNode; 27 | 28 | private LocalStore localStore; 29 | 30 | private Node bobNode; 31 | 32 | private Node charlieNode; 33 | 34 | /** 35 | * Setup method. 36 | */ 37 | @Before 38 | public void setUp() { 39 | // Setup ownNode 40 | this.ownNode = new OwnNode(0); 41 | this.genesisBlock = TestHelper.generateGenesis(this.ownNode, 10, 1000); 42 | Application application = mock(Application.class); 43 | this.localStore = new LocalStore(this.ownNode, application, this.genesisBlock, false); 44 | // Setup bobNode 45 | this.bobNode = new Node(1); 46 | bobNode.getChain().setGenesisBlock(this.genesisBlock); 47 | // Setup charlie 48 | this.charlieNode = new Node(2); 49 | this.charlieNode.getChain().setGenesisBlock(this.genesisBlock); 50 | } 51 | 52 | /** 53 | * Create a transaction from a genesis block. 54 | * @param sender - node sender 55 | * @param receiver - node receiver 56 | * @param amount - amount of the transaction 57 | * @param remainder - remainder of the transaction 58 | * @return 59 | */ 60 | private Transaction createTransactionFromGenesis(Node sender, Node receiver, long amount, long remainder) { 61 | // Create new block for transaction 62 | List blockList = new ArrayList<>(); 63 | blockList.add(new Block(sender.getChain().getLastBlock().getNumber() + 1, sender, new ArrayList<>())); 64 | sender.getChain().update(blockList, localStore); 65 | // Setup sources 66 | TreeSet sources = new TreeSet<>(); 67 | sources.add(this.genesisBlock.getTransactions().get(sender.getId())); 68 | // Create transaction 69 | Transaction transaction = new Transaction(transactionId++, sender, receiver, amount, remainder, sources); 70 | Block blockOfTransaction = sender.getChain().getLastBlock(); 71 | transaction.setBlockNumber(blockOfTransaction.getNumber()); 72 | blockOfTransaction.addTransaction(transaction); 73 | return transaction; 74 | } 75 | 76 | /** 77 | * Test for {@link CommunicationHelper#receiveTransaction}. 78 | */ 79 | @Test 80 | public void testReceiveTransaction_Valid() { 81 | // Create Transaction and Proof 82 | Transaction transaction = this.createTransactionFromGenesis(this.bobNode, this.ownNode, 100, 900); 83 | Proof proof = new Proof(transaction); 84 | 85 | assertTrue(CommunicationHelper.receiveTransaction(proof, this.localStore)); 86 | assertTrue(this.localStore.getUnspent().contains(transaction)); 87 | } 88 | 89 | /** 90 | * Test for {@link CommunicationHelper#receiveTransaction}. 91 | */ 92 | @Test 93 | public void testReceiveTransaction_InvalidReceiver() { 94 | // Create Transaction and Proof 95 | Transaction transaction = this.createTransactionFromGenesis(this.bobNode, this.charlieNode, 100, 900); 96 | Proof proof = new Proof(transaction); 97 | 98 | assertFalse(CommunicationHelper.receiveTransaction(proof, this.localStore)); 99 | } 100 | 101 | /** 102 | * Test for {@link CommunicationHelper#receiveTransaction}. 103 | */ 104 | @Test 105 | public void testReceiveTransaction_InvalidAmount() { 106 | // Create Transaction and Proof 107 | Transaction transaction = this.createTransactionFromGenesis(this.bobNode, this.ownNode, 9999, 900); 108 | Proof proof = new Proof(transaction); 109 | 110 | assertFalse(CommunicationHelper.receiveTransaction(proof, this.localStore)); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/LocalStoreTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.test.utils.TestHelper; 13 | 14 | /** 15 | * Class to test {@link LocalStore}. 16 | */ 17 | public class LocalStoreTest { 18 | 19 | private OwnNode ownNode; 20 | 21 | private LocalStore localStore; 22 | 23 | /** 24 | * Setup method. 25 | */ 26 | @Before 27 | public void setUp() { 28 | this.ownNode = new OwnNode(0); 29 | Application application = mock(Application.class); 30 | Block genesisBlock = TestHelper.generateGenesis(ownNode, 10, 1000); 31 | this.localStore = new LocalStore(ownNode, application, genesisBlock, false); 32 | } 33 | 34 | /** 35 | * Test for {@link LocalStore#getTransactionFromNode(int, int)}. 36 | */ 37 | @Test 38 | public void testGetTransactionFromNode_Valid_GenesisSender() { 39 | Transaction transaction = this.localStore.getTransactionFromNode(Transaction.GENESIS_SENDER, 0, 0); 40 | assertEquals(this.ownNode, transaction.getReceiver()); 41 | } 42 | 43 | /** 44 | * Test for {@link LocalStore#getTransactionFromNode(int, int)}. 45 | */ 46 | @Test(expected = IllegalStateException.class) 47 | public void testGetTransactionFromNode_Invalid_GenesisSender() { 48 | this.localStore.getTransactionFromNode(Transaction.GENESIS_SENDER, 0, 99); 49 | } 50 | 51 | /** 52 | * Test for {@link LocalStore#getTransactionFromNode(int, int)}. 53 | */ 54 | @Test 55 | public void testGetTransactionFromNode_Valid_NormalSender() { 56 | Transaction transaction = this.localStore.getTransactionFromNode(0, 0, 0); 57 | assertEquals(this.ownNode, transaction.getReceiver()); 58 | } 59 | 60 | /** 61 | * Test for {@link LocalStore#getTransactionFromNode(int, int)}. 62 | */ 63 | @Test(expected = IllegalStateException.class) 64 | public void testGetTransactionFromNode_Invalid_Block() { 65 | this.localStore.getTransactionFromNode(0, 99, 1); 66 | } 67 | 68 | /** 69 | * Test for {@link LocalStore#getTransactionFromNode(int, int)}. 70 | */ 71 | @Test(expected = IllegalStateException.class) 72 | public void testGetTransactionFromNode_Invalid_Transaction() { 73 | this.localStore.getTransactionFromNode(0, 0, 99); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/MessageTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import static org.mockito.Mockito.*; 4 | 5 | import org.junit.Before; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.Application; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 10 | 11 | /** 12 | * Base class for testing messages. 13 | */ 14 | public abstract class MessageTest { 15 | 16 | protected LocalStore storeSpy; 17 | 18 | protected Application appMock; 19 | 20 | /** 21 | * Creates the store and the application. 22 | */ 23 | @Before 24 | public void setUp() { 25 | OwnNode ownNode = new OwnNode(0); 26 | this.appMock = mock(Application.class); 27 | this.storeSpy = spy(new LocalStore(ownNode, this.appMock, null, false)); 28 | doNothing().when(this.storeSpy).updateNodes(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/StartTransactingMessageTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import static org.mockito.Mockito.*; 4 | 5 | import org.junit.Test; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 8 | 9 | /** 10 | * Test class for {@link StartTransactingMessage}. 11 | */ 12 | public class StartTransactingMessageTest extends MessageTest { 13 | 14 | /** 15 | * Test method for {@link StartTransactingMessage#handle(LocalStore)}. 16 | */ 17 | @Test 18 | public void testHandle() { 19 | StartTransactingMessage msg = new StartTransactingMessage(); 20 | msg.handle(storeSpy); 21 | 22 | //We should have started transacting. 23 | verify(appMock, times(1)).startTransacting(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/StopTransactingMessageTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import static org.mockito.Mockito.*; 4 | 5 | import org.junit.Test; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 8 | 9 | /** 10 | * Test class for {@link StopTransactingMessage}. 11 | */ 12 | public class StopTransactingMessageTest extends MessageTest { 13 | 14 | /** 15 | * Test method for {@link StopTransactingMessage#handle(LocalStore)}. 16 | */ 17 | @Test 18 | public void testHandle() { 19 | StopTransactingMessage msg = new StopTransactingMessage(); 20 | msg.handle(storeSpy); 21 | 22 | //We should have stopped transacting. 23 | verify(appMock, times(1)).stopTransacting(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/message/TransactionPatternMessageTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.message; 2 | 3 | import static org.mockito.Mockito.*; 4 | 5 | import org.junit.Test; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.transactionpattern.ITransactionPattern; 9 | 10 | /** 11 | * Test class for {@link TransactionPatternMessage}. 12 | */ 13 | public class TransactionPatternMessageTest extends MessageTest { 14 | 15 | /** 16 | * Test method for {@link TransactionPatternMessage#handle(LocalStore)}. 17 | */ 18 | @Test 19 | public void testHandle() { 20 | ITransactionPattern pattern = mock(ITransactionPattern.class); 21 | TransactionPatternMessage msg = new TransactionPatternMessage(pattern); 22 | msg.handle(this.storeSpy); 23 | 24 | // The correct transaction pattern should be set. 25 | verify(appMock, times(1)).setTransactionPattern(same(pattern)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/BlockAbstractTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.security.SignatureException; 6 | import java.util.ArrayList; 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.assertTrue; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | /** 13 | * Test for {@link BlockAbstract}. 14 | */ 15 | public class BlockAbstractTest { 16 | 17 | private Ed25519Key key; 18 | 19 | private Block block; 20 | 21 | private BlockAbstract blockAbstract; 22 | 23 | /** 24 | * Setup method. 25 | * @throws java.security.SignatureException - error while signing 26 | * @throws java.security.InvalidKeyException - error while using the key 27 | * @throws java.security.NoSuchAlgorithmException - error while using Ed25519 28 | */ 29 | @Before 30 | public void setUp() throws SignatureException, InvalidKeyException, NoSuchAlgorithmException { 31 | this.key = new Ed25519Key(); 32 | OwnNode ownNode = new OwnNode(0); 33 | ownNode.setPublicKey(this.key.getPublicKey()); 34 | ownNode.setPrivateKey(this.key.getPrivateKey()); 35 | this.block = new Block(2, ownNode, new ArrayList<>()); 36 | this.blockAbstract = this.block.calculateBlockAbstract(); 37 | } 38 | 39 | /** 40 | * Test for {@link BlockAbstract#fromBytes()}. 41 | */ 42 | @Test 43 | public void testFromBytes_Invalid() { 44 | // Invalid decoding 45 | BlockAbstract newBlockAbstract = BlockAbstract.fromBytes(new byte[0]); 46 | assertEquals(null, newBlockAbstract); 47 | } 48 | 49 | /** 50 | * Test for {@link BlockAbstract#toBytes()}. 51 | */ 52 | @Test 53 | public void testToBytes_Invalid() { 54 | // Encode 55 | byte[] bytes = this.blockAbstract.toBytes(); 56 | // Decode 57 | BlockAbstract newBlockAbstract = BlockAbstract.fromBytes(bytes); 58 | 59 | assertEquals(this.blockAbstract, newBlockAbstract); 60 | } 61 | 62 | /** 63 | * Test for {@link BlockAbstract#checkBlockHash}. 64 | */ 65 | @Test 66 | public void testCheckBlockHash_Valid() { 67 | assertTrue(this.blockAbstract.checkBlockHash(this.block)); 68 | } 69 | 70 | /** 71 | * Test for {@link BlockAbstract#checkSignature(byte[])}. 72 | */ 73 | @Test 74 | public void testCheckSignature_Valid() { 75 | assertTrue(this.blockAbstract.checkSignature(this.key.getPublicKey())); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/BlockTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import static org.junit.Assert.*; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import java.security.SignatureException; 7 | import java.util.ArrayList; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import nl.tudelft.blockchain.scaleoutdistributedledger.Application; 13 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 14 | 15 | /** 16 | * Test class for {@link Block}. 17 | */ 18 | public class BlockTest { 19 | 20 | private OwnNode owner; 21 | 22 | private Block block; 23 | 24 | private Application application; 25 | 26 | private LocalStore localStore; 27 | 28 | /** 29 | * Setup method. 30 | */ 31 | @Before 32 | public void setUp() { 33 | Ed25519Key keyPair = new Ed25519Key(); 34 | this.owner = new OwnNode(24); 35 | this.owner.setPrivateKey(keyPair.getPrivateKey()); 36 | this.owner.setPublicKey(keyPair.getPublicKey()); 37 | this.block = new Block(1234, this.owner, new ArrayList<>()); 38 | this.application = mock(Application.class); 39 | this.localStore = new LocalStore(this.owner, this.application, null, false); 40 | } 41 | 42 | /** 43 | * Test for {@link Block#getHash()}. 44 | */ 45 | @Test 46 | public void testGetHash_Valid() { 47 | String hash = "334777d018eb8d1acd2d04a3f26b973169920d1c81937241a2b24c0cf0b9b448"; 48 | 49 | assertTrue(this.block.getHash().toString().equals(hash)); 50 | } 51 | 52 | /** 53 | * Test for {@link Block#getHash()}. 54 | */ 55 | @Test 56 | public void testGetHash_Invalid() { 57 | String hash = "004777d018eb8d1acd2d04a3f26b973169920d1c81937241a2b24c0cf0b9b448"; 58 | 59 | assertFalse(this.block.getHash().toString().equals(hash)); 60 | } 61 | 62 | /** 63 | * Test for {@link Block#calculateBlockAbstract()}. 64 | * @throws java.security.SignatureException - fail to sign 65 | */ 66 | @Test 67 | public void testCalculateBlockAbstract_Valid() throws SignatureException { 68 | byte[] attrInBytes = BlockAbstract.calculateBytesForSignature(this.owner.getId(), this.block.getNumber(), this.block.getHash()); 69 | 70 | assertTrue(this.owner.verify(attrInBytes, this.block.calculateBlockAbstract().getSignature())); 71 | } 72 | 73 | /** 74 | * Test for {@link Block#calculateBlockAbstract()}. 75 | * @throws java.security.SignatureException - fail to sign 76 | */ 77 | @Test 78 | public void testCalculateBlockAbstract_Invalid() throws SignatureException { 79 | byte[] attrInBytes = BlockAbstract.calculateBytesForSignature(this.owner.getId() + 1, this.block.getNumber(), this.block.getHash()); 80 | 81 | assertFalse(this.owner.verify(attrInBytes, this.block.calculateBlockAbstract().getSignature())); 82 | } 83 | 84 | /** 85 | * Test for {@link Block#calculateBlockAbstract()}. 86 | */ 87 | @Test(expected = UnsupportedOperationException.class) 88 | public void testCalculateBlockAbstract_Invalid_NotOwnNode() { 89 | Block newBlock = new Block(1234, new Node(1), new ArrayList<>()); 90 | newBlock.calculateBlockAbstract(); 91 | } 92 | 93 | /** 94 | * Test for {@link Block#commit(LocalStore)}. 95 | */ 96 | @Test 97 | public void testCommit_Valid() { 98 | this.block.commit(this.localStore); 99 | 100 | assertEquals(this.block, this.owner.getChain().getLastCommittedBlock()); 101 | } 102 | 103 | /** 104 | * Test for {@link Block#commit(LocalStore)}. 105 | */ 106 | @Test(expected = IllegalStateException.class) 107 | public void testCommitTwice_Invalid() { 108 | this.block.commit(this.localStore); 109 | this.block.commit(this.localStore); 110 | } 111 | 112 | /** 113 | * Test for {@link Block#genesisCopy()}. 114 | */ 115 | @Test 116 | public void testGenesisCopy_Valid() { 117 | Block geneisBlock = new Block(Block.GENESIS_BLOCK_NUMBER, this.owner, new ArrayList<>()); 118 | Block newGenesisBlock = geneisBlock.genesisCopy(); 119 | 120 | assertEquals(geneisBlock, newGenesisBlock); 121 | } 122 | 123 | /** 124 | * Test for {@link Block#isOnMainChain(LocalStore) ()}. 125 | */ 126 | @Test 127 | public void testIsOnMainChain() { 128 | // Is true because TendermintMock returns always true 129 | assertTrue(this.block.isOnMainChain(this.localStore)); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/ChainTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import static org.mockito.Mockito.mock; 12 | 13 | /** 14 | * Test class for {@link Chain}. 15 | */ 16 | public class ChainTest { 17 | 18 | private OwnNode ownNode; 19 | 20 | private Node node; 21 | 22 | private Chain chain; 23 | 24 | private LocalStore localStore; 25 | 26 | /** 27 | * Setup method. 28 | */ 29 | @Before 30 | public void setUp() { 31 | this.ownNode = new OwnNode(0); 32 | this.localStore = new LocalStore(this.ownNode, null, null, false); 33 | this.node = mock(Node.class); 34 | this.chain = new Chain(this.node); 35 | } 36 | 37 | /** 38 | * Test for {@link Chainw#update()}. 39 | */ 40 | @Test 41 | public void testUpdate_EmptyUpdate() { 42 | List updateList = new ArrayList<>(); 43 | this.chain.update(updateList, localStore); 44 | 45 | assertTrue(this.chain.getBlocks().isEmpty()); 46 | } 47 | 48 | /** 49 | * Test for {@link Chainw#update()}. 50 | */ 51 | @Test 52 | public void testUpdate_EmptyChain() { 53 | List updateList = new ArrayList<>(); 54 | updateList.add(new Block(0, this.node, new ArrayList<>())); 55 | this.chain.update(updateList, localStore); 56 | 57 | assertEquals(updateList, this.chain.getBlocks()); 58 | } 59 | 60 | /** 61 | * Test for {@link Chainw#update()}. 62 | */ 63 | @Test 64 | public void testUpdate_NotEmptyChain() { 65 | List updateList = new ArrayList<>(); 66 | updateList.add(new Block(0, this.node, new ArrayList<>())); 67 | this.chain.update(updateList, localStore); 68 | updateList.add(new Block(1, this.node, new ArrayList<>())); 69 | updateList.add(new Block(2, this.node, new ArrayList<>())); 70 | this.chain.update(updateList, localStore); 71 | 72 | assertEquals(updateList, this.chain.getBlocks()); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/Ed25519KeyTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.security.InvalidKeyException; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.security.SignatureException; 6 | import static org.junit.Assert.*; 7 | 8 | import org.junit.Test; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 10 | import org.junit.Before; 11 | 12 | /** 13 | * Test class for {@link Ed25519Key}. 14 | */ 15 | public class Ed25519KeyTest { 16 | 17 | private Ed25519Key validKey; 18 | 19 | private Ed25519Key invalidKey; 20 | 21 | /** 22 | * Setup method. 23 | */ 24 | @Before 25 | public void setUp() { 26 | byte[] validPub = Utils.hexStringToBytes("BE8933DFF1600C026E34718F1785A4CDEAB90C35698B394E38B6947AE91DE116"); 27 | byte[] validPriv = Utils.hexStringToBytes("547AA07C7A8CE16C5CB2A40C6C26D15B0A32960410A9F1EA6E50B636F1AB389" 28 | + "ABE8933DFF1600C026E34718F1785A4CDEAB90C35698B394E38B6947AE91DE116"); 29 | this.validKey = new Ed25519Key(validPriv, validPub); 30 | 31 | byte[] invalidPriv = Utils.hexStringToBytes("0000A07C7A8CE16C5CB2A40C6C26D15B0A32960410A9F1EA6E50B636F1AB389" 32 | + "ABE8933DFF1600C026E34718F1785A4CDEAB90C35698B394E38B6947AE91DE116"); 33 | this.invalidKey = new Ed25519Key(invalidPriv, validPub); 34 | } 35 | 36 | /** 37 | * Test for {@link Ed25519Key#sign(byte[], byte[])}. 38 | */ 39 | @Test 40 | public void testSign_Valid() { 41 | byte[] message = "testing message".getBytes(); 42 | 43 | try { 44 | byte[] signature = Ed25519Key.sign(message, this.validKey.getPrivateKey()); 45 | boolean valid = Ed25519Key.verify(message, signature, this.validKey.getPublicKey()); 46 | 47 | assertTrue(valid); 48 | } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException ex) { 49 | fail(); 50 | } 51 | } 52 | 53 | /** 54 | * Test for {@link Ed25519Key#sign(byte[], byte[])}. 55 | */ 56 | @Test 57 | public void testSign_Invalid() { 58 | byte[] message = "testing message".getBytes(); 59 | 60 | try { 61 | byte[] signature = Ed25519Key.sign(message, this.invalidKey.getPrivateKey()); 62 | boolean valid = Ed25519Key.verify(message, signature, this.invalidKey.getPublicKey()); 63 | 64 | assertFalse(valid); 65 | } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException ex) { 66 | fail(); 67 | } 68 | } 69 | 70 | /** 71 | * Test for {@link Ed25519Key#generateKeys()}. 72 | */ 73 | @Test 74 | public void testGenerateKeys_Valid() { 75 | Ed25519Key keyPair = new Ed25519Key(); 76 | byte[] pub = keyPair.getPublicKey(); 77 | byte[] priv = keyPair.getPrivateKey(); 78 | byte[] message = "testing message".getBytes(); 79 | 80 | try { 81 | byte[] signature = Ed25519Key.sign(message, priv); 82 | boolean valid = Ed25519Key.verify(message, signature, pub); 83 | 84 | assertTrue(valid); 85 | } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException ex) { 86 | fail(); 87 | } 88 | } 89 | 90 | /** 91 | * Test for {@link Ed25519Key#sign()} and {@link Ed25519Key#verify()}. 92 | */ 93 | @Test 94 | public void testSignVerify_Valid() { 95 | Ed25519Key keyPair = new Ed25519Key(); 96 | byte[] message = "testing message".getBytes(); 97 | 98 | try { 99 | byte[] signature = keyPair.sign(message); 100 | boolean valid = keyPair.verify(message, signature); 101 | 102 | assertTrue(valid); 103 | } catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException ex) { 104 | fail(); 105 | } 106 | } 107 | 108 | /** 109 | * Test for {@link Ed25519Key#verify(byte[], byte[], byte[]) ()}. 110 | */ 111 | @Test 112 | public void testVerify_Invalid() { 113 | byte[] message = "testing message".getBytes(); 114 | byte[] signature = new byte[10]; 115 | 116 | try { 117 | Ed25519Key.verify(message, signature, this.validKey.getPublicKey()); 118 | 119 | fail(); 120 | } catch (SignatureException ex) { 121 | // Good 122 | } 123 | } 124 | 125 | /** 126 | * Test for {@link Ed25519Key#equals()}. 127 | */ 128 | @Test 129 | public void testEquals_Valid() { 130 | Ed25519Key keyPair = new Ed25519Key(); 131 | Ed25519Key newKeyPair = new Ed25519Key(keyPair.getPrivateKey(), keyPair.getPublicKey()); 132 | 133 | assertEquals(keyPair, newKeyPair); 134 | } 135 | 136 | /** 137 | * Test for {@link Ed25519Key#hashCode() ()}. 138 | */ 139 | @Test 140 | public void testHashCode() { 141 | assertNotEquals(this.validKey.hashCode(), this.invalidKey.hashCode()); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/NodeTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import java.util.ArrayList; 4 | import org.junit.Test; 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Class to test {@link Node}. 9 | */ 10 | public class NodeTest { 11 | 12 | /** 13 | * Test for {@link Node#updateMetaKnowledge(Proof) }. 14 | */ 15 | @Test 16 | public void testUpdateMetaKnowledge() { 17 | Ed25519Key key = new Ed25519Key(); 18 | Node node = new Node(1, key.getPublicKey(), "127.0.0.1", 1234); 19 | Node otherNode = new Node(2); 20 | Proof proof = new Proof(null); 21 | proof.addBlock(new Block(1, otherNode, new ArrayList<>())); 22 | proof.addBlock(new Block(2, otherNode, new ArrayList<>())); 23 | proof.addBlock(new Block(3, otherNode, new ArrayList<>())); 24 | node.updateMetaKnowledge(proof); 25 | 26 | assertEquals(3, node.getMetaKnowledge().get(otherNode.getId()).intValue()); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/Sha256HashTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertNotEquals; 6 | import org.junit.Test; 7 | 8 | /** 9 | * Class to test {@link Sha256Hash}. 10 | */ 11 | public class Sha256HashTest { 12 | 13 | /** 14 | * Test for {@link Sha256Hash#Sha256Hash(java.lang.String) }. 15 | */ 16 | @Test 17 | public void testConstuctorMessage() { 18 | Sha256Hash hash = new Sha256Hash("hello"); 19 | String expectedStr = "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"; 20 | 21 | assertEquals(expectedStr, hash.toString().toUpperCase()); 22 | } 23 | 24 | /** 25 | * Test for {@link Sha256Hash#Sha256Hash(byte[]) }. 26 | */ 27 | @Test 28 | public void testConstructorBytes() { 29 | Sha256Hash hash = new Sha256Hash(Utils.hexStringToBytes("ABBA")); 30 | 31 | assertNotEquals("ABBA", hash.toString()); 32 | } 33 | 34 | /** 35 | * Test for {@link Sha256Hash#withHash(byte[])}. 36 | */ 37 | @Test 38 | public void testWith() { 39 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("ABBA")); 40 | 41 | assertNotEquals("ABBA", hash.toString()); 42 | } 43 | 44 | /** 45 | * Test for {@link Sha256Hash#equals(java.lang.Object)}. 46 | */ 47 | @Test 48 | public void testEquals() { 49 | Sha256Hash hash = new Sha256Hash("hello"); 50 | Sha256Hash otherHash = new Sha256Hash("hello"); 51 | 52 | assertEquals(hash, otherHash); 53 | } 54 | 55 | /** 56 | * Test for {@link Sha256Hash#equals(java.lang.Object)}. 57 | */ 58 | @Test 59 | public void testHashCode() { 60 | Sha256Hash hash = new Sha256Hash("hello"); 61 | Sha256Hash otherHash = new Sha256Hash("hello2"); 62 | 63 | assertNotEquals(hash.hashCode(), otherHash.hashCode()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/mainchain/tendermint/ABCIClientTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.tendermint; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.BlockAbstract; 4 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Sha256Hash; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.test.utils.SilencedTestClass; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 7 | 8 | import org.json.JSONArray; 9 | import org.json.JSONObject; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.Assert.*; 16 | import static org.mockito.Mockito.*; 17 | 18 | /** 19 | * Test class for {@link ABCIClient}. 20 | * Tests cases that log warnings, so logging is silenced. 21 | */ 22 | public class ABCIClientTest extends SilencedTestClass { 23 | private ABCIClient instance; 24 | 25 | /** 26 | * Create a fresh client for each test. 27 | */ 28 | @Before 29 | public void setUp() { 30 | this.instance = spy(new ABCIClient("localhost:9998")); 31 | } 32 | 33 | /** 34 | * Test a successful commit. 35 | */ 36 | @Test 37 | public void testCommitSuccess() { 38 | String hash = "AAFF"; 39 | BlockAbstract abs = new BlockAbstract(0, 0, null, null); 40 | JSONObject json = new JSONObject(); 41 | json.put("result", json); 42 | json.put("deliver_tx", json); 43 | json.put("code", 0); 44 | json.put("hash", hash); 45 | 46 | doReturn(json).when(instance).sendRequest(anyString(), any()); 47 | assertArrayEquals(Utils.hexStringToBytes(hash), instance.commit(abs)); 48 | } 49 | 50 | /** 51 | * Test a commit that returns an error. 52 | */ 53 | @Test 54 | public void testCommitError() { 55 | BlockAbstract abs = new BlockAbstract(0, 0, null, null); 56 | JSONObject json = new JSONObject(); 57 | JSONObject jsonError = new JSONObject(); 58 | json.put("error", jsonError); 59 | jsonError.put("data", "An error message"); 60 | 61 | doReturn(json).when(instance).sendRequest(anyString(), any()); 62 | assertNull(instance.commit(abs)); 63 | } 64 | 65 | /** 66 | * Test a commit where the connection fails. 67 | */ 68 | @Test 69 | public void testCommitFail() { 70 | BlockAbstract abs = new BlockAbstract(0, 0, null, null); 71 | 72 | doReturn(null).when(instance).sendRequest(anyString(), any()); 73 | assertNull(instance.commit(abs)); 74 | } 75 | 76 | /** 77 | * Test a successful query. 78 | */ 79 | @Test 80 | public void testQuerySuccess() { 81 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF00")); 82 | JSONObject json = new JSONObject(); 83 | json.put("result", 42); 84 | 85 | doReturn(json).when(instance).sendRequest(anyString(), any()); 86 | assertTrue(instance.query(hash)); 87 | } 88 | 89 | /** 90 | * Test a query that returns an error. 91 | */ 92 | @Test 93 | public void testQueryError() { 94 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF00")); 95 | JSONObject json = new JSONObject(); 96 | json.put("error", 42); 97 | 98 | doReturn(json).when(instance).sendRequest(anyString(), any()); 99 | assertFalse(instance.query(hash)); 100 | } 101 | 102 | /** 103 | * Test a query on a failing connection. 104 | */ 105 | @Test 106 | public void testQueryFail() { 107 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF00")); 108 | 109 | doReturn(null).when(instance).sendRequest(anyString(), any()); 110 | assertFalse(instance.query(hash)); 111 | } 112 | 113 | /** 114 | * Test a successful height lookup. 115 | */ 116 | @Test 117 | public void testQueryHeightSuccess() { 118 | BlockAbstract abs1 = new BlockAbstract(0, 0, Sha256Hash.withHash(Utils.hexStringToBytes("11FF")), null); 119 | BlockAbstract abs2 = new BlockAbstract(0, 0, Sha256Hash.withHash(Utils.hexStringToBytes("AABB")), null); 120 | 121 | JSONObject json = new JSONObject(); 122 | JSONArray jsonArray = new JSONArray(); 123 | json.put("result", json); 124 | json.put("block", json); 125 | json.put("data", json); 126 | json.put("txs", jsonArray); 127 | 128 | jsonArray.put(Utils.bytesToBas64String(abs1.toBytes())); 129 | jsonArray.put(Utils.bytesToBas64String(abs2.toBytes())); 130 | 131 | doReturn(json).when(instance).sendRequest(anyString(), any()); 132 | 133 | List result = instance.query(10); 134 | assertEquals(2, result.size()); 135 | assertEquals(abs1.getBlockHash(), result.get(0).getBlockHash()); 136 | } 137 | 138 | /** 139 | * Test a height lookup that gives a malformed result. 140 | */ 141 | @Test 142 | public void testQueryHeightMalformed() { 143 | JSONObject json = new JSONObject(); 144 | json.put("malformed", 42); 145 | 146 | doReturn(json).when(instance).sendRequest(anyString(), any()); 147 | 148 | assertNull(instance.query(10)); 149 | } 150 | 151 | /** 152 | * Test a height lookup that results in an error. 153 | */ 154 | @Test 155 | public void testQueryHeightError() { 156 | JSONObject json = new JSONObject(); 157 | JSONObject jsonError = new JSONObject(); 158 | json.put("error", jsonError); 159 | jsonError.put("data", "An error message"); 160 | 161 | doReturn(json).when(instance).sendRequest(anyString(), any()); 162 | 163 | assertNull(instance.query(10)); 164 | } 165 | 166 | /** 167 | * Test a height lookup when the connection fails. 168 | */ 169 | @Test 170 | public void testQueryHeightFail() { 171 | doReturn(null).when(instance).sendRequest(anyString(), any()); 172 | assertNull(instance.query(10)); 173 | } 174 | 175 | /** 176 | * Test a successful status call. 177 | */ 178 | @Test 179 | public void testStatusSuccess() { 180 | JSONObject json = new JSONObject(); 181 | JSONObject jsonResult = new JSONObject(); 182 | json.put("result", jsonResult); 183 | 184 | doReturn(json).when(instance).sendRequest(anyString(), any()); 185 | 186 | assertEquals(jsonResult, instance.status()); 187 | } 188 | 189 | /** 190 | * Test a status call that results in a fail. 191 | */ 192 | @Test 193 | public void testStatusFail() { 194 | doReturn(null).when(instance).sendRequest(anyString(), any()); 195 | 196 | assertEquals(0, instance.status().length()); 197 | } 198 | 199 | } -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/model/mainchain/tendermint/TendermintChainTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.tendermint; 2 | 3 | import com.github.jtendermint.jabci.socket.TSocket; 4 | import nl.tudelft.blockchain.scaleoutdistributedledger.Application; 5 | import nl.tudelft.blockchain.scaleoutdistributedledger.LocalStore; 6 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.BlockAbstract; 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Sha256Hash; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.mainchain.MainChain; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.test.utils.SilencedTestClass; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Utils; 12 | import org.json.JSONObject; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.mockito.invocation.InvocationOnMock; 16 | import org.mockito.stubbing.Answer; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.Set; 22 | 23 | import static org.junit.Assert.*; 24 | import static org.mockito.Mockito.*; 25 | 26 | /** 27 | * Class testing the Tendermint implementation of the {@link MainChain} interface. 28 | * Tests cases that log warnings, so logging is silenced. 29 | */ 30 | public class TendermintChainTest extends SilencedTestClass { 31 | private TendermintChain instance; 32 | private ABCIClient clientMock; 33 | private TSocket socketMock; 34 | private Set cache; 35 | 36 | /** 37 | * Create a fresh instance for each test. 38 | */ 39 | @Before 40 | public void setUp() { 41 | clientMock = mock(ABCIClient.class); 42 | socketMock = mock(TSocket.class); 43 | cache = new HashSet<>(); 44 | Application appMock = mock(Application.class); 45 | LocalStore localStoreMock = mock(LocalStore.class); 46 | 47 | when(appMock.getLocalStore()).thenReturn(localStoreMock); 48 | when(localStoreMock.getOwnNode()).thenReturn(new OwnNode(0)); 49 | 50 | instance = new TendermintChain(clientMock, socketMock, cache, appMock); 51 | } 52 | 53 | /** 54 | * Test updating the cache. 55 | * Adds two blocks containing 3 abstracts and fails to trigger the loop. 56 | */ 57 | @Test 58 | public void testInitialUpdateCache() { 59 | when(clientMock.query(anyLong())).thenAnswer(new Answer>() { 60 | private int c; 61 | 62 | @Override 63 | public List answer(InvocationOnMock invocation) { 64 | List data = new ArrayList<>(); 65 | if (c == 0) { 66 | data.add(new BlockAbstract(0, 0, new Sha256Hash(Utils.hexStringToBytes("FF44")), null)); 67 | data.add(new BlockAbstract(0, 0, new Sha256Hash(Utils.hexStringToBytes("FF55")), null)); 68 | } else { 69 | data.add(new BlockAbstract(0, 0, new Sha256Hash(Utils.hexStringToBytes("FF66")), null)); 70 | } 71 | c++; 72 | return data; 73 | } 74 | }); 75 | 76 | when(clientMock.status()).thenAnswer(new Answer() { 77 | private JSONObject json; 78 | 79 | @Override 80 | public JSONObject answer(InvocationOnMock invocation) { 81 | 82 | if (json == null) { 83 | json = new JSONObject(); 84 | } else { 85 | json.put("latest_block_height", 2L); 86 | } 87 | return json; 88 | } 89 | }); 90 | 91 | instance.initialUpdateCache(); 92 | 93 | assertEquals(3, cache.size()); 94 | assertEquals(2, instance.getCurrentHeight()); 95 | } 96 | 97 | /** 98 | * Test if stopping stops the socket. 99 | */ 100 | @Test 101 | public void testStop() { 102 | instance.stop(); 103 | verify(socketMock, times(1)).stop(); 104 | } 105 | 106 | /** 107 | * Test if committing calls the client and if the hashes are set correctly. 108 | */ 109 | @Test 110 | public void testCommitAbstract() { 111 | BlockAbstract abs = new BlockAbstract(0, 0, Sha256Hash.withHash(Utils.hexStringToBytes("11FF")), null); 112 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF11")); 113 | 114 | when(clientMock.commit(any(BlockAbstract.class))).thenReturn(Utils.hexStringToBytes("FF11")); 115 | 116 | Sha256Hash result = instance.commitAbstract(abs); 117 | assertEquals(hash, result); 118 | assertEquals(hash, abs.getAbstractHash()); 119 | verify(clientMock, times(1)).commit(any(BlockAbstract.class)); 120 | } 121 | 122 | /** 123 | * Test the case where comitting fails. 124 | */ 125 | @Test 126 | public void testCommitAbstractFail() { 127 | BlockAbstract abs = new BlockAbstract(0, 0, Sha256Hash.withHash(Utils.hexStringToBytes("11FF")), null); 128 | when(clientMock.commit(any(BlockAbstract.class))).thenReturn(null); 129 | 130 | assertNull(instance.commitAbstract(abs)); 131 | } 132 | 133 | /** 134 | * Test isPresent when the data is in the cache. 135 | */ 136 | @Test 137 | public void testIsPresentAlreadyInCache() { 138 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF11")); 139 | cache.add(hash); 140 | 141 | assertTrue(instance.isPresent(hash)); 142 | } 143 | 144 | /** 145 | * Test isPresent when the data is not in the cache initially and an update does not fix that. 146 | */ 147 | @Test 148 | public void testIsPresentNotInCacheAfterUpdate() { 149 | Sha256Hash hash1 = Sha256Hash.withHash(Utils.hexStringToBytes("FF11")); 150 | Sha256Hash hash2 = Sha256Hash.withHash(Utils.hexStringToBytes("AAFF")); 151 | 152 | JSONObject json = new JSONObject(); 153 | json.put("latest_block_height", 1); 154 | when(clientMock.status()).thenReturn(json); 155 | List abss = new ArrayList<>(); 156 | abss.add(new BlockAbstract(0, 0, hash2, null)); 157 | when(clientMock.query(anyLong())).thenReturn(abss); 158 | 159 | assertFalse(instance.isPresent(hash1)); 160 | } 161 | 162 | /** 163 | * Test adding to the cache. 164 | */ 165 | @Test 166 | public void testAddToCache() { 167 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF22")); 168 | assertTrue(instance.addToCache(hash)); 169 | assertTrue(cache.contains(hash)); 170 | } 171 | 172 | /** 173 | * Test adding an existing item to the cache. 174 | */ 175 | @Test 176 | public void testAddToCacheFails() { 177 | Sha256Hash hash = Sha256Hash.withHash(Utils.hexStringToBytes("FF22")); 178 | cache.add(hash); 179 | 180 | assertFalse(instance.addToCache(hash)); 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/test/utils/SilencedTestClass.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.test.utils; 2 | 3 | import nl.tudelft.blockchain.scaleoutdistributedledger.utils.Log; 4 | import org.junit.AfterClass; 5 | import org.junit.BeforeClass; 6 | 7 | import java.util.logging.Level; 8 | 9 | public class SilencedTestClass { 10 | private static Level level; 11 | 12 | /** 13 | * Store the current log level (for restoring) and disable logging. 14 | */ 15 | @BeforeClass 16 | public static void setUpClass() { 17 | level = Log.getLogLevel(); 18 | Log.setLogLevel(Level.OFF); 19 | } 20 | 21 | /** 22 | * Re-enable the logging with the old level. 23 | */ 24 | @AfterClass 25 | public static void tearDownClass() throws Exception { 26 | Log.setLogLevel(level); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/test/utils/TestHelper.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.test.utils; 2 | 3 | import java.util.Map; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.IntStream; 6 | 7 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Block; 8 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Node; 9 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.OwnNode; 10 | import nl.tudelft.blockchain.scaleoutdistributedledger.model.Transaction; 11 | import nl.tudelft.blockchain.scaleoutdistributedledger.simulation.tendermint.TendermintHelper; 12 | 13 | /** 14 | * Helper class for tests. 15 | */ 16 | public final class TestHelper { 17 | private TestHelper() {} 18 | 19 | /** 20 | * @param ownNode - the own node 21 | * @param nrOfNodes - the number of nodes 22 | * @param amount - the amount each node gets 23 | * @return - the genesis block 24 | */ 25 | public static Block generateGenesis(OwnNode ownNode, int nrOfNodes, long amount) { 26 | Map nodeList = IntStream.rangeClosed(0, nrOfNodes) 27 | .boxed() 28 | .collect(Collectors.toMap(i -> i, Node::new)); 29 | nodeList.put(ownNode.getId(), ownNode); 30 | Block genesisBlock = TendermintHelper.generateGenesisBlock(amount, nodeList); 31 | 32 | for (Node node : nodeList.values()) { 33 | node.getChain().setGenesisBlock(genesisBlock); 34 | } 35 | return genesisBlock; 36 | } 37 | 38 | /** 39 | * @param genesisBlock - the genesis block 40 | * @return - the map of nodes 41 | */ 42 | public static Map getNodeList(Block genesisBlock) { 43 | return genesisBlock.getTransactions() 44 | .stream() 45 | .map(Transaction::getReceiver) 46 | .collect(Collectors.toMap(Node::getId, n -> n)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/nl/tudelft/blockchain/scaleoutdistributedledger/utils/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package nl.tudelft.blockchain.scaleoutdistributedledger.utils; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Test class for {@link Utils}. 9 | */ 10 | public class UtilsTest { 11 | /** 12 | * Test the byte array conversion without using the 0x prefix. 13 | */ 14 | @Test 15 | public void testHexStringToByteArray_WithoutPrefix_Uppercase() { 16 | String test = "091AFE"; 17 | assertArrayEquals(new byte[] {0x09, 0x1A, (byte) 0xFE}, Utils.hexStringToBytes(test)); 18 | } 19 | 20 | /** 21 | * Test the byte array conversion without using the 0x prefix. 22 | */ 23 | @Test 24 | public void testHexStringToByteArray_WithoutPrefix_Lowercase() { 25 | String test = "091afe"; 26 | assertArrayEquals(new byte[] {0x09, 0x1A, (byte) 0xFE}, Utils.hexStringToBytes(test)); 27 | } 28 | 29 | /** 30 | * Test the byte array conversion with an invalid input. 31 | */ 32 | @Test(expected = IllegalArgumentException.class) 33 | public void testHexStringToByteArray_Invalid() { 34 | String test = "00G0FF"; 35 | Utils.hexStringToBytes(test); 36 | } 37 | 38 | /** 39 | * Test the byte array conversion to a String. 40 | */ 41 | @Test 42 | public void testByteArrayToHexString() { 43 | byte[] array = new byte[]{0x00, 0x12, (byte) 0xFF}; 44 | assertEquals("0012ff", Utils.bytesToHexString(array)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/resources/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/src/test/resources/.gitignore -------------------------------------------------------------------------------- /tracker-server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /tracker-server/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb/base", 4 | "env": { 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "ecmaFeatures": { 10 | "classes": true, 11 | "modules": true 12 | }, 13 | "rules": { 14 | "comma-dangle": 0, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tracker-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .idea/* 3 | dist/* 4 | data.csv 5 | nodelist.json 6 | transactionlist.json -------------------------------------------------------------------------------- /tracker-server/app.js: -------------------------------------------------------------------------------- 1 | import bodyParser from 'body-parser'; 2 | import cookieParser from 'cookie-parser'; 3 | import Debug from 'debug'; 4 | import logger from 'morgan'; 5 | import express from 'express'; 6 | import http from 'http'; 7 | import index from './routes/index'; 8 | import NodeList from './model/NodeList'; 9 | import TransactionList from './model/TransactionList'; 10 | import path from "path"; 11 | const sseMW = require('./helpers/sse'); 12 | 13 | const app = express(); 14 | const debug = Debug('test:app'); 15 | 16 | app.use(sseMW.sseMiddleware); 17 | 18 | app.use(logger('dev')); 19 | app.use(bodyParser.json()); 20 | app.use(bodyParser.urlencoded({ 21 | extended: false 22 | })); 23 | app.set('views', path.join(__dirname, '../views')); 24 | app.set('view engine', 'ejs'); 25 | app.use(express.static(path.join(__dirname, '../public'))); 26 | 27 | const port = normalizePort(process.env.PORT || '3000'); 28 | app.set('port', port); 29 | const server = http.createServer(app); 30 | 31 | app.use(cookieParser()); 32 | 33 | app.nodeList = new NodeList(); 34 | app.transactionList = new TransactionList(); 35 | app.use('/', index); 36 | 37 | server.listen(port); 38 | server.on('listening', onListening); 39 | 40 | // catch 404 and forward to error handler 41 | app.use((req, res, next) => { 42 | const err = new Error('Not Found'); 43 | err.status = 404; 44 | next(err); 45 | }); 46 | 47 | // error handler 48 | /* eslint no-unused-vars: 0 */ 49 | app.use((err, req, res, next) => { 50 | console.log(err); 51 | // set locals, only providing error in development 52 | res.locals.message = err.message; 53 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 54 | // render the error page 55 | res.status(err.status || 500); 56 | res.json(err); 57 | }); 58 | 59 | // Handle uncaughtException 60 | process.on('uncaughtException', (err) => { 61 | debug('Caught exception: %j', err); 62 | process.exit(1); 63 | }); 64 | 65 | /** 66 | * Normalize a port into a number, string, or false. 67 | */ 68 | 69 | function normalizePort(val) { 70 | let port = parseInt(val, 10); 71 | 72 | if (isNaN(port)) { 73 | // named pipe 74 | return val; 75 | } 76 | 77 | if (port >= 0) { 78 | // port number 79 | return port; 80 | } 81 | 82 | return false; 83 | } 84 | 85 | /** 86 | * Event listener for HTTP server "listening" event. 87 | */ 88 | function onListening() { 89 | const addr = server.address(); 90 | const bind = typeof addr === 'string' 91 | ? 'pipe ' + addr 92 | : 'port ' + addr.port; 93 | console.log('Listening on ' + bind); 94 | } 95 | 96 | export default app; 97 | -------------------------------------------------------------------------------- /tracker-server/helpers/sse.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | console.log("loading sse.js"); 4 | 5 | // ... with this middleware: 6 | function sseMiddleware(req, res, next) { 7 | res.sseConnection = new Connection(res); 8 | next(); 9 | } 10 | exports.sseMiddleware = sseMiddleware; 11 | /** 12 | * A Connection is a simple SSE manager for 1 client. 13 | */ 14 | var Connection = (function () { 15 | function Connection(res) { 16 | this.res = res; 17 | } 18 | Connection.prototype.setup = function () { 19 | this.res.writeHead(200, { 20 | 'Content-Type': 'text/event-stream', 21 | 'Cache-Control': 'no-cache', 22 | 'Connection': 'keep-alive' 23 | }); 24 | }; 25 | Connection.prototype.send = function (data) { 26 | this.res.write("data: " + JSON.stringify(data) + "\n\n"); 27 | }; 28 | return Connection; 29 | }()); 30 | 31 | exports.Connection = Connection; 32 | /** 33 | * A Topic handles a bundle of connections with cleanup after lost connection. 34 | */ 35 | var Topic = (function () { 36 | function Topic() { 37 | this.connections = []; 38 | } 39 | Topic.prototype.add = function (conn) { 40 | const connections = this.connections; 41 | connections.push(conn); 42 | console.log('New client connected, now: ', connections.length); 43 | conn.res.on('close', function () { 44 | const i = connections.indexOf(conn); 45 | if (i >= 0) { 46 | connections.splice(i, 1); 47 | } 48 | console.log('Client disconnected, now: ', connections.length); 49 | }); 50 | }; 51 | Topic.prototype.forEach = function (cb) { 52 | this.connections.forEach(cb); 53 | }; 54 | return Topic; 55 | }()); 56 | exports.Topic = Topic; -------------------------------------------------------------------------------- /tracker-server/model/Node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to store Node information. 3 | */ 4 | class Node { 5 | 6 | /** 7 | * Constructor. 8 | * @param id - the id of this node. 9 | * @param address - the address of this node. 10 | * @param port - the port of this node. 11 | */ 12 | constructor(id, address, port, publicKey) { 13 | this.id = id; 14 | this.address = address; 15 | this.port = port; 16 | this.publicKey = publicKey; 17 | this.lastSeen = new Date(); 18 | this.running = false; 19 | } 20 | } 21 | 22 | module.exports = Node; 23 | -------------------------------------------------------------------------------- /tracker-server/model/NodeList.js: -------------------------------------------------------------------------------- 1 | import Node from './Node'; 2 | 3 | /** 4 | * Class to store a list of nodes. 5 | */ 6 | class NodeList { 7 | 8 | /** 9 | * Constructor. 10 | */ 11 | constructor() { 12 | this.nodes = []; 13 | } 14 | 15 | /** 16 | * 17 | * Adds or updates a node to/in the nodelist. 18 | * @param id - id of the node to update 19 | * @param address - new address 20 | * @param port - new port 21 | * @param publicKey - new public key 22 | * @returns {boolean} - whether the id was valid. 23 | */ 24 | updateNode(id, address, port, publicKey) { 25 | if(id < this.nodes.length) { 26 | if (!!this.nodes[id]) return false; 27 | this.nodes[id].address = address; 28 | this.nodes[id].port = port; 29 | this.nodes[id].publicKey = publicKey; 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | /** 36 | * Register a new node. 37 | * @param id - the id of the new node 38 | * @param address - the address of the new node 39 | * @param port - the port of the new node 40 | * @param publicKey - the public key of the new node 41 | * @returns {number} - the id of the new node 42 | */ 43 | registerNode(id, address, port, publicKey) { 44 | this.nodes[id] = new Node(id, address, port, publicKey) 45 | return id; 46 | } 47 | 48 | /** 49 | * Set the status of the node. 50 | * @param id - the id of the node that is marked 51 | * @param running - the new status of the node 52 | * @returns {boolean} - whether the id was valid. 53 | */ 54 | setNodeStatus(id, running) { 55 | if(id < this.nodes.length) { 56 | this.nodes[id].running = running; 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | /** 63 | * Get the number of nodes that are running. 64 | * @returns {int} - the number of nodes that is initialized 65 | */ 66 | getRunning() { 67 | let count = 0; 68 | for (let i = 0; i < this.nodes.length; ++i) { 69 | if (!!this.nodes[i] && this.nodes[i].running) { 70 | count ++; 71 | } 72 | } 73 | return count 74 | } 75 | 76 | /** 77 | * Gets the node at the specified id, if present. 78 | * @param id - the id of the node to get. 79 | * @returns {Node} - the node at the specified id. 80 | */ 81 | getNode(id) { 82 | if(id < this.nodes.length) { 83 | return this.nodes[id]; 84 | } 85 | return null; 86 | } 87 | 88 | /** 89 | * Get the full list of nodes. 90 | * @returns {Array} - the list of nodes. 91 | */ 92 | getNodes() { 93 | return this.nodes; 94 | } 95 | 96 | /** Get the number of registered nodes. 97 | * @returns {number} - the number of registered nodes on the server 98 | */ 99 | getSize() { 100 | if (this.nodes) { 101 | let count = 0; 102 | for (let i = 0; i < this.nodes.length; ++i) { 103 | if (this.nodes[i]) { 104 | count ++; 105 | } 106 | } 107 | return count; 108 | } else { 109 | return 0; 110 | } 111 | } 112 | 113 | getGraphNodes() { 114 | const nodes = []; 115 | this.nodes.forEach(node => { 116 | nodes.push({id: node.id, label: node.id.toString()}); 117 | }); 118 | return nodes; 119 | } 120 | } 121 | 122 | module.exports = NodeList; 123 | -------------------------------------------------------------------------------- /tracker-server/model/Transaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to store transaction information. 3 | */ 4 | class Transaction { 5 | 6 | /** 7 | * Constructor. 8 | * @param from - id of sender node 9 | * @param to - id of receiver node 10 | * @param amount - amount of transaction 11 | * @param remainder - remainder of transaction 12 | * @param numberOfChains - number of chains sent in proof 13 | * @param numberOfBlocks - number of blocks sent in proof 14 | */ 15 | constructor(from, to, amount, remainder, numberOfChains, numberOfBlocks) { 16 | this.from = from; 17 | this.to = to; 18 | this.amount = amount; 19 | this.remainder = remainder; 20 | this.numberOfChains = numberOfChains; 21 | this.numberOfBlocks = numberOfBlocks; 22 | } 23 | } 24 | 25 | module.exports = Transaction; -------------------------------------------------------------------------------- /tracker-server/model/TransactionList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class to store a list of all transactions. 3 | */ 4 | class TransactionList { 5 | 6 | /** 7 | * Constructor. 8 | */ 9 | constructor() { 10 | // Store transactions in a dictionary 11 | this.transactions = {}; 12 | this.numberOfTransactions = 0; 13 | this.numberOfChains = 0; 14 | this.numberOfBlocks = 0; 15 | this.numbersArray = []; 16 | } 17 | 18 | /** 19 | * Add a transaction to the transaction list. 20 | * @param transaction - the transaction to add 21 | */ 22 | addTransaction(transaction) { 23 | this.numberOfTransactions += 1; 24 | this.numberOfChains += transaction.numberOfChains; 25 | this.numberOfBlocks += transaction.numberOfBlocks; 26 | 27 | // Make sure we use a consistent key for all transactions between the same two nodes 28 | let key = [transaction.from, transaction.to]; 29 | if(transaction.from > transaction.to) 30 | key = [transaction.to, transaction.from]; 31 | 32 | if(!this.transactions[key]) 33 | this.transactions[key] = [transaction]; 34 | else 35 | this.transactions[key].push(transaction); 36 | } 37 | 38 | /** 39 | * Gets the edge weight between two nodes. 40 | * @param node1 - the first node 41 | * @param node2 - the second node 42 | * @returns {number} 43 | */ 44 | getEdgeWeight(node1, node2) { 45 | let key = [node1, node2]; 46 | if(node1 > node2) { 47 | key = [node2, node1]; 48 | } 49 | return this.getEdgeWeightWithKey(key); 50 | } 51 | 52 | /** 53 | * Gets the edge weight for a certain key. 54 | * @param key - the key 55 | * @returns {number} 56 | */ 57 | getEdgeWeightWithKey(key) { 58 | if(!this.transactions[key]) return 0; 59 | 60 | // TODO: something other than number of transactions? 61 | return this.transactions[key].length; 62 | } 63 | 64 | /** 65 | * Returns parsed edges for the graph. 66 | * @returns {Array} 67 | */ 68 | getGraphEdges() { 69 | const edges = []; 70 | for (const key of Object.keys(this.transactions)) { 71 | const keyArray = key.split(","); 72 | const weight = this.getEdgeWeightWithKey(key); 73 | edges.push({from: keyArray[0], to: keyArray[1], value: weight}); 74 | } 75 | return edges; 76 | } 77 | 78 | /** 79 | * Returns a JSON object containing some interesting number. 80 | * @returns {{numberOfTransactions: number, averageNumberOfBlocks: number, averageNumberOfChains: number}} 81 | */ 82 | getNumbers() { 83 | let averageNumberOfChains = 0, 84 | averageNumberOfBlocks = 0; 85 | if(this.numberOfTransactions !== 0) { 86 | averageNumberOfBlocks = this.numberOfBlocks / this.numberOfTransactions; 87 | averageNumberOfChains = this.numberOfChains / this.numberOfTransactions; 88 | } 89 | return { 90 | numberOfTransactions: this.numberOfTransactions, 91 | averageNumberOfBlocks: averageNumberOfBlocks, 92 | averageNumberOfChains: averageNumberOfChains 93 | }; 94 | } 95 | 96 | /** 97 | * Adds numbers data point to the array. 98 | */ 99 | addNumbersToArray() { 100 | const numbers = this.getNumbers(); 101 | if(numbers.numberOfTransactions !== 0 || numbers.averageNumberOfBlocks !== 0 || numbers.averageNumberOfChains !== 0) { 102 | this.numbersArray.push(numbers); 103 | } 104 | } 105 | 106 | /** 107 | * Gets the numbers array. 108 | * @returns {Array} 109 | */ 110 | getNumbersArray() { 111 | return this.numbersArray; 112 | } 113 | } 114 | 115 | module.exports = TransactionList; -------------------------------------------------------------------------------- /tracker-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tracker-server", 3 | "version": "1.0.0", 4 | "description": "Tracker server for scale-out blockchain project", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "rimraf dist/ && babel ./ --out-dir dist/ --ignore ./node_modules,./.babelrc,./package.json,./npm-debug.log,./public,./views--copy-files", 9 | "start": "npm run build && node dist/app.js" 10 | }, 11 | "author": "Bart de Jonge", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-cli": "^6.26.0", 15 | "babel-core": "^6.26.0", 16 | "babel-preset-es2015": "^6.24.1", 17 | "rimraf": "^2.6.2" 18 | }, 19 | "dependencies": { 20 | "body-parser": "latest", 21 | "cookie-parser": "^1.4.3", 22 | "csv-writer": "0.0.3", 23 | "debug": "latest", 24 | "ejs": "^2.5.7", 25 | "express": "^4.16.2", 26 | "fs": "0.0.1-security", 27 | "jsnetworkx": "^0.3.4", 28 | "morgan": "^1.9.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tracker-server/public/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/tracker-server/public/images/background.png -------------------------------------------------------------------------------- /tracker-server/public/images/background.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blockchain-lab/ScaleOutDistributedLedger/1bb03b7eeb20b609033cc5a7797df654cb8ce061/tracker-server/public/images/background.psd -------------------------------------------------------------------------------- /tracker-server/public/javascripts/demo.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | var source = new EventSource("../topn/updates"); 3 | source.onmessage = function(event) { 4 | var data = JSON.parse(event.data); 5 | // $(".odometer").text(counter); 6 | network.setData({nodes: data.nodes, edges: data.edges}); 7 | network.redraw(); 8 | $(".transactions").text(data.numbers.numberOfTransactions); 9 | $(".chains").text(Math.round(data.numbers.averageNumberOfChains * 100) / 100); 10 | $(".blocks").text(Math.round(data.numbers.averageNumberOfBlocks * 100) / 100); 11 | }; 12 | 13 | window.odometerOptions = { 14 | auto: false, 15 | format: '(,ddd).ddd', // Change how digit groups are formatted, and how many digits are shown after the decimal point 16 | duration: 100, 17 | animation: 'count' 18 | }; 19 | 20 | var nodes = []; 21 | var edges = []; 22 | var network = null; 23 | 24 | function draw() { 25 | 26 | // Instantiate our network object. 27 | var container = document.getElementById('demo-graph'); 28 | var data = { 29 | nodes: nodes, 30 | edges: edges 31 | }; 32 | var options = { 33 | nodes: { 34 | shape: 'dot' 35 | }, 36 | edges: { 37 | color: { 38 | opacity: 0.5 39 | }, 40 | smooth: { 41 | type: 'discrete' 42 | } 43 | }, 44 | layout: { 45 | randomSeed: 2 46 | }, 47 | physics: { 48 | barnesHut: { 49 | springLength: 1000 50 | } 51 | }, 52 | configure: { 53 | filter:function (option, path) { 54 | if (path.indexOf('physics') !== -1) { 55 | return true; 56 | } 57 | if (path.indexOf('smooth') !== -1 || option === 'smooth') { 58 | return true; 59 | } 60 | if (path.indexOf('color') !== -1 || option === 'color') { 61 | return true; 62 | } 63 | if (path.indexOf('nodes') !== -1) { 64 | return true; 65 | } 66 | return false; 67 | }, 68 | container: document.getElementById('config') 69 | } 70 | }; 71 | network = new vis.Network(container, data, options); 72 | } 73 | draw(); 74 | 75 | var counter = 12; 76 | 77 | $("#saveButton").on("click", function() { 78 | $.ajax({ 79 | method: 'POST', 80 | url: '/write-data' 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /tracker-server/public/stylesheets/demo.css: -------------------------------------------------------------------------------- 1 | #demo-graph { 2 | width: 1000px; 3 | height: 600px; 4 | display: block; 5 | border: 2px solid black; 6 | margin: 30px auto auto; 7 | } 8 | 9 | .odometer { 10 | font-size: 50px; 11 | margin: auto; 12 | } 13 | 14 | .odometer_wrapper { 15 | text-align: center; 16 | } 17 | 18 | body { 19 | text-align: center; 20 | } 21 | 22 | .numbers_table { 23 | margin: auto; 24 | } 25 | 26 | .numbers_table tr.labels { 27 | font-size: 30px; 28 | font-weight: bold; 29 | } 30 | 31 | .numbers_table tr td { 32 | padding: 10px 20px; 33 | } 34 | 35 | h1 { 36 | font-size: 45px; 37 | } 38 | 39 | html { 40 | background: rgba(0, 0, 0, 0.1) url(/images/background.png) no-repeat fixed center center; 41 | -webkit-background-size: cover; 42 | -moz-background-size: cover; 43 | -o-background-size: cover; 44 | background-size: cover; 45 | } 46 | -------------------------------------------------------------------------------- /tracker-server/routes/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import app from '../app'; 4 | import NodeList from '../model/NodeList'; 5 | const sseMW = require('./../helpers/sse'); 6 | const fs = require('fs'); 7 | import Transaction from '../model/Transaction'; 8 | import TransactionList from '../model/TransactionList'; 9 | 10 | /** 11 | * Gets all nodes. 12 | */ 13 | router.get('/', (req, res) => { 14 | res.json({ 15 | nodes: app.nodeList.getNodes() 16 | }); 17 | }); 18 | 19 | /** 20 | * Updates a certain node. Body should contain id, address, port and publicKey. 21 | */ 22 | router.post('/update-node', (req, res) => { 23 | if(!isPresent(req.body.id) || !isPresent(req.body.address) || !isPresent(req.body.port) || !isPresent(req.body.publicKey)) { 24 | res.status(403); 25 | res.json({success: false, err: 'Specify id, address, port and publicKey'}); 26 | } else { 27 | if (app.nodeList.updateNode(req.body.id, req.body.address, req.body.port, req.body.publicKey)) { 28 | res.json({success: true}); 29 | } else { 30 | res.status(403); 31 | res.json({success: false, err: 'invalid node'}); 32 | } 33 | } 34 | }); 35 | 36 | /** 37 | * Register a new node, body should contain id, address, port and publicKey. 38 | */ 39 | router.post('/register-node', (req, res) => { 40 | if(!isPresent(req.body.address) || !isPresent(req.body.port) || !isPresent(req.body.publicKey) || !isPresent(req.body.id)) { 41 | res.status(403); 42 | res.json({success: false, err: 'Specify id, address, port and publicKey'}); 43 | } else { 44 | const id = app.nodeList.registerNode(req.body.id, req.body.address, req.body.port, req.body.publicKey); 45 | updateSseClients(); 46 | res.json({success: true, id: id}); 47 | } 48 | }); 49 | 50 | router.post('/register-transaction', (req, res) => { 51 | if(!isPresent(req.body.from) || !isPresent(req.body.to) || !isPresent(req.body.amount)|| !isPresent(req.body.remainder)|| !isPresent(req.body.numberOfChains) || !isPresent(req.body.numberOfBlocks)) { 52 | res.status(403); 53 | res.json({success: false, err: 'Specify from, to, amount, remainder, remainder, numberOfChains and numberOfBlocks'}); 54 | } else { 55 | const tx = new Transaction(req.body.from, req.body.to, req.body.amount, req.body.remainder, req.body.numberOfChains, req.body.numberOfBlocks); 56 | app.transactionList.addTransaction(tx); 57 | updateSseClients(); 58 | res.json({success: true}); 59 | } 60 | }); 61 | 62 | router.post('/register-transactions', (req, res) => { 63 | if(!isPresent(req.body.transactions)) { 64 | res.status(403); 65 | res.json({success: false, err: 'Specify array of transactions'}); 66 | } else { 67 | req.body.transactions.forEach(tx => { 68 | app.transactionList.addTransaction(new Transaction(tx.from, tx.to, tx.amount, tx.remainder, tx.numberOfChains, tx.numberOfBlocks)); 69 | }); 70 | updateSseClients(); 71 | res.json({success: true}); 72 | } 73 | }); 74 | 75 | /** 76 | * Update the running status of a node. 77 | */ 78 | router.post('/set-node-status', (req, res) => { 79 | if(!isPresent(req.body.id) || !isPresent(req.body.running)) { 80 | res.status(403); 81 | res.json({success: false, err: 'Specify node ID and running status'}); 82 | } else { 83 | app.nodeList.setNodeStatus(req.body.id, req.body.running); 84 | res.json({success: true, id: req.body.id}); 85 | } 86 | }); 87 | 88 | /** 89 | * Gets a specific node. Getter body should contain id. 90 | */ 91 | router.get('/node', (req, res) => { 92 | if(!isPresent(req.body.id)) { 93 | res.status(403); 94 | res.json({success: false, err: 'Specify id'}); 95 | } else { 96 | const node = app.nodeList.getNode(req.body.id); 97 | if (node == null) { 98 | res.status(403); 99 | res.json({success: false, err: 'invalid id or uninitialized node'}); 100 | } else { 101 | res.json({success: true, node: node}); 102 | } 103 | } 104 | }); 105 | 106 | router.get('/demo', (req, res) => { 107 | res.render('demo'); 108 | }); 109 | 110 | /** 111 | * Reset the nodelist on the tracker server. 112 | */ 113 | router.post('/reset', (req, res) => { 114 | app.nodeList = new NodeList(); 115 | app.transactionList = new TransactionList(); 116 | res.json({success: true}); 117 | initDataHeartbeat(); 118 | }); 119 | 120 | /** 121 | * Get the number of currently registered nodes and currently running nodes on the tracker server. 122 | */ 123 | router.get('/status', (req, res) => { 124 | res.json({registered: app.nodeList.getSize(), running: app.nodeList.getRunning()}); 125 | }); 126 | 127 | function isPresent(arg) { 128 | return !!(arg || arg === 0 || arg === "" || arg === false); 129 | } 130 | 131 | /////////////////////////// TOPN stuff ////////////////////////// 132 | 133 | const sseClients = new sseMW.Topic(); 134 | // initial registration of SSE Client Connection 135 | router.get('/topn/updates', function(req,res){ 136 | const sseConnection = res.sseConnection; 137 | sseConnection.setup(); 138 | sseClients.add(sseConnection); 139 | } ); 140 | 141 | /** 142 | * send message to all registered SSE clients 143 | */ 144 | function updateSseClients() { 145 | const nodes = app.nodeList.getGraphNodes(); 146 | const edges = app.transactionList.getGraphEdges(); 147 | sseClients.forEach(sseConnection => sseConnection.send( 148 | {nodes: nodes, edges: edges, numbers: app.transactionList.getNumbers()})); 149 | } 150 | 151 | function initDataHeartbeat() { 152 | setInterval(() => { 153 | app.transactionList.addNumbersToArray(); 154 | }, 5000); 155 | } 156 | 157 | /** 158 | * Write all data. 159 | */ 160 | router.post('/write-data', (req, res) => { 161 | const createCsvWriter = require('csv-writer').createObjectCsvWriter; 162 | const csvWriter = createCsvWriter({ 163 | path: './data.csv', 164 | header: [ 165 | {id: 'numberOfTransactions', title:'numberOfTransactions'}, 166 | {id: 'averageNumberOfBlocks', title:'averageNumberOfBlocks'}, 167 | {id: 'averageNumberOfChains', title:'averageNumberOfChains'} 168 | ] 169 | }); 170 | fs.writeFile('./nodelist.json', JSON.stringify(app.nodeList, null, 2) , 'utf-8'); 171 | fs.writeFile('./transactionlist.json', JSON.stringify(app.transactionList, null, 2) , 'utf-8'); 172 | csvWriter.writeRecords(app.transactionList.getNumbersArray()).then(() => { 173 | console.log("Writing to csv done!"); 174 | res.json(app.transactionList.getNumbersArray()); 175 | }); 176 | }); 177 | 178 | /** 179 | * send a heartbeat signal to all SSE clients, once every interval seconds (or every 3 seconds if no interval is specified) 180 | * @param interval - interval in seconds 181 | */ 182 | function initHeartbeat(interval) { 183 | setInterval(() => { 184 | const msg = {"label":"The latest", "time":new Date()}; 185 | updateSseClients( JSON.stringify(msg)); 186 | }, interval?interval*1000:3000); 187 | } 188 | // initialize heartbeat at 10 second interval 189 | initHeartbeat(10); 190 | 191 | export default router; 192 | -------------------------------------------------------------------------------- /tracker-server/views/demo.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 | 21 | 22 | 23 |

Scale-Out Distributed Ledger

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
Total #transactionsAverage #chains per proofAverage #blocks per proof
36 | 37 |
38 |
39 |
40 | 41 | 42 | 43 | 46 |
--------------------------------------------------------------------------------