Merge pull request #2 from svost/master
[novacoin-seeder.git] / db.h
1 #include <stdint.h>
2 #include <math.h>
3
4 #include <set>
5 #include <map>
6 #include <vector>
7 #include <deque>
8
9 #include "netbase.h"
10 #include "protocol.h"
11 #include "util.h"
12
13 #define MIN_RETRY 1000
14
15 std::string static inline ToString(const CService &ip) {
16   std::string str = ip.ToString();
17   while (str.size() < 22) str += ' ';
18   return str;
19 }
20
21 class CAddrStat {
22 private:
23   float weight;
24   float count;
25   float reliability;
26 public:
27   CAddrStat() : weight(0), count(0), reliability(0) {}
28
29   void Update(bool good, int64 age, double tau) {
30     double f =  exp(-age/tau);
31     reliability = reliability * f + (good ? (1.0-f) : 0);
32     count = count * f + 1;
33     weight = weight * f + (1.0-f);
34   }
35   
36   IMPLEMENT_SERIALIZE (
37     READWRITE(weight);
38     READWRITE(count);
39     READWRITE(reliability);
40   )
41
42   friend class CAddrInfo;
43 };
44
45 class CAddrReport {
46 public:
47   CService ip;
48   int clientVersion;
49   int blocks;
50   double uptime[5];
51   std::string clientSubVersion;
52 };
53
54
55 class CAddrInfo {
56 private:
57   CService ip;
58   uint64_t services;
59   int64 lastTry;
60   int64 ourLastTry;
61   int64 ignoreTill;
62   CAddrStat stat2H;
63   CAddrStat stat8H;
64   CAddrStat stat1D;
65   CAddrStat stat1W;
66   CAddrStat stat1M;
67   int clientVersion;
68   int blocks;
69   int total;
70   int success;
71   std::string clientSubVersion;
72 public:
73   CAddrInfo() : services(0), lastTry(0), ourLastTry(0), ignoreTill(0), clientVersion(0), blocks(0), total(0), success(0) {}
74   
75   CAddrReport GetReport() const {
76     CAddrReport ret;
77     ret.ip = ip;
78     ret.clientVersion = clientVersion;
79     ret.clientSubVersion = clientSubVersion;
80     ret.blocks = blocks;
81     ret.uptime[0] = stat2H.reliability;
82     ret.uptime[1] = stat8H.reliability;
83     ret.uptime[2] = stat1D.reliability;
84     ret.uptime[3] = stat1W.reliability;
85     ret.uptime[4] = stat1M.reliability;
86     return ret;
87   }
88   
89   bool IsGood() const {
90     if (ip.GetPort() != ::nP2Port) return false;
91     if (!(services & NODE_NETWORK)) return false;
92     if (!ip.IsRoutable()) return false;
93     if (clientVersion && clientVersion < 32400) return false;
94
95     if (total <= 3 && success * 2 >= total) return true;
96
97     if (stat2H.reliability > 0.85 && stat2H.count > 2) return true;
98     if (stat8H.reliability > 0.70 && stat8H.count > 4) return true;
99     if (stat1D.reliability > 0.55 && stat1D.count > 8) return true;
100     if (stat1W.reliability > 0.45 && stat1W.count > 16) return true;
101     if (stat1M.reliability > 0.35 && stat1M.count > 32) return true;
102     
103     return false;
104   }
105   int GetBanTime() const {
106     if (IsGood()) return 0;
107     if (clientVersion && clientVersion < 31900) { return 604800; }
108     return 0;
109   }
110   int GetIgnoreTime() const {
111     if (IsGood()) return 0;
112     if (stat1M.reliability - stat1M.weight + 1.0 < 0.08 && stat1D.count > 48) { return 8*3600; }
113     if (stat1W.reliability - stat1W.weight + 1.0 < 0.12 && stat8H.count > 24) { return 4*3600; }
114     if (stat1D.reliability - stat1D.weight + 1.0 < 0.16 && stat1D.count > 12) { return 2*3600; }
115     if (stat8H.reliability - stat8H.weight + 1.0 < 0.20 && stat8H.count > 6)  { return 1*3600; }
116     return 0;
117   }
118   
119   void Update(bool good);
120   
121   friend class CAddrDb;
122   
123   IMPLEMENT_SERIALIZE (
124     unsigned char version = 3;
125     READWRITE(version);
126     READWRITE(ip);
127     READWRITE(services);
128     READWRITE(lastTry);
129     unsigned char tried = ourLastTry != 0;
130     READWRITE(tried);
131     if (tried) {
132       READWRITE(ourLastTry);
133       READWRITE(ignoreTill);
134       READWRITE(stat2H);
135       READWRITE(stat8H);
136       READWRITE(stat1D);
137       READWRITE(stat1W);
138       if (version >= 1)
139           READWRITE(stat1M);
140       else
141           if (!fWrite)
142               *((CAddrStat*)(&stat1M)) = stat1W;
143       READWRITE(total);
144       READWRITE(success);
145       READWRITE(clientVersion);
146       if (version >= 2)
147           READWRITE(clientSubVersion);
148       if (version >= 3)
149           READWRITE(blocks);
150     }
151   )
152 };
153
154 class CAddrDbStats {
155 public:
156   int nBanned;
157   int nAvail;
158   int nTracked;
159   int nNew;
160   int nGood;
161   int nAge;
162 };
163
164 //             seen nodes
165 //            /          \
166 // (a) banned nodes       available nodes--------------
167 //                       /       |                     \
168 //               tracked nodes   (b) unknown nodes   (e) active nodes
169 //              /           \
170 //     (d) good nodes   (c) non-good nodes 
171
172 class CAddrDb {
173 private:
174   mutable CCriticalSection cs;
175   int nId; // number of address id's
176   std::map<int, CAddrInfo> idToInfo; // map address id to address info (b,c,d,e)
177   std::map<CService, int> ipToId; // map ip to id (b,c,d,e)
178   std::deque<int> ourId; // sequence of tried nodes, in order we have tried connecting to them (c,d)
179   std::set<int> unkId; // set of nodes not yet tried (b)
180   std::set<int> goodId; // set of good nodes  (d, good e)
181   int nDirty;
182   
183 protected:
184   // internal routines that assume proper locks are acquired
185   void Add_(const CAddress &addr, bool force);   // add an address
186   bool Get_(CService &ip, int& wait);      // get an IP to test (must call Good_, Bad_, or Skipped_ on result afterwards)
187   void Good_(const CService &ip, int clientV, std::string clientSV, int blocks); // mark an IP as good (must have been returned by Get_)
188   void Bad_(const CService &ip, int ban);  // mark an IP as bad (and optionally ban it) (must have been returned by Get_)
189   void Skipped_(const CService &ip);       // mark an IP as skipped (must have been returned by Get_)
190   int Lookup_(const CService &ip);         // look up id of an IP
191   void GetIPs_(std::set<CNetAddr>& ips, int max, const bool *nets); // get a random set of IPs (shared lock only)
192
193 public:
194   std::map<CService, time_t> banned; // nodes that are banned, with their unban time (a)
195
196   void GetStats(CAddrDbStats &stats) {
197     SHARED_CRITICAL_BLOCK(cs) {
198       stats.nBanned = banned.size();
199       stats.nAvail = idToInfo.size();
200       stats.nTracked = ourId.size();
201       stats.nGood = goodId.size();
202       stats.nNew = unkId.size();
203       stats.nAge = time(NULL) - idToInfo[ourId[0]].ourLastTry;
204     }
205   }
206
207   void ResetIgnores() {
208       for (std::map<int, CAddrInfo>::iterator it = idToInfo.begin(); it != idToInfo.end(); it++) {
209            (*it).second.ignoreTill = 0;
210       }
211   }
212   
213   std::vector<CAddrReport> GetAll() {
214     std::vector<CAddrReport> ret;
215     SHARED_CRITICAL_BLOCK(cs) {
216       for (std::deque<int>::const_iterator it = ourId.begin(); it != ourId.end(); it++) {
217         const CAddrInfo &info = idToInfo[*it];
218         if (info.success > 0) {
219           ret.push_back(info.GetReport());
220         }
221       }
222     }
223     return ret;
224   }
225   
226   // serialization code
227   // format:
228   //   nVersion (0 for now)
229   //   n (number of ips in (b,c,d))
230   //   CAddrInfo[n]
231   //   banned
232   // acquires a shared lock (this does not suffice for read mode, but we assume that only happens at startup, single-threaded)
233   // this way, dumping does not interfere with GetIPs_, which is called from the DNS thread
234   IMPLEMENT_SERIALIZE (({
235     int nVersion = 0;
236     READWRITE(nVersion);
237     SHARED_CRITICAL_BLOCK(cs) {
238       if (fWrite) {
239         CAddrDb *db = const_cast<CAddrDb*>(this);
240         int n = ourId.size() + unkId.size();
241         READWRITE(n);
242         for (std::deque<int>::const_iterator it = ourId.begin(); it != ourId.end(); it++) {
243           std::map<int, CAddrInfo>::iterator ci = db->idToInfo.find(*it);
244           READWRITE((*ci).second);
245         }
246         for (std::set<int>::const_iterator it = unkId.begin(); it != unkId.end(); it++) {
247           std::map<int, CAddrInfo>::iterator ci = db->idToInfo.find(*it);
248           READWRITE((*ci).second);
249         }
250       } else {
251         CAddrDb *db = const_cast<CAddrDb*>(this);
252         db->nId = 0;
253         int n;
254         READWRITE(n);
255         for (int i=0; i<n; i++) {
256           CAddrInfo info;
257           READWRITE(info);
258           if (!info.GetBanTime()) {
259             int id = db->nId++;
260             db->idToInfo[id] = info;
261             db->ipToId[info.ip] = id;
262             if (info.ourLastTry) {
263               db->ourId.push_back(id);
264               if (info.IsGood()) db->goodId.insert(id);
265             } else {
266               db->unkId.insert(id);
267             }
268           }
269         }
270         db->nDirty++;
271       }
272       READWRITE(banned);
273     }
274   });)
275
276   void Add(const CAddress &addr, bool fForce = false) {
277     CRITICAL_BLOCK(cs)
278       Add_(addr, fForce);
279   }
280   void Add(const std::vector<CAddress> &vAddr, bool fForce = false) {
281     CRITICAL_BLOCK(cs)
282       for (int i=0; i<vAddr.size(); i++)
283         Add_(vAddr[i], fForce);
284   }
285   void Good(const CService &addr, int clientVersion, std::string clientSubVersion, int blocks) {
286     CRITICAL_BLOCK(cs)
287       Good_(addr, clientVersion, clientSubVersion, blocks);
288   }
289   void Skipped(const CService &addr) {
290     CRITICAL_BLOCK(cs)
291       Skipped_(addr);
292   }
293   void Bad(const CService &addr, int ban = 0) {
294     CRITICAL_BLOCK(cs)
295       Bad_(addr, ban);
296   }
297   bool Get(CService &ip, int& wait) {
298     CRITICAL_BLOCK(cs)
299       return Get_(ip, wait);
300   }
301   void GetIPs(std::set<CNetAddr>& ips, int max, const bool *nets) {
302     SHARED_CRITICAL_BLOCK(cs)
303       GetIPs_(ips, max, nets);
304   }
305 };