Processing math: 100%

Simulator

Below is the main function of the simulator. The parameters relate to the discussions in the paper as follows:

Parameter Meaning
initDifficulty default difficulty
initReward default reward
numCompetitions number of competitions/blocks for the simulation
idealBlockTime the target blocktime ˜tb
idealNetworkPower the target network power ˜w
priceList a list of length numCompetitions containing the prices x of the token.
nodePowers a list of powers wi of each node i=1n
nodeCFs a list of cost factors ci of each node i=1n
difficultyAdaptationRate adaptation rate αd
rewardAdaptationRate adaptation rate αr
adaptationStrategy “adaptive” if the controller is adaptive as per the paper, “fixed” if there is no adaptation.
1
2
3
4
5
6
7
8
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
simulateNetworkBasic <- function(
  initDifficulty,
  initReward,
  numCompetitions,
  idealBlockTime,
  idealNetworkPower,
  priceList = NULL,
  nodePowers = NULL, 
  nodeCFs = NULL, 
  difficultyAdaptationRate = 0.1,
  rewardAdaptationRate = 0.1,
  adaptationStrategy = "adaptive"){

  numNodes = length(nodePowers)
  
  #
  # Results List
  #
  difficultyList = NULL
  rewardList = NULL
  blockTimeList = NULL
  powerList = NULL
  participationList = NULL
  
  # play is as long as the number of nodes and flags which node will play the next round
  play = numeric()
  
  reward = initReward
  difficulty = initDifficulty
  networkPower = sum(nodePowers)
  
  print(paste0("Running ", numCompetitions, " competitions for ", numNodes))
  pb <- txtProgressBar(1, numCompetitions, style=3)
  
  for (i in 1:numCompetitions){
    
    #
    # Each node decides whether to participate.
    #
    for (j in 1:numNodes){
      bkEvenR = getBreakEvenReward (d = difficulty,
                                    x = priceList[i],
                                    c = nodeCFs[j],
                                    N = (networkPower-nodePowers[j])/nodePowers[j]
      )

    if (reward > bkEvenR){
        play[j] = 1
      } else {
        play[j] = -1
      }
    }

        
    numParticipants = sum(play[play==1])
    networkPower = sum(nodePowers[play==1]) # Full observability
    
    #
    # If the network power is zero then the network has simply collapsed.
    # [we choose to end simulation for that]
    #
    if (numParticipants == 0) {
      print("Network Collapsed.")
      return(data.frame(Difficulty = difficultyList,
                        BlockTime = blockTimeList,
                        Reward = rewardList,
                        Price = priceList[1:length(difficultyList)],
                        NetPower = powerList,
                        Participation = participationList
      ))
    }
    
    
    blockTime = difficulty/networkPower
    
    # Capture Statistics
    difficultyList = c(difficultyList,difficulty)
    rewardList = c(rewardList,reward)
    powerList = c(powerList,networkPower)
    blockTimeList = c(blockTimeList,blockTime)
    participationList = c(participationList,numParticipants)
    
    
    #
    # Netowrk Adapts Reward and Difficulty.
    #
    
    # Adjust d
    if (adaptationStrategy == "fixed"){
      # Do nothing
    } else {
      newDiff = idealBlockTime * networkPower
      difficulty = difficulty + (newDiff - difficulty)*difficultyAdaptationRate
    }
    
    # Adjust r
    if (adaptationStrategy == "fixed"){
      # Do nothing
    } else {
      newR = getOptimalReward(difficulty = difficulty,
                              price = priceList[i],
                              nodePowers = nodePowers,
                              nodeCFs = nodeCFs,
                              networkPower = networkPower,
                              idealPower = idealNetworkPower)
      reward = reward + (newR - reward)*rewardAdaptationRate          
    }
    setTxtProgressBar(pb,i)
  }
  
  return(data.frame(Difficulty = difficultyList,
                    BlockTime = blockTimeList,
                    Reward = rewardList,
                    Price = priceList,
                    NetPower = powerList,
                    Participation = participationList
  ))
  
}

Adaptation in the code is taking place in lines 84-107 according to formulae dnext=dcurrent+(dtargetdcurrent)αd and ernext=ercurrent+(ertargetercurrent)αer

Please also node that the controller does not make any estimations but has somehow full awareness of the nodes, their powers (wi, nodePowers) and cost factors (ci, nodeCfs) and who participates captured in the play list (lines 47-52) following a transparent cost-benefit analysis (lines 40-45).

For the cost benefit analysis the following routine is used:

getBreakEvenReward <- function(d,x,c,N){
  return ( (c*d/x)*(d/(d-1))^N )
}

which is a solution of equation (1) in the paper for er, assuming ef=0.

(er+ef)x=cd(dd1)N

ef=0

er=cdx(dd1)N

Runs

Parameters

The paper mentions 4 runs: 1 with fixed strategy and three (3) with the proposed adaptive one. The corresponding parameters are:

Parameter Value
initDifficulty 3e+06
initReward 12.5
numCompetitions 500
idealBlockTime 600
idealNetworkPower 6000
priceList [see below]
nodePowers [see below]
nodeCFs [see below]
difficultyAdaptationRate 0.01, 0.1 and 0.5 (for fixed case is irrelevant)
rewardAdaptationRate always same as difficultyAdaptationRate
adaptationStrategy “fixed” for the first simulation, “adaptive” for the next three.

For nodePowers and nodeCFs a normally distributed list of 100 nodes is generated with average 100 (trials/sec) and 0.001 (USD/sec) and standard deviations equal to the 20% of the averages.

N = 100
pAvg = 100
pSD = 0.2*pAvg
cAvg = 0.001
cSD = 0.2*cAvg
powers = round(rnorm(n = N,mean = pAvg, sd = pSD),0)
cFs = rnorm(n = N,mean = cAvg, sd = cSD)

The priceList is generated as follows (reps = 500):

initPrice = 168
t =seq(0,10,10/reps)
t = t[1:(length(t)-1)]
priceList = initPrice + 0.3*initPrice*sin(t)

The initial price is found empirically such that a majority of nodes are close to their break-even point. 500 samples of a sinusoidal disturbance with width up to 30% of that price is added.

Results

The following data sets are produced:

Run Data
Fixed Fixed.csv
Adaptive (α=0.01) Alpha001.csv
Adaptive (α=0.1) Alpha01.csv
Adaptive (α=0.5) Alpha05.csv

To produce the visuals the following can be tried:

r1 <- read.csv(url("https://www.yorku.ca/liaskos/Papers/SEAMS2019/Alpha001.csv"))
r2 <- read.csv(url("https://www.yorku.ca/liaskos/Papers/SEAMS2019/Alpha01.csv"))
r3 <- read.csv(url("https://www.yorku.ca/liaskos/Papers/SEAMS2019/Alpha05.csv"))
f <- read.csv(url("https://www.yorku.ca/liaskos/Papers/SEAMS2019/Fixed.csv"))

offset = 50

result = r1
result = result[offset:nrow(result),]
dd <- data.frame(Time = (1:nrow(result)), Value = result$Price, Variable = "Price", Rate = "0.01")
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Reward, Variable = "Reward",Rate = "0.01"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Difficulty, Variable = "Difficulty",Rate = "0.01"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$BlockTime, Variable = "BlockTime", Rate = "0.01"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$NetPower, Variable = "Network Power", Rate = "0.01"))


result = r2
result = result[offset:nrow(result),]
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Price, Variable = "Price", Rate = "0.1"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Reward, Variable = "Reward", Rate = "0.1"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Difficulty, Variable = "Difficulty",Rate = "0.1"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$BlockTime, Variable = "BlockTime", Rate = "0.1"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$NetPower, Variable = "Network Power", Rate = "0.1"))


result = r3
result = result[offset:nrow(result),]
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Price, Variable = "Price", Rate = "0.5"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Reward, Variable = "Reward", Rate = "0.5"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$Difficulty, Variable = "Difficulty",Rate = "0.5"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$BlockTime, Variable = "BlockTime", Rate = "0.5"))
dd = rbind(dd,data.frame(Time = (1:nrow(result)), Value = result$NetPower, Variable = "Network Power", Rate = "0.5"))


dd$Variable = as.factor(dd$Variable)
dd$Rate = as.factor(dd$Rate)
cbPalette <- c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")
p1 <- ggplot(dd, aes(x = Time, y = Value, color = Rate)) + 
    geom_line(size = 0.7) + 
    facet_grid(rows = vars(Variable), scales = "free") + 
    theme(text = element_text(size=13),legend.position = "bottom") +
    scale_colour_manual(values=cbPalette)

Noting that the first 50 stabilization cycles have been trimmed. The graph for the fixed non-adaptive policy can be produced likewise.