From eb8588df9f9a8fbaad7d28f8364fa3477a46372a Mon Sep 17 00:00:00 2001 From: Arunima Chaudhuri Date: Sun, 21 Jan 2024 01:27:17 +0530 Subject: [PATCH] Introduce 'maliciousNode' parameter Signed-off-by: Arunima Chaudhuri --- DAS/results.py | 12 +++++++ DAS/shape.py | 4 ++- DAS/simulator.py | 21 ++++++++++-- DAS/validator.py | 62 ++++++++++++++++++++++++----------- DAS/visualizor.py | 83 +++++++++++++++++++++++++++++++++++++++++++---- smallConf.py | 21 +++++++----- 6 files changed, 165 insertions(+), 38 deletions(-) diff --git a/DAS/results.py b/DAS/results.py index 76d96d1..628e8a8 100644 --- a/DAS/results.py +++ b/DAS/results.py @@ -16,6 +16,18 @@ class Result: self.tta = -1 self.missingVector = [] self.metrics = {} + self.amImalicious = [0] * shape.numberNodes + self.msgSentCount = [0] * shape.numberNodes + self.msgRecvCount = [0] * shape.numberNodes + self.sampleRecvCount = [0] * shape.numberNodes + + def copyValidators(self, validators): + """Copy information from simulator.validators to result.""" + for i in range(0,self.shape.numberNodes): + self.amImalicious[i] = validators[i].amImalicious + self.msgSentCount[i] = validators[i].msgSentCount + self.msgRecvCount[i] = validators[i].msgRecvCount + self.sampleRecvCount[i] = validators[i].sampleRecvCount def populate(self, shape, config, missingVector): """It populates part of the result data inside a vector.""" diff --git a/DAS/shape.py b/DAS/shape.py index 9f6d573..109bf97 100644 --- a/DAS/shape.py +++ b/DAS/shape.py @@ -3,13 +3,14 @@ class Shape: """This class represents a set of parameters for a specific simulation.""" - def __init__(self, blockSize, numberNodes, failureModel, failureRate, class1ratio, chi, vpn1, vpn2, netDegree, bwUplinkProd, bwUplink1, bwUplink2, run): + def __init__(self, blockSize, numberNodes, failureModel, failureRate, maliciousNodes, class1ratio, chi, vpn1, vpn2, netDegree, bwUplinkProd, bwUplink1, bwUplink2, run): """Initializes the shape with the parameters passed in argument.""" self.run = run self.numberNodes = numberNodes self.blockSize = blockSize self.failureModel = failureModel self.failureRate = failureRate + self.maliciousNodes = maliciousNodes self.netDegree = netDegree self.class1ratio = class1ratio self.chi = chi @@ -36,6 +37,7 @@ class Shape: shastr += "-bwup2-"+str(self.bwUplink2) shastr += "-nd-"+str(self.netDegree) shastr += "-r-"+str(self.run) + shastr += "-mn-"+str(self.maliciousNodes) return shastr def setSeed(self, seed): diff --git a/DAS/simulator.py b/DAS/simulator.py index 147d499..faffdff 100644 --- a/DAS/simulator.py +++ b/DAS/simulator.py @@ -70,7 +70,14 @@ class Simulator: assignedRows = [] assignedCols = [] + maliciousNodesCount = int((self.shape.maliciousNodes / 100) * self.shape.numberNodes) for i in range(self.shape.numberNodes): + if i==0: + amImalicious_value = 0 + elif i < maliciousNodesCount+1: + amImalicious_value = 1 + else: + amImalicious_value = 0 if self.config.evenLineDistribution: if i < int(lightVal/self.shape.vpn1): # First start with the light nodes start = i *self.shape.chi*self.shape.vpn1 @@ -81,7 +88,7 @@ class Simulator: end = offset+((j+1)*self.shape.chi*self.shape.vpn2) r = rows[start:end] c = columns[start:end] - val = Validator(i, int(not i!=0), self.logger, self.shape, self.config, r, c) + val = Validator(i, int(not i!=0), amImalicious_value, self.logger, self.shape, self.config, r, c) self.logger.debug("Node %d has row IDs: %s" % (val.ID, val.rowIDs), extra=self.format) self.logger.debug("Node %d has column IDs: %s" % (val.ID, val.columnIDs), extra=self.format) assignedRows = assignedRows + list(r) @@ -90,7 +97,7 @@ class Simulator: self.nodeColumns.append(val.columnIDs) else: - val = Validator(i, int(not i!=0), self.logger, self.shape, self.config) + val = Validator(i, int(not i!=0), amImalicious_value, self.logger, self.shape, self.config) if i == self.proposerID: val.initBlock() else: @@ -229,6 +236,7 @@ class Simulator: missingVector = [] progressVector = [] trafficStatsVector = [] + malicious_nodes_not_added_count = 0 steps = 0 while(True): missingVector.append(missingSamples) @@ -298,6 +306,14 @@ class Simulator: break steps += 1 + for i in range(0,self.shape.numberNodes): + if not self.validators[i].amIaddedToQueue : + malicious_nodes_not_added_count += 1 + + self.logger.debug("Number of malicious nodes not added to the send queue: %d" % malicious_nodes_not_added_count, extra=self.format) + malicious_nodes_not_added_percentage = (malicious_nodes_not_added_count * 100)/(self.shape.numberNodes) + self.logger.debug("Percentage of malicious nodes not added to the send queue: %d" % malicious_nodes_not_added_percentage, extra=self.format) + progress = pd.DataFrame(progressVector) if self.config.saveRCdist: self.result.addMetric("rowDist", self.distR) @@ -305,5 +321,6 @@ class Simulator: if self.config.saveProgress: self.result.addMetric("progress", progress.to_dict(orient='list')) self.result.populate(self.shape, self.config, missingVector) + self.result.copyValidators(self.validators) return self.result diff --git a/DAS/validator.py b/DAS/validator.py index 4e8d350..eaf7686 100644 --- a/DAS/validator.py +++ b/DAS/validator.py @@ -38,7 +38,7 @@ class Validator: """It returns the validator ID.""" return str(self.ID) - def __init__(self, ID, amIproposer, logger, shape, config, rows = None, columns = None): + def __init__(self, ID, amIproposer, amImalicious, logger, shape, config, rows = None, columns = None): """It initializes the validator with the logger shape and rows/columns. If rows/columns are specified these are observed, otherwise (default) @@ -54,6 +54,12 @@ class Validator: self.receivedQueue = deque() self.sendQueue = deque() self.amIproposer = amIproposer + self.amImalicious = amImalicious + self.amIaddedToQueue = 0 + self.msgSentCount = 0 + self.msgRecvCount = 0 + self.sampleSentCount = 0 + self.sampleRecvCount = 0 self.logger = logger if self.shape.chi < 1: self.logger.error("Chi has to be greater than 0", extra=self.format) @@ -197,8 +203,10 @@ class Validator: if not self.receivedBlock.getSegment(rID, cID): self.logger.trace("Recv new: %d->%d: %d,%d", src, self.ID, rID, cID, extra=self.format) self.receivedBlock.setSegment(rID, cID) + self.sampleRecvCount += 1 if self.perNodeQueue or self.perNeighborQueue: self.receivedQueue.append((rID, cID)) + self.msgRecvCount += 1 else: self.logger.trace("Recv DUP: %d->%d: %d,%d", src, self.ID, rID, cID, extra=self.format) self.statsRxDupInSlot += 1 @@ -206,17 +214,23 @@ class Validator: def addToSendQueue(self, rID, cID): """Queue a segment for forwarding.""" - if self.perNodeQueue: + if self.perNodeQueue and not self.amImalicious: self.sendQueue.append((rID, cID)) + self.amIaddedToQueue = 1 + self.msgSentCount += 1 - if self.perNeighborQueue: + if self.perNeighborQueue and not self.amImalicious: if rID in self.rowIDs: for neigh in self.rowNeighbors[rID].values(): neigh.sendQueue.append(cID) + self.amIaddedToQueue = 1 + self.msgSentCount += 1 if cID in self.columnIDs: for neigh in self.columnNeighbors[cID].values(): neigh.sendQueue.append(rID) + self.amIaddedToQueue = 1 + self.msgSentCount += 1 def receiveRowsColumns(self): """Finalize time step by merging newly received segments in state.""" @@ -233,11 +247,14 @@ class Validator: neigh.received |= neigh.receiving neigh.receiving.setall(0) + for rID, cID in self.receivedQueue: + self.msgRecvCount += 1 # add newly received segments to the send queue if self.perNodeQueue or self.perNeighborQueue: while self.receivedQueue: (rID, cID) = self.receivedQueue.popleft() - self.addToSendQueue(rID, cID) + if not self.amImalicious: + self.addToSendQueue(rID, cID) def updateStats(self): """It updates the stats related to sent and received data.""" @@ -269,7 +286,7 @@ class Validator: def checkSendSegmentToNeigh(self, rID, cID, neigh): """Check and send a segment to a neighbor if needed.""" - if self.checkSegmentToNeigh(rID, cID, neigh): + if self.checkSegmentToNeigh(rID, cID, neigh) and not self.amImalicious: self.sendSegmentToNeigh(rID, cID, neigh) return True else: @@ -286,14 +303,16 @@ class Validator: if rID in self.rowIDs: for _, neigh in shuffledDict(self.rowNeighbors[rID], self.shuffleNeighbors): - self.checkSendSegmentToNeigh(rID, cID, neigh) + if not self.amImalicious: + self.checkSendSegmentToNeigh(rID, cID, neigh) if self.statsTxInSlot >= self.bwUplink: return if cID in self.columnIDs: for _, neigh in shuffledDict(self.columnNeighbors[cID], self.shuffleNeighbors): - self.checkSendSegmentToNeigh(rID, cID, neigh) + if not self.amImalicious: + self.checkSendSegmentToNeigh(rID, cID, neigh) if self.statsTxInSlot >= self.bwUplink: return @@ -318,19 +337,20 @@ class Validator: # collect and shuffle for rID, neighs in self.rowNeighbors.items(): for neigh in neighs.values(): - if (neigh.sendQueue): + if (neigh.sendQueue) and not self.amImalicious: queues.append((0, rID, neigh)) for cID, neighs in self.columnNeighbors.items(): for neigh in neighs.values(): - if (neigh.sendQueue): + if (neigh.sendQueue) and not self.amImalicious: queues.append((1, cID, neigh)) for dim, lineID, neigh in shuffled(queues, self.shuffleQueues): - if dim == 0: - self.checkSendSegmentToNeigh(lineID, neigh.sendQueue.popleft(), neigh) - else: - self.checkSendSegmentToNeigh(neigh.sendQueue.popleft(), lineID, neigh) + if not self.amImalicious: + if dim == 0: + self.checkSendSegmentToNeigh(lineID, neigh.sendQueue.popleft(), neigh) + else: + self.checkSendSegmentToNeigh(neigh.sendQueue.popleft(), lineID, neigh) progress = True if self.statsTxInSlot >= self.bwUplink: return @@ -450,22 +470,24 @@ class Validator: """ Send as much as we can in the timestep, limited by bwUplink.""" # process node level send queue - self.processSendQueue() + if not self.amImalicious: + self.processSendQueue() if self.statsTxInSlot >= self.bwUplink: return # process neighbor level send queues in shuffled breadth-first order - self.processPerNeighborSendQueue() + if not self.amImalicious: + self.processPerNeighborSendQueue() if self.statsTxInSlot >= self.bwUplink: return # process possible segments to send in shuffled breadth-first order - if self.segmentShuffleScheduler: + if self.segmentShuffleScheduler and not self.amImalicious: self.runSegmentShuffleScheduler() if self.statsTxInSlot >= self.bwUplink: return - if self.dumbRandomScheduler: + if self.dumbRandomScheduler and not self.amImalicious: self.runDumbRandomScheduler() if self.statsTxInSlot >= self.bwUplink: return @@ -497,7 +519,8 @@ class Validator: for i in range(len(rep)): if rep[i]: self.logger.trace("Rep: %d,%d", id, i, extra=self.format) - self.addToSendQueue(id, i) + if not self.amImalicious: + self.addToSendQueue(id, i) # self.statsRepairInSlot += rep.count(1) def restoreColumns(self): @@ -515,7 +538,8 @@ class Validator: for i in range(len(rep)): if rep[i]: self.logger.trace("Rep: %d,%d", i, id, extra=self.format) - self.addToSendQueue(i, id) + if not self.amImalicious: + self.addToSendQueue(i, id) # self.statsRepairInSlot += rep.count(1) def checkStatus(self): diff --git a/DAS/visualizor.py b/DAS/visualizor.py index cb3c5d9..fb582e9 100644 --- a/DAS/visualizor.py +++ b/DAS/visualizor.py @@ -6,16 +6,20 @@ import os def plotData(conf): plt.clf() fig = plt.figure("9, 3") + plt.grid(True) if conf["desLoc"] == 1: xDes = 0 else: xDes = conf["xdots"][-1] * 0.6 props = dict(boxstyle='round', facecolor='wheat', alpha=0.5) plt.text(xDes, conf["yaxismax"]/4, conf["textBox"], fontsize=10, verticalalignment='top', bbox=props) - for i in range(len(conf["data"])): - if conf["type"] == "plot": + if conf["type"] == "plot": + for i in range(len(conf["data"])): plt.plot(conf["xdots"], conf["data"][i], conf["colors"][i], label=conf["labels"][i]) - if conf["type"] == "bar": + elif conf["type"] == "individual_bar": + plt.bar(conf["xdots"], conf["data"]) + elif conf["type"] == "grouped_bar": + for i in range(len(conf["data"])): plt.bar(conf["xdots"], conf["data"][i], label=conf["labels"][i]) plt.title(conf["title"]) plt.ylabel(conf["ylabel"]) @@ -66,6 +70,9 @@ class Visualizor: for result in self.results: plotPath = "results/"+self.execID+"/plots/"+str(result.shape) os.makedirs(plotPath, exist_ok=True) + self.plotMessagesSent(result, plotPath) + self.plotMessagesRecv(result, plotPath) + self.plotSampleRecv(result, plotPath) self.plotMissingSamples(result, plotPath) self.plotProgress(result, plotPath) self.plotSentData(result, plotPath) @@ -74,6 +81,28 @@ class Visualizor: if self.config.saveRCdist: self.plotRowCol(result, plotPath) + def plotSampleRecv(self, result, plotPath): + """Plots the percentage sampleRecv for each node""" + conf = {} + text = str(result.shape).split("-") + conf["textBox"] = "Block Size: "+text[1]+"\nNumber of nodes: "+text[3]\ + +"\nFailure rate: "+text[7]+" \nNetwork degree: "+text[23]+"\nX: "+text[11]+" rows/columns" + conf["title"] = "Percentage of Samples Received by Nodes" + conf["type"] = "individual_bar" + conf["legLoc"] = 1 + conf["desLoc"] = 1 + conf["xlabel"] = "Nodes" + conf["ylabel"] = "Percentage of samples received (%)" + total_samples = result.shape.blockSize * result.shape.blockSize + percentage_data = [(count / total_samples) * 100 for count in result.sampleRecvCount] + conf["data"] = percentage_data + conf["xdots"] = range(result.shape.numberNodes) + conf["path"] = plotPath + "/sampleRecv.png" + maxi = max(conf["data"]) + conf["yaxismax"] = maxi + plotData(conf) + print("Plot %s created." % conf["path"]) + def plotMissingSamples(self, result, plotPath): """Plots the missing samples in the network""" conf = {} @@ -101,9 +130,9 @@ class Visualizor: def plotProgress(self, result, plotPath): """Plots the percentage of nodes ready in the network""" - vector1 = result.metrics["progress"]["nodes ready"] - vector2 = result.metrics["progress"]["validators ready"] - vector3 = result.metrics["progress"]["samples received"] + vector1 = [x * 100 for x in result.metrics["progress"]["nodes ready"]] + vector2 = [x * 100 for x in result.metrics["progress"]["validators ready"]] + vector3 = [x * 100 for x in result.metrics["progress"]["samples received"]] conf = {} text = str(result.shape).split("-") conf["textBox"] = "Block Size: "+text[1]+"\nNumber of nodes: "+text[3]\ @@ -228,7 +257,7 @@ class Visualizor: conf["textBox"] = "Block Size: "+text[1]+"\nNumber of nodes: "+text[3]\ +"\nFailure rate: "+text[7]+" \nNetwork degree: "+text[23]+"\nX: "+text[11]+" rows/columns" conf["title"] = "Row/Column distribution" - conf["type"] = "bar" + conf["type"] = "grouped_bar" conf["legLoc"] = 2 conf["desLoc"] = 2 conf["colors"] = ["r+", "b+"] @@ -246,3 +275,43 @@ class Visualizor: plotData(conf) print("Plot %s created." % conf["path"]) + def plotMessagesSent(self, result, plotPath): + """Plots the number of messages sent by all nodes""" + conf = {} + text = str(result.shape).split("-") + conf["textBox"] = "Block Size: "+text[1]+"\nNumber of nodes: "+text[3]\ + +"\nFailure rate: "+text[7]+" \nNetwork degree: "+text[23]+"\nX: "+text[11]+" rows/columns" + conf["title"] = "Number of Messages Sent by Nodes" + conf["type"] = "individual_bar" + conf["legLoc"] = 1 + conf["desLoc"] = 1 + conf["xlabel"] = "Nodes" + conf["ylabel"] = "Number of Messages Sent" + conf["data"] = result.msgSentCount + conf["xdots"] = range(result.shape.numberNodes) + conf["path"] = plotPath + "/messagesSent.png" + maxi = max(conf["data"]) + conf["yaxismax"] = maxi + plotData(conf) + print("Plot %s created." % conf["path"]) + + def plotMessagesRecv(self, result, plotPath): + """Plots the number of messages received by all nodes""" + conf = {} + text = str(result.shape).split("-") + conf["textBox"] = "Block Size: "+text[1]+"\nNumber of nodes: "+text[3]\ + +"\nFailure rate: "+text[7]+" \nNetwork degree: "+text[23]+"\nX: "+text[11]+" rows/columns" + conf["title"] = "Number of Messages Received by Nodes" + conf["type"] = "individual_bar" + conf["legLoc"] = 1 + conf["desLoc"] = 1 + conf["xlabel"] = "Nodes" + conf["ylabel"] = "Number of Messages Received" + conf["data"] = result.msgRecvCount + conf["xdots"] = range(result.shape.numberNodes) + conf["path"] = plotPath + "/messagesRecv.png" + maxi = max(conf["data"]) + conf["yaxismax"] = maxi + plotData(conf) + print("Plot %s created." % conf["path"]) + diff --git a/smallConf.py b/smallConf.py index 7ab3f44..e1c9125 100644 --- a/smallConf.py +++ b/smallConf.py @@ -45,19 +45,22 @@ numJobs = -1 evenLineDistribution = True # Number of simulation runs with the same parameters for statistical relevance -runs = range(3) +runs = [1] # Number of validators -numberNodes = range(128, 513, 128) +numberNodes = [1024] # select failure model between: "random, sequential, MEP, MEP+1, DEP, DEP+1, MREP, MREP-1" failureModels = ["random"] # Percentage of block not released by producer -failureRates = range(40, 81, 20) +failureRates = [0] + +# Percentage of nodes that are considered malicious +maliciousNodes = [95] # Block size in one dimension in segments. Block is blockSizes * blockSizes segments. -blockSizes = range(64, 113, 128) +blockSizes = [128] # Per-topic mesh neighborhood size netDegrees = range(8, 9, 2) @@ -69,8 +72,8 @@ chis = range(2, 3, 2) class1ratios = [0.8] # Number of validators per beacon node -validatorsPerNode1 = [1] -validatorsPerNode2 = [500] +validatorsPerNode1 = [10] +validatorsPerNode2 = [250] # Set uplink bandwidth in megabits/second bwUplinksProd = [200] @@ -102,9 +105,9 @@ diagnostics = False saveGit = False def nextShape(): - for run, fm, fr, class1ratio, chi, vpn1, vpn2, blockSize, nn, netDegree, bwUplinkProd, bwUplink1, bwUplink2 in itertools.product( - runs, failureModels, failureRates, class1ratios, chis, validatorsPerNode1, validatorsPerNode2, blockSizes, numberNodes, netDegrees, bwUplinksProd, bwUplinks1, bwUplinks2): + for run, fm, fr, mn, class1ratio, chi, vpn1, vpn2, blockSize, nn, netDegree, bwUplinkProd, bwUplink1, bwUplink2 in itertools.product( + runs, failureModels, failureRates, maliciousNodes, class1ratios, chis, validatorsPerNode1, validatorsPerNode2, blockSizes, numberNodes, netDegrees, bwUplinksProd, bwUplinks1, bwUplinks2): # Network Degree has to be an even number if netDegree % 2 == 0: - shape = Shape(blockSize, nn, fm, fr, class1ratio, chi, vpn1, vpn2, netDegree, bwUplinkProd, bwUplink1, bwUplink2, run) + shape = Shape(blockSize, nn, fm, fr, mn, class1ratio, chi, vpn1, vpn2, netDegree, bwUplinkProd, bwUplink1, bwUplink2, run) yield shape