diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..34beed15 Binary files /dev/null and b/.DS_Store differ diff --git a/src/main/java/jabs/Main.java b/src/main/java/jabs/Main.java index c40d243a..46face82 100644 --- a/src/main/java/jabs/Main.java +++ b/src/main/java/jabs/Main.java @@ -2,10 +2,7 @@ import jabs.ledgerdata.bitcoin.BitcoinBlockWithoutTx; import jabs.log.*; -import jabs.scenario.AbstractScenario; -import jabs.scenario.BitcoinGlobalNetworkScenario; -import jabs.scenario.NormalEthereumNetworkScenario; -import jabs.scenario.PBFTLANScenario; +import jabs.scenario.*; import java.io.IOException; import java.nio.file.Paths; @@ -20,7 +17,7 @@ public class Main { */ public static void main(String[] args) throws IOException { AbstractScenario scenario; - +/* // Simulate one day in the life of Bitcoin network // Nakamoto protocol with block every 600 seconds // Around 8000 nodes with 30 miners @@ -53,5 +50,11 @@ public static void main(String[] args) throws IOException { 40, 3600); scenario.AddNewLogger(new PBFTCSVLogger(Paths.get("output/pbft-simulation-log.csv"))); scenario.run(); +*/ + // Simulate Snow LAN network of 40 nodes for 1 hour + scenario = new SnowLANScenario("One hour of a Snow lan Network", 1, 40, + 3600); + scenario.AddNewLogger(new SnowCSVLogger(Paths.get("output/snow-simulation-log.csv"))); + scenario.run(); } } \ No newline at end of file diff --git a/src/main/java/jabs/consensus/algorithm/QueryingBasedConsensus.java b/src/main/java/jabs/consensus/algorithm/QueryingBasedConsensus.java new file mode 100644 index 00000000..abb64fa9 --- /dev/null +++ b/src/main/java/jabs/consensus/algorithm/QueryingBasedConsensus.java @@ -0,0 +1,9 @@ +package jabs.consensus.algorithm; + +import jabs.ledgerdata.Block; +import jabs.ledgerdata.Tx; +import jabs.ledgerdata.Query; + +public interface QueryingBasedConsensus , T extends Tx> extends ConsensusAlgorithm{ + void newIncomingQuery(Query query); +} diff --git a/src/main/java/jabs/consensus/algorithm/Snow.java b/src/main/java/jabs/consensus/algorithm/Snow.java new file mode 100644 index 00000000..956627c1 --- /dev/null +++ b/src/main/java/jabs/consensus/algorithm/Snow.java @@ -0,0 +1,302 @@ +package jabs.consensus.algorithm; + +import jabs.consensus.blockchain.LocalBlockTree; +import jabs.ledgerdata.BlockFactory; +import jabs.ledgerdata.SingleParentBlock; +import jabs.ledgerdata.Tx; +import jabs.ledgerdata.Query; +import jabs.ledgerdata.snow.*; +import jabs.network.message.QueryMessage; +import jabs.network.node.nodes.Node; +import jabs.network.node.nodes.snow.SnowNode; +import jabs.simulator.randengine.RandomnessEngine; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +/** + * File: Snow.java + * Description: Implements SnowBall protocol for JABS blockchain simulator. + * Author: Siamak Abdi + * Date: August 22, 2023 + */ + +public class Snow, T extends Tx> extends AbstractChainBasedConsensus + implements QueryingBasedConsensus, DeterministicFinalityConsensus { + + static { + try { + File file = new File("output/snow-events-log.txt"); + if (file.exists()) { + file.delete(); + } + writer = new PrintWriter(new FileWriter(file, true)); + } catch (Exception e) { + e.printStackTrace(); + } + } + //*------------------------------------------------------------------------------------------------------- + // settings for the Snow protocols + public static int k = 2; // sample size + private int alpha = k/2; // quorum size or threshold + private int beta = 5; // conviction threshold + private double mean = 0.5; // Mean of the normal distribution + private double stdDev = 0.1; // Standard deviation of the normal distribution + //*------------------------------------------------------------------------------------------------------- + private static PrintWriter writer; + private final int numAllParticipants; + private static int numReadyNodes; + private static boolean readyTransfer; + private final HashMap> commitQueries = new HashMap<>(); + private final static HashSet committedNodes = new HashSet<>(); + public static int numConsensus; + private int numReceivedSamples; + private int conviction; + public boolean consensus; + private SnowPhase snowPhase = SnowPhase.PREPARING; + private SnowMode snowMode = SnowMode.NORMAL; + @Override + public boolean isBlockFinalized(B block) { + return false; + } + + @Override + public boolean isTxFinalized(T tx) { + return false; + } + + @Override + public int getNumOfFinalizedBlocks() { + return 0; + } + + @Override + public int getNumOfFinalizedTxs() { + return 0; + } + + public enum SnowPhase { + PREPARING, + COMMITTING + } + private enum SnowMode{ + NORMAL, + TRANSFERRING + } + + public Snow(LocalBlockTree localBlockTree, int numAllParticipants) { + super(localBlockTree); + this.numAllParticipants = numAllParticipants; + this.currentMainChainHead = localBlockTree.getGenesisBlock(); + } + + public void newIncomingQuery(Query query) { + if (query instanceof SnowBlockQuery) { + SnowBlockQuery blockQuery = (SnowBlockQuery) query; + B block = blockQuery.getBlock(); + SnowNode peer = (SnowNode) this.peerBlockchainNode; + if(peer.currentBlock.getHeight() < block.getHeight()){ // If the node has not yet voted for the new block + double sample = generateNormalSample(this.peerBlockchainNode.getNetwork().getRandom(), mean, stdDev); // decides whether to vote for the new block or not + peer.currentBlock = sample >= 0.5 ? (SnowBlock) block : peer.currentBlock; + } + SnowNode inquirer = (SnowNode) blockQuery.getInquirer(); + switch (blockQuery.getQueryType()) { + case PREPARE : + if (!this.localBlockTree.contains(block)) { + this.localBlockTree.add(block); + } + if (this.localBlockTree.getLocalBlock(block).isConnectedToGenesis) { + this.snowPhase = SnowPhase.COMMITTING; + this.peerBlockchainNode.respondQuery( + new QueryMessage( + new SnowCommitQuery<>(this.peerBlockchainNode, peer.currentBlock) + ), inquirer + ); + } + break; + case COMMIT: + checkQueries(blockQuery, (B) peer.currentBlock, this.commitQueries, SnowPhase.PREPARING); + break; + } + } + } + + private void checkQueries(SnowBlockQuery query, B block, HashMap> queries, Snow.SnowPhase nextStep) { + this.snowPhase = nextStep; + this.numReceivedSamples++; + List sampledNeighbors = sampleNeighbors(this.peerBlockchainNode.getNetwork().getRandom(), k); + if (!queries.containsKey(block)) { // the first query reply received for the block + queries.put(block, new HashMap<>()); + } + queries.get(block).put(query.getInquirer(), query); + if (numReceivedSamples == k) { // if the node has received all the replies + this.numReceivedSamples = 0; + if(this.snowMode == SnowMode.TRANSFERRING){ + if(!readyTransfer){ + this.snowMode = SnowMode.NORMAL; + } + queries.clear(); + continueSampling(nextStep, sampledNeighbors, block); // Just to participate in the process + }else if(this.snowMode == SnowMode.NORMAL){ + if(committedNodes.contains(this.peerBlockchainNode)){ // if the node has reached consensus (beta threshold) + queries.clear(); + if(!this.consensus){ + checkConsensus(block); + } + if(!readyTransfer){ + continueSampling(nextStep, sampledNeighbors, block); // Just to participate in the process + }else { + startGenerateNewBlock(nextStep, sampledNeighbors, block); + } + } else if ((queries.get(block).size() > alpha) && (block.getHeight()>this.currentMainChainHead.getHeight())) { // If a successful query for the block reaches the alpha value + this.conviction++; + queries.clear(); + if((this.conviction==beta)){ // if the node's conviction value reaches the beta value (local consensus) + committedNodes.add(this.peerBlockchainNode); + this.conviction = 0; + if(!this.consensus){ + checkConsensus(block); + } + } + continueSampling(nextStep, sampledNeighbors, block); // Just to participate in the process + } else { + queries.clear(); + this.conviction = 0; + continueSampling(nextStep, sampledNeighbors, block); // Just to participate in the process + } + } + } + } + + private void startGenerateNewBlock(Snow.SnowPhase nextStep, List sampledNeighbors, B block) { + //System.out.println("node "+this.peerBlockchainNode.nodeID+" started"+ " at "+this.peerBlockchainNode.getSimulator().getSimulationTime()); + this.conviction = 0; + this.snowMode = SnowMode.TRANSFERRING; + committedNodes.remove(this.peerBlockchainNode); + this.consensus = false; + if(committedNodes.size()==0){ + numReadyNodes = 0; + readyTransfer = false; + //System.out.println("All transferred!"); + } + switch (nextStep) { + case PREPARING: + if(this.peerBlockchainNode.nodeID == this.getCurrentPrimaryNumber()) { // The next node to start proposing a new block + SnowNode snowNode = (SnowNode) this.peerBlockchainNode; + SnowBlock newBlock = BlockFactory.sampleSnowBlock(this.peerBlockchainNode.getSimulator(), this.peerBlockchainNode.getNetwork().getRandom(), + snowNode, (SnowBlock) block); + snowNode.currentBlock = newBlock; + for (Node destination : sampledNeighbors) { + snowNode.query( + new QueryMessage( + new SnowPrepareQuery<>(snowNode, newBlock) + ), destination + ); + } + }else { + SnowNode snowNode = (SnowNode) this.peerBlockchainNode; + snowNode.currentBlock = (SnowBlock) block; + for(Node destination:sampledNeighbors){ + snowNode.query( + new QueryMessage( + new SnowPrepareQuery<>(snowNode, block) + ), destination + ); + } + } + break; + } + } + + private void continueSampling(Snow.SnowPhase nextStep, List sampledNeighbors, B block) { + switch (nextStep) { + case PREPARING: + for(Node destination:sampledNeighbors){ + this.peerBlockchainNode.query( + new QueryMessage( + new SnowPrepareQuery<>(this.peerBlockchainNode, block) + ), destination + ); + } + break; + } + } + + private void checkConsensus(B block) { + if(committedNodes.size() == getNumAllParticipants()){ + //System.out.println("node "+this.peerBlockchainNode.nodeID+" is ready"+ " at "+this.peerBlockchainNode.getSimulator().getSimulationTime()); + this.consensus=true; + numReadyNodes++; + if(numReadyNodes==getNumAllParticipants()){ + readyTransfer = true; + } + this.currentMainChainHead = block; // accepts the block + updateChain(); + writer.println("Consensus occurred in node " + this.peerBlockchainNode.getNodeID() + " for the block " + block.getHeight() + " at " + this.peerBlockchainNode.getSimulator().getSimulationTime()); + writer.flush(); + } + } + + private List sampleNeighbors(RandomnessEngine randomnessEngine, int k) { + List neighbors = new ArrayList<>(); + neighbors.addAll(this.peerBlockchainNode.getP2pConnections().getNeighbors()); + neighbors.remove(this.peerBlockchainNode); + List sampledNodes = new ArrayList<>(); + for (int i = 0; i < k; i++) { + int randomIndex = randomnessEngine.sampleInt(neighbors.size()); + sampledNodes.add(neighbors.get(randomIndex)); + neighbors.remove(randomIndex); + } + return sampledNodes; + } + private static double generateNormalSample(RandomnessEngine random, double mean, double stdDev) { + return mean + stdDev * random.nextGaussian(); + } + + @Override + public void newIncomingBlock(B block) { + + } + + /** + * @param block + * @return + */ + @Override + public boolean isBlockConfirmed(B block) { + return false; + } + + /** + * @param block + * @return + */ + @Override + public boolean isBlockValid(B block) { + return false; + } + + public int getNumAllParticipants() { + return this.numAllParticipants; + } + + public Snow.SnowPhase getSnowPhase() { + return this.snowPhase; + } + + public int getCurrentPrimaryNumber() { + return (this.currentMainChainHead.getHeight() % this.numAllParticipants); + } + + @Override + protected void updateChain() { + this.confirmedBlocks.add(this.currentMainChainHead); + numConsensus = this.confirmedBlocks.size(); + } + +} diff --git a/src/main/java/jabs/consensus/config/SnowConsensusConfig.java b/src/main/java/jabs/consensus/config/SnowConsensusConfig.java new file mode 100644 index 00000000..14ea5478 --- /dev/null +++ b/src/main/java/jabs/consensus/config/SnowConsensusConfig.java @@ -0,0 +1,4 @@ +package jabs.consensus.config; + +public class SnowConsensusConfig implements ConsensusAlgorithmConfig{ +} diff --git a/src/main/java/jabs/ledgerdata/BlockFactory.java b/src/main/java/jabs/ledgerdata/BlockFactory.java index eb86baf4..06de90dc 100644 --- a/src/main/java/jabs/ledgerdata/BlockFactory.java +++ b/src/main/java/jabs/ledgerdata/BlockFactory.java @@ -8,6 +8,8 @@ import jabs.network.node.nodes.pbft.PBFTNode; import jabs.simulator.randengine.RandomnessEngine; import jabs.simulator.Simulator; +import jabs.ledgerdata.snow.SnowBlock; +import jabs.network.node.nodes.snow.SnowNode; import java.util.Set; @@ -57,6 +59,11 @@ public static PBFTBlock samplePBFTBlock(Simulator simulator, RandomnessEngine ra simulator.getSimulationTime(), creator, parent); // TODO: Size of PBFT Blocks } + public static SnowBlock sampleSnowBlock(Simulator simulator, RandomnessEngine randomnessEngine, SnowNode creator, SnowBlock parent) { + return new SnowBlock(sampleBitcoinBlockSize(randomnessEngine), parent.getHeight() + 1, + simulator.getSimulationTime(), creator, parent); // TODO: Size of Snow Blocks + } + public static EthereumBlock sampleEthereumBlock(Simulator simulator, RandomnessEngine randomnessEngine, EthereumMinerNode creator, EthereumBlock parent, Set uncles, double weight) { return new EthereumBlock(sampleBitcoinBlockSize(randomnessEngine), parent.getHeight() + 1, diff --git a/src/main/java/jabs/ledgerdata/Query.java b/src/main/java/jabs/ledgerdata/Query.java new file mode 100644 index 00000000..166ff3d3 --- /dev/null +++ b/src/main/java/jabs/ledgerdata/Query.java @@ -0,0 +1,17 @@ +package jabs.ledgerdata; + +import jabs.network.node.nodes.Node; + +public class Query extends BasicData { + private final Node inquirer; + + protected Query(int size, Node inquirer) { + super(size); + this.inquirer = inquirer; + } + + public Node getInquirer() { + return inquirer; + } + +} diff --git a/src/main/java/jabs/ledgerdata/snow/SnowBlock.java b/src/main/java/jabs/ledgerdata/snow/SnowBlock.java new file mode 100644 index 00000000..5e3d239a --- /dev/null +++ b/src/main/java/jabs/ledgerdata/snow/SnowBlock.java @@ -0,0 +1,12 @@ +package jabs.ledgerdata.snow; + +import jabs.ledgerdata.SingleParentBlock; +import jabs.network.node.nodes.Node; + +public class SnowBlock extends SingleParentBlock { + public static final int Snow_BLOCK_HASH_SIZE = 32; + + public SnowBlock(int size, int height, double creationTime, Node creator, SnowBlock parent) { + super(size, height, creationTime, creator, parent, Snow_BLOCK_HASH_SIZE); + } +} diff --git a/src/main/java/jabs/ledgerdata/snow/SnowBlockQuery.java b/src/main/java/jabs/ledgerdata/snow/SnowBlockQuery.java new file mode 100644 index 00000000..fab1f9de --- /dev/null +++ b/src/main/java/jabs/ledgerdata/snow/SnowBlockQuery.java @@ -0,0 +1,29 @@ +package jabs.ledgerdata.snow; + +import jabs.ledgerdata.Block; +import jabs.ledgerdata.Query; +import jabs.network.node.nodes.Node; +public class SnowBlockQuery > extends Query { + private final B block; + private final SnowBlockQuery.QueryType queryType; + + public static final int SNOW_QUERY_SIZE_OVERHEAD = 10; + + public enum QueryType { + PREPARE, + COMMIT + } + + protected SnowBlockQuery(int size, Node inquirer, B block, SnowBlockQuery.QueryType queryType) { + super(size, inquirer); + this.block = block; + this.queryType = queryType; + } + + public SnowBlockQuery.QueryType getQueryType() { + return this.queryType; + } + public B getBlock() { + return this.block; + } +} diff --git a/src/main/java/jabs/ledgerdata/snow/SnowCommitQuery.java b/src/main/java/jabs/ledgerdata/snow/SnowCommitQuery.java new file mode 100644 index 00000000..5c41ba52 --- /dev/null +++ b/src/main/java/jabs/ledgerdata/snow/SnowCommitQuery.java @@ -0,0 +1,10 @@ +package jabs.ledgerdata.snow; + +import jabs.ledgerdata.Block; +import jabs.network.node.nodes.Node; + +public class SnowCommitQuery > extends SnowBlockQuery { + public SnowCommitQuery(Node inquirer, B block) { + super(block.getHash().getSize() + SNOW_QUERY_SIZE_OVERHEAD, inquirer, block, QueryType.COMMIT); + } +} diff --git a/src/main/java/jabs/ledgerdata/snow/SnowPrepareQuery.java b/src/main/java/jabs/ledgerdata/snow/SnowPrepareQuery.java new file mode 100644 index 00000000..39128512 --- /dev/null +++ b/src/main/java/jabs/ledgerdata/snow/SnowPrepareQuery.java @@ -0,0 +1,10 @@ +package jabs.ledgerdata.snow; + +import jabs.ledgerdata.Block; +import jabs.network.node.nodes.Node; + +public class SnowPrepareQuery > extends SnowBlockQuery { + public SnowPrepareQuery(Node inquirer, B block) { + super(block.getHash().getSize() + SNOW_QUERY_SIZE_OVERHEAD, inquirer, block, SnowBlockQuery.QueryType.PREPARE); + } +} diff --git a/src/main/java/jabs/ledgerdata/snow/SnowTx.java b/src/main/java/jabs/ledgerdata/snow/SnowTx.java new file mode 100644 index 00000000..ba1633ae --- /dev/null +++ b/src/main/java/jabs/ledgerdata/snow/SnowTx.java @@ -0,0 +1,9 @@ +package jabs.ledgerdata.snow; + +import jabs.ledgerdata.Tx; + +public class SnowTx extends Tx { + protected SnowTx(int size, int hashSize) { + super(size, hashSize); + } +} diff --git a/src/main/java/jabs/log/SnowCSVLogger.java b/src/main/java/jabs/log/SnowCSVLogger.java new file mode 100644 index 00000000..e47d1e3e --- /dev/null +++ b/src/main/java/jabs/log/SnowCSVLogger.java @@ -0,0 +1,79 @@ +package jabs.log; + +import jabs.ledgerdata.Query; +import jabs.ledgerdata.snow.SnowCommitQuery; +import jabs.ledgerdata.snow.SnowPrepareQuery; +import jabs.network.message.Packet; +import jabs.network.message.QueryMessage; +import jabs.simulator.event.Event; +import jabs.simulator.event.PacketDeliveryEvent; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Path; + +public class SnowCSVLogger extends AbstractCSVLogger { + + /** + * creates an abstract CSV logger + * @param writer this is output CSV of the logger + */ + public SnowCSVLogger(Writer writer) { + super(writer); + } + + /** + * creates an abstract CSV logger + * @param path this is output path of CSV file + */ + public SnowCSVLogger(Path path) throws IOException { + super(path); + } + + @Override + protected String csvStartingComment() { + return String.format("Snow Simulation with %d nodes on %s network", this.scenario.getNetwork().getAllNodes().size(), this.scenario.getNetwork().getClass().getSimpleName()); + } + + @Override + protected boolean csvOutputConditionBeforeEvent(Event event) { + return false; + } + + @Override + protected boolean csvOutputConditionAfterEvent(Event event) { + if (event instanceof PacketDeliveryEvent) { + PacketDeliveryEvent deliveryEvent = (PacketDeliveryEvent) event; + Packet packet = deliveryEvent.packet; + return packet.getMessage() instanceof QueryMessage; + } + return false; + } + + @Override + protected boolean csvOutputConditionFinalPerNode() { + return false; + } + + @Override + protected String[] csvHeaderOutput() { + return new String[]{"Simulation time", "Query message type", "Inquirer ID", "From Node", "To Node"}; + } + + @Override + protected String[] csvEventOutput(Event event) { + Packet packet = ((PacketDeliveryEvent) event).packet; + Query query = ((QueryMessage) packet.getMessage()).getQuery(); + + String queryType = ""; + if (query instanceof SnowCommitQuery) { + queryType = "COMMIT"; + } else if (query instanceof SnowPrepareQuery) { + queryType = "PREPARE"; + } + + return new String[]{Double.toString(this.scenario.getSimulator().getSimulationTime()), queryType, + Integer.toString(query.getInquirer().nodeID), Integer.toString(packet.getFrom().nodeID), + Integer.toString(packet.getTo().nodeID)}; + } +} diff --git a/src/main/java/jabs/network/message/QueryMessage.java b/src/main/java/jabs/network/message/QueryMessage.java new file mode 100644 index 00000000..f259e55f --- /dev/null +++ b/src/main/java/jabs/network/message/QueryMessage.java @@ -0,0 +1,16 @@ +package jabs.network.message; + +import jabs.ledgerdata.Query; + +public class QueryMessage extends Message{ + private final Query query; + + public QueryMessage(Query query) { + super(query.getSize()); + this.query = query; + } + + public Query getQuery() { + return query; + } +} diff --git a/src/main/java/jabs/network/networks/snow/SnowLocalLANNetwork.java b/src/main/java/jabs/network/networks/snow/SnowLocalLANNetwork.java new file mode 100644 index 00000000..e1e3be90 --- /dev/null +++ b/src/main/java/jabs/network/networks/snow/SnowLocalLANNetwork.java @@ -0,0 +1,47 @@ +package jabs.network.networks.snow; + +import jabs.consensus.config.ConsensusAlgorithmConfig; +import jabs.network.networks.Network; +import jabs.network.node.nodes.Node; +import jabs.network.node.nodes.snow.SnowNode; +import jabs.network.stats.lan.LAN100MNetworkStats; +import jabs.network.stats.lan.SingleNodeType; +import jabs.simulator.Simulator; +import jabs.simulator.randengine.RandomnessEngine; + +public class SnowLocalLANNetwork extends Network { + public SnowLocalLANNetwork(RandomnessEngine randomnessEngine) { + super(randomnessEngine, new LAN100MNetworkStats(randomnessEngine)); + } + + public SnowNode createNewSnowNode(Simulator simulator, int nodeID, int numAllParticipants) { + return new SnowNode(simulator, this, nodeID, + this.sampleDownloadBandwidth(SingleNodeType.LAN_NODE), + this.sampleUploadBandwidth(SingleNodeType.LAN_NODE), + numAllParticipants); + } + + @Override + public void populateNetwork(Simulator simulator, ConsensusAlgorithmConfig snowConsensusConfig) { + populateNetwork(simulator, 40, snowConsensusConfig); + } + + @Override + public void populateNetwork(Simulator simulator, int numNodes, ConsensusAlgorithmConfig snowConsensusConfig) { + for (int i = 0; i < numNodes; i++) { + this.addNode(createNewSnowNode(simulator, i, numNodes), SingleNodeType.LAN_NODE); + } + + for (Node node:this.getAllNodes()) { + node.getP2pConnections().connectToNetwork(this); + } + } + + /** + * @param node A Snow node to add to the network + */ + @Override + public void addNode(SnowNode node) { + this.addNode(node, SingleNodeType.LAN_NODE); + } +} diff --git a/src/main/java/jabs/network/node/nodes/Node.java b/src/main/java/jabs/network/node/nodes/Node.java index 362ea434..ad2905fd 100644 --- a/src/main/java/jabs/network/node/nodes/Node.java +++ b/src/main/java/jabs/network/node/nodes/Node.java @@ -110,6 +110,23 @@ public void broadcastMessage(Message message) { ); } } + /** + * Forces the node to respond a message to the inquirer + * @param message The message to be responded + */ + public void respondQuery(Message message, Node destination) { + this.networkInterface.addToUpLinkQueue( + new Packet(this, destination, message)); + } + + /** + * Forces the node to query from its sampled neighbors + * @param message The message to be queried + */ + public void query(Message message, Node destination) { + this.networkInterface.addToUpLinkQueue( + new Packet(this, destination, message)); + } /** * Returns node's simulator diff --git a/src/main/java/jabs/network/node/nodes/PeerBlockchainNode.java b/src/main/java/jabs/network/node/nodes/PeerBlockchainNode.java index 4e95ab3d..60c63c2c 100644 --- a/src/main/java/jabs/network/node/nodes/PeerBlockchainNode.java +++ b/src/main/java/jabs/network/node/nodes/PeerBlockchainNode.java @@ -19,6 +19,7 @@ public abstract class PeerBlockchainNode, T exten protected final HashMap alreadySeenTxs = new HashMap<>(); protected final HashMap alreadySeenBlocks = new HashMap<>(); protected final HashSet alreadySeenVotes = new HashSet<>(); + protected final HashSet alreadySeenQueries = new HashSet<>(); protected final LocalBlockTree localBlockTree; public PeerBlockchainNode(Simulator simulator, Network network, int nodeID, long downloadBandwidth, @@ -112,11 +113,18 @@ public void processIncomingPacket(Packet packet) { alreadySeenVotes.add(vote); this.processNewVote(vote); } + }else if (message instanceof QueryMessage) { + Query query = ((QueryMessage) message).getQuery(); + if (!alreadySeenQueries.contains(query)) { + alreadySeenQueries.add(query); + this.processNewQuery(query); + } } } protected abstract void processNewBlock(B block); protected abstract void processNewVote(Vote vote); + protected abstract void processNewQuery(Query query); public AbstractChainBasedConsensus getConsensusAlgorithm() { return this.consensusAlgorithm; diff --git a/src/main/java/jabs/network/node/nodes/PeerDLTNode.java b/src/main/java/jabs/network/node/nodes/PeerDLTNode.java index d2bb6cda..17ce907f 100644 --- a/src/main/java/jabs/network/node/nodes/PeerDLTNode.java +++ b/src/main/java/jabs/network/node/nodes/PeerDLTNode.java @@ -19,6 +19,7 @@ public abstract class PeerDLTNode, T extends Tx> extends N protected final HashMap alreadySeenTxs = new HashMap<>(); protected final HashMap alreadySeenBlocks = new HashMap<>(); protected final HashSet alreadySeenVotes = new HashSet<>(); + protected final HashSet alreadySeenQueries = new HashSet<>(); protected final LocalBlockDAG localBlockTree; public PeerDLTNode(Simulator simulator, Network network, int nodeID, long downloadBandwidth, long uploadBandwidth, AbstractP2PConnections routingTable, @@ -114,13 +115,19 @@ public void processIncomingPacket(Packet packet) { alreadySeenVotes.add(vote); this.processNewVote(vote); } + }else if (message instanceof QueryMessage) { + Query query = ((QueryMessage) message).getQuery(); + if (!alreadySeenQueries.contains(query)) { + alreadySeenQueries.add(query); + this.processNewQuery(query); + } } } protected abstract void processNewTx(T tx, Node from); protected abstract void processNewBlock(B block); protected abstract void processNewVote(Vote vote); - + protected abstract void processNewQuery(Query query); public AbstractDAGBasedConsensus getConsensusAlgorithm() { return this.consensusAlgorithm; } diff --git a/src/main/java/jabs/network/node/nodes/bitcoin/BitcoinNode.java b/src/main/java/jabs/network/node/nodes/bitcoin/BitcoinNode.java index c8b4ba16..ee26fc67 100644 --- a/src/main/java/jabs/network/node/nodes/bitcoin/BitcoinNode.java +++ b/src/main/java/jabs/network/node/nodes/bitcoin/BitcoinNode.java @@ -46,6 +46,11 @@ protected void processNewVote(Vote vote) { } + @Override + protected void processNewQuery(jabs.ledgerdata.Query query) { + + } + protected void broadcastTxInvMessage(BitcoinTx tx) { for (Node neighbor:this.p2pConnections.getNeighbors()) { this.networkInterface.addToUpLinkQueue( diff --git a/src/main/java/jabs/network/node/nodes/ethereum/EthereumNode.java b/src/main/java/jabs/network/node/nodes/ethereum/EthereumNode.java index 65f030db..66712b4e 100644 --- a/src/main/java/jabs/network/node/nodes/ethereum/EthereumNode.java +++ b/src/main/java/jabs/network/node/nodes/ethereum/EthereumNode.java @@ -61,6 +61,11 @@ protected void processNewVote(Vote vote) { } } + @Override + protected void processNewQuery(jabs.ledgerdata.Query query) { + + } + @Override public void generateNewTransaction() { broadcastTransaction(TransactionFactory.sampleEthereumTransaction(network.getRandom())); diff --git a/src/main/java/jabs/network/node/nodes/iota/IOTANode.java b/src/main/java/jabs/network/node/nodes/iota/IOTANode.java index b11f6aa3..40b76dd8 100644 --- a/src/main/java/jabs/network/node/nodes/iota/IOTANode.java +++ b/src/main/java/jabs/network/node/nodes/iota/IOTANode.java @@ -3,6 +3,7 @@ import jabs.consensus.algorithm.TangleIOTA; import jabs.consensus.blockchain.LocalBlockDAG; import jabs.consensus.config.TangleIOTAConsensusConfig; +import jabs.ledgerdata.Query; import jabs.ledgerdata.Vote; import jabs.ledgerdata.tangle.TangleTx; import jabs.ledgerdata.tangle.TangleBlock; @@ -96,6 +97,11 @@ protected void processNewVote(Vote vote) { } + @Override + protected void processNewQuery(Query query) { + + } + /** * */ diff --git a/src/main/java/jabs/network/node/nodes/pbft/PBFTNode.java b/src/main/java/jabs/network/node/nodes/pbft/PBFTNode.java index d50926f7..4e54312e 100644 --- a/src/main/java/jabs/network/node/nodes/pbft/PBFTNode.java +++ b/src/main/java/jabs/network/node/nodes/pbft/PBFTNode.java @@ -37,6 +37,11 @@ protected void processNewVote(Vote vote) { ((PBFT) this.consensusAlgorithm).newIncomingVote(vote); } + @Override + protected void processNewQuery(jabs.ledgerdata.Query query) { + + } + @Override public void generateNewTransaction() { // nothing for now diff --git a/src/main/java/jabs/network/node/nodes/snow/SnowNode.java b/src/main/java/jabs/network/node/nodes/snow/SnowNode.java new file mode 100644 index 00000000..c936e23a --- /dev/null +++ b/src/main/java/jabs/network/node/nodes/snow/SnowNode.java @@ -0,0 +1,48 @@ +package jabs.network.node.nodes.snow; + +import jabs.consensus.algorithm.Snow; +import jabs.consensus.blockchain.LocalBlockTree; +import jabs.ledgerdata.Query; +import jabs.ledgerdata.Vote; +import jabs.ledgerdata.snow.SnowBlock; +import jabs.ledgerdata.snow.SnowTx; +import jabs.network.networks.Network; +import jabs.network.node.nodes.Node; +import jabs.network.node.nodes.PeerBlockchainNode; +import jabs.network.p2p.SnowP2P; +import jabs.simulator.Simulator; + +public class SnowNode extends PeerBlockchainNode { + public static final SnowBlock SNOW_GENESIS_BLOCK = + new SnowBlock(0, 0, 0, null, null); + public SnowBlock currentBlock; + public SnowNode(Simulator simulator, Network network, int nodeID, long downloadBandwidth, long uploadBandwidth, int numAllParticipants) { + super(simulator, network, nodeID, downloadBandwidth, uploadBandwidth, + new SnowP2P(), + new Snow<>(new LocalBlockTree<>(SNOW_GENESIS_BLOCK), numAllParticipants) + ); + this.consensusAlgorithm.setNode(this); + } + + @Override + protected void processNewTx(SnowTx tx, Node from) { + // nothing for now + } + + @Override + protected void processNewBlock(SnowBlock block) { + // nothing for now + } + @Override + protected void processNewVote(Vote vote) { + + } + @Override + protected void processNewQuery(Query query) { + ((Snow) this.consensusAlgorithm).newIncomingQuery(query); + } + @Override + public void generateNewTransaction() { + // nothing for now + } +} diff --git a/src/main/java/jabs/network/p2p/AbstractBlockchainP2PConnections.java b/src/main/java/jabs/network/p2p/AbstractBlockchainP2PConnections.java index c5e84520..f7fc46c8 100644 --- a/src/main/java/jabs/network/p2p/AbstractBlockchainP2PConnections.java +++ b/src/main/java/jabs/network/p2p/AbstractBlockchainP2PConnections.java @@ -43,7 +43,7 @@ public void connectToNetwork(Network network){ public boolean requestConnection(Node remoteNode) { if (this.inbound.size() <= (this.maxConnections - this.numOutboundConnections)) { this.inbound.add(remoteNode); - this.neighbors.add(remoteNode); + this.peerNeighbors.add(remoteNode); return true; } else { return false; @@ -52,6 +52,6 @@ public boolean requestConnection(Node remoteNode) { public void addOutbound(Node remoteNode) { this.outbound.add(remoteNode); - this.neighbors.add(remoteNode); + this.peerNeighbors.add(remoteNode); } } diff --git a/src/main/java/jabs/network/p2p/AbstractP2PConnections.java b/src/main/java/jabs/network/p2p/AbstractP2PConnections.java index c980157a..5f502d30 100644 --- a/src/main/java/jabs/network/p2p/AbstractP2PConnections.java +++ b/src/main/java/jabs/network/p2p/AbstractP2PConnections.java @@ -9,7 +9,7 @@ // TODO recheck if it is a better method for implementing Abstract Routing Table public abstract class AbstractP2PConnections { protected Node node; - protected final List neighbors = new ArrayList<>(); + protected final List peerNeighbors = new ArrayList<>(); protected Node getNode() { return node; @@ -18,7 +18,7 @@ public void setNode(Node node) { this.node = node; } public List getNeighbors(){ - return this.neighbors; + return this.peerNeighbors; } public abstract void connectToNetwork(Network network); public abstract boolean requestConnection(Node node); diff --git a/src/main/java/jabs/network/p2p/PBFTP2P.java b/src/main/java/jabs/network/p2p/PBFTP2P.java index 37e9a99f..f5626be3 100644 --- a/src/main/java/jabs/network/p2p/PBFTP2P.java +++ b/src/main/java/jabs/network/p2p/PBFTP2P.java @@ -6,7 +6,7 @@ public class PBFTP2P extends AbstractP2PConnections { @Override public void connectToNetwork(Network network) { - this.neighbors.addAll(network.getAllNodes()); + this.peerNeighbors.addAll(network.getAllNodes()); node.getNodeNetworkInterface().connectNetwork(network, network.getRandom()); } diff --git a/src/main/java/jabs/network/p2p/SnowP2P.java b/src/main/java/jabs/network/p2p/SnowP2P.java new file mode 100644 index 00000000..5e4e925d --- /dev/null +++ b/src/main/java/jabs/network/p2p/SnowP2P.java @@ -0,0 +1,16 @@ +package jabs.network.p2p; + +import jabs.network.networks.Network; +import jabs.network.node.nodes.Node; + +public class SnowP2P extends AbstractP2PConnections{ + public void connectToNetwork(Network network) { + this.peerNeighbors.addAll(network.getAllNodes()); + node.getNodeNetworkInterface().connectNetwork(network, network.getRandom()); + } + + @Override + public boolean requestConnection(Node node) { + return false; + } +} diff --git a/src/main/java/jabs/network/stats/lan/LAN100MNetworkStats.java b/src/main/java/jabs/network/stats/lan/LAN100MNetworkStats.java index ba2f0230..fcc0fa4f 100644 --- a/src/main/java/jabs/network/stats/lan/LAN100MNetworkStats.java +++ b/src/main/java/jabs/network/stats/lan/LAN100MNetworkStats.java @@ -5,7 +5,7 @@ public class LAN100MNetworkStats implements NetworkStats { protected final RandomnessEngine randomnessEngine; - protected static final double LAN_AVERAGE_LATENCY = 0.002; // 2 milli seconds + protected static final double LAN_AVERAGE_LATENCY = 0.02; // 20 milli seconds protected static final double LATENCY_PARETO_SHAPE = 5; protected static final long LAN_AVERAGE_BANDWIDTH = 100000000; diff --git a/src/main/java/jabs/scenario/SnowLANScenario.java b/src/main/java/jabs/scenario/SnowLANScenario.java new file mode 100644 index 00000000..171bd983 --- /dev/null +++ b/src/main/java/jabs/scenario/SnowLANScenario.java @@ -0,0 +1,88 @@ +package jabs.scenario; + +import jabs.consensus.algorithm.Snow; +import jabs.consensus.config.SnowConsensusConfig; +import jabs.ledgerdata.BlockFactory; +import jabs.ledgerdata.snow.SnowBlock; +import jabs.ledgerdata.snow.SnowPrepareQuery; +import jabs.network.message.QueryMessage; +import jabs.network.networks.snow.SnowLocalLANNetwork; +import jabs.network.node.nodes.Node; +import jabs.network.node.nodes.snow.SnowNode; + +import java.util.ArrayList; +import java.util.List; + +import static jabs.network.node.nodes.snow.SnowNode.SNOW_GENESIS_BLOCK; + +/** + * File: Snow.java + * Description: Implements SnowBall protocol for JABS blockchain simulator. + * Author: Siamak Abdi + * Date: August 22, 2023 + */ + +public class SnowLANScenario extends AbstractScenario{ + protected int numNodes; + protected double simulationStopTime; + + public SnowLANScenario(String name, long seed, int numNodes, double simulationStopTime) { + super(name, seed); + this.numNodes = numNodes; + this.simulationStopTime = simulationStopTime; + } + + @Override + public void createNetwork() { + network = new SnowLocalLANNetwork(randomnessEngine); + network.populateNetwork(this.simulator, this.numNodes, new SnowConsensusConfig()); + } + + @Override + protected void insertInitialEvents() { + List nodes = network.getAllNodes(); + Node startNode = nodes.get(0); // the first node that starts proposing a block + SnowNode snowNode = (SnowNode) startNode; + List sampledNeighbors = sampleNeighbors(startNode, Snow.k); + SnowBlock snowBlock = BlockFactory.sampleSnowBlock(simulator, network.getRandom(), + (SnowNode) startNode, SNOW_GENESIS_BLOCK); + snowNode.currentBlock = snowBlock; + for(Node destination:sampledNeighbors){ + startNode.query( + new QueryMessage( + new SnowPrepareQuery<>(startNode, snowBlock) + ), destination + ); + } + //----------------------------------- + for(int n=1;n samples = sampleNeighbors(nodes.get(n), Snow.k); + SnowNode otherNodes = (SnowNode) nodes.get(n); + otherNodes.currentBlock = SNOW_GENESIS_BLOCK; + for(Node destination:samples){ + nodes.get(n).query( + new QueryMessage( + new SnowPrepareQuery<>(nodes.get(n), SNOW_GENESIS_BLOCK) + ), destination + ); + } + } + } + private List sampleNeighbors(Node node, int k) { + List neighbors = new ArrayList<>(); + neighbors.addAll(node.getP2pConnections().getNeighbors()); + neighbors.remove(node); + List sampledNodes = new ArrayList<>(); + for (int i = 0; i < k; i++) { + int randomIndex = randomnessEngine.sampleInt(neighbors.size()); + sampledNodes.add(neighbors.get(randomIndex)); + neighbors.remove(randomIndex); + } + return sampledNodes; + } + @Override + public boolean simulationStopCondition() { + return (simulator.getSimulationTime() > this.simulationStopTime); + } + +}