]>
Commit | Line | Data |
---|---|---|
b2a98c42 MT |
1 | /******************************************************************** |
2 | * (C) 2019 Michael Toutonghi | |
3 | * | |
4 | * Distributed under the MIT software license, see the accompanying | |
5 | * file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
6 | * | |
7 | * This provides support for PBaaS cross chain communication. | |
8 | * | |
9 | * In merge mining and notarization, Verus acts as a hub that other PBaaS chains | |
10 | * call via RPC in order to get information that allows earning and submitting | |
11 | * notarizations. | |
12 | * | |
13 | * All PBaaS chains communicate with their primary reserve chain, which is either Verus | |
14 | * or the chain that is their reserve coin. The child PBaaS chain initiates all of | |
15 | * the communication with the parent / reserve daemon. | |
16 | * | |
17 | * Generally, the PBaaS chain will call the Verus chain to either get information needed | |
18 | * to create an earned or accepted notarization. If there is no Verus daemon available | |
19 | * staking and mining of a PBaaS chain proceeds as usual, but without notarization | |
20 | * reward opportunities. | |
21 | * | |
22 | */ | |
23 | ||
24 | #include "chainparamsbase.h" | |
25 | #include "clientversion.h" | |
26 | #include "rpc/client.h" | |
27 | #include "rpc/protocol.h" | |
28 | #include "util.h" | |
29 | #include "utilstrencodings.h" | |
30 | ||
31 | #include <boost/filesystem/operations.hpp> | |
56fe75cb | 32 | #include <boost/format.hpp> |
b2a98c42 MT |
33 | #include <stdio.h> |
34 | ||
35 | #include <event2/buffer.h> | |
36 | #include <event2/keyvalq_struct.h> | |
37 | #include "support/events.h" | |
38 | ||
39 | #include <univalue.h> | |
40 | ||
41 | #include "uint256.h" | |
42 | #include "hash.h" | |
43 | #include "pbaas/crosschainrpc.h" | |
2acb304a | 44 | #include "pbaas/identity.h" |
b2a98c42 MT |
45 | |
46 | using namespace std; | |
47 | ||
9f0c14b2 MT |
48 | extern string PBAAS_HOST; |
49 | extern string PBAAS_USERPASS; | |
50 | extern int32_t PBAAS_PORT; | |
51 | ||
b2a98c42 MT |
52 | // |
53 | // Exception thrown on connection error. This error is used to determine | |
54 | // when to wait if -rpcwait is given. | |
55 | // | |
56 | class CConnectionFailed : public std::runtime_error | |
57 | { | |
58 | public: | |
59 | ||
60 | explicit inline CConnectionFailed(const std::string& msg) : | |
61 | std::runtime_error(msg) | |
62 | {} | |
63 | ||
64 | }; | |
65 | ||
66 | /** Reply structure for request_done to fill in */ | |
67 | struct HTTPReply | |
68 | { | |
69 | HTTPReply(): status(0), error(-1) {} | |
70 | ||
71 | int status; | |
72 | int error; | |
73 | std::string body; | |
74 | }; | |
75 | ||
76 | const char *http_errorstring(int code) | |
77 | { | |
78 | switch(code) { | |
79 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
80 | case EVREQ_HTTP_TIMEOUT: | |
81 | return "timeout reached"; | |
82 | case EVREQ_HTTP_EOF: | |
83 | return "EOF reached"; | |
84 | case EVREQ_HTTP_INVALID_HEADER: | |
85 | return "error while reading header, or invalid header"; | |
86 | case EVREQ_HTTP_BUFFER_ERROR: | |
87 | return "error encountered while reading or writing"; | |
88 | case EVREQ_HTTP_REQUEST_CANCEL: | |
89 | return "request was canceled"; | |
90 | case EVREQ_HTTP_DATA_TOO_LONG: | |
91 | return "response body is larger than allowed"; | |
92 | #endif | |
93 | default: | |
94 | return "unknown"; | |
95 | } | |
96 | } | |
97 | ||
98 | static void http_request_done(struct evhttp_request *req, void *ctx) | |
99 | { | |
100 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
101 | ||
102 | if (req == NULL) { | |
103 | /* If req is NULL, it means an error occurred while connecting: the | |
104 | * error code will have been passed to http_error_cb. | |
105 | */ | |
106 | reply->status = 0; | |
107 | return; | |
108 | } | |
109 | ||
110 | reply->status = evhttp_request_get_response_code(req); | |
111 | ||
112 | struct evbuffer *buf = evhttp_request_get_input_buffer(req); | |
113 | if (buf) | |
114 | { | |
115 | size_t size = evbuffer_get_length(buf); | |
116 | const char *data = (const char*)evbuffer_pullup(buf, size); | |
117 | if (data) | |
118 | reply->body = std::string(data, size); | |
119 | evbuffer_drain(buf, size); | |
120 | } | |
121 | } | |
122 | ||
123 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
124 | static void http_error_cb(enum evhttp_request_error err, void *ctx) | |
125 | { | |
126 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
127 | reply->error = err; | |
128 | } | |
129 | #endif | |
130 | ||
9f0c14b2 | 131 | static CCrossChainRPCData LoadFromConfig(std::string name) |
b2a98c42 | 132 | { |
9f0c14b2 MT |
133 | map<string, string> settings; |
134 | map<string, vector<string>> settingsmulti; | |
135 | CCrossChainRPCData ret; | |
136 | ||
137 | // if we are requested to automatically load the information from the Verus chain, do it if we can find the daemon | |
138 | if (ReadConfigFile(name, settings, settingsmulti)) | |
139 | { | |
140 | auto rpcuser = settings.find("-rpcuser"); | |
141 | auto rpcpwd = settings.find("-rpcpassword"); | |
142 | auto rpcport = settings.find("-rpcport"); | |
143 | auto rpchost = settings.find("-rpchost"); | |
144 | ret.credentials = rpcuser != settings.end() ? rpcuser->second + ":" : ""; | |
145 | ret.credentials += rpcpwd != settings.end() ? rpcpwd->second : ""; | |
146 | ret.port = rpcport != settings.end() ? atoi(rpcport->second) : (name == "VRSC" ? 27486 : 0); | |
147 | ret.host = rpchost != settings.end() ? rpchost->second : "127.0.0.1"; | |
148 | } | |
149 | return ret; | |
b2a98c42 MT |
150 | } |
151 | ||
152 | // credentials for now are "user:password" | |
153 | UniValue RPCCall(const string& strMethod, const UniValue& params, const string credentials, int port, const string host, int timeout) | |
154 | { | |
155 | // Used for inter-daemon communicatoin to enable merge mining and notarization without a client | |
156 | // | |
157 | ||
158 | // Obtain event base | |
159 | raii_event_base base = obtain_event_base(); | |
160 | ||
161 | // Synchronously look up hostname | |
162 | raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port); | |
163 | evhttp_connection_set_timeout(evcon.get(), timeout); | |
164 | ||
165 | HTTPReply response; | |
166 | raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response); | |
167 | if (req == NULL) | |
168 | throw std::runtime_error("create http request failed"); | |
169 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
170 | evhttp_request_set_error_cb(req.get(), http_error_cb); | |
171 | #endif | |
172 | ||
173 | struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get()); | |
174 | assert(output_headers); | |
175 | evhttp_add_header(output_headers, "Host", host.c_str()); | |
176 | evhttp_add_header(output_headers, "Connection", "close"); | |
177 | evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(credentials)).c_str()); | |
178 | ||
179 | // Attach request data | |
180 | std::string strRequest = JSONRPCRequest(strMethod, params, 1); | |
181 | struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get()); | |
182 | assert(output_buffer); | |
183 | evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); | |
184 | ||
185 | int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); | |
186 | req.release(); // ownership moved to evcon in above call | |
187 | if (r != 0) { | |
188 | throw CConnectionFailed("send http request failed"); | |
189 | } | |
190 | ||
191 | event_base_dispatch(base.get()); | |
192 | ||
193 | if (response.status == 0) | |
194 | throw CConnectionFailed(strprintf("couldn't connect to server: %s (code %d)\n(make sure server is running and you are connecting to the correct RPC port)", http_errorstring(response.error), response.error)); | |
195 | else if (response.status == HTTP_UNAUTHORIZED) | |
196 | throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); | |
197 | else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) | |
198 | throw std::runtime_error(strprintf("server returned HTTP error %d", response.status)); | |
199 | else if (response.body.empty()) | |
200 | throw std::runtime_error("no response from server"); | |
201 | ||
202 | // Parse reply | |
203 | UniValue valReply(UniValue::VSTR); | |
204 | if (!valReply.read(response.body)) | |
205 | throw std::runtime_error("couldn't parse reply from server"); | |
206 | const UniValue& reply = valReply.get_obj(); | |
207 | if (reply.empty()) | |
208 | throw std::runtime_error("expected reply to have result, error and id properties"); | |
209 | ||
210 | return reply; | |
211 | } | |
9f0c14b2 | 212 | |
2156d3d0 | 213 | UniValue RPCCallRoot(const string& strMethod, const UniValue& params, int timeout) |
9f0c14b2 MT |
214 | { |
215 | string host, credentials; | |
216 | int port; | |
217 | map<string, string> settings; | |
218 | map<string, vector<string>> settingsmulti; | |
219 | ||
220 | if (PBAAS_HOST != "" && PBAAS_PORT != 0) | |
221 | { | |
222 | return RPCCall(strMethod, params, PBAAS_USERPASS, PBAAS_PORT, PBAAS_HOST); | |
223 | } | |
224 | else if (ReadConfigFile(PBAAS_TESTMODE ? "VRSCTEST" : "VRSC", settings, settingsmulti)) | |
225 | { | |
7af5cf39 | 226 | PBAAS_USERPASS = settingsmulti.find("-rpcuser")->second[0] + ":" + settingsmulti.find("-rpcpassword")->second[0]; |
227 | PBAAS_PORT = atoi(settingsmulti.find("-rpcport")->second[0]); | |
228 | PBAAS_HOST = settingsmulti.find("-rpchost")->second[0]; | |
229 | if (!PBAAS_HOST.size()) | |
9f0c14b2 | 230 | { |
7af5cf39 | 231 | PBAAS_HOST = "127.0.0.1"; |
9f0c14b2 | 232 | } |
2156d3d0 | 233 | return RPCCall(strMethod, params, credentials, port, host, timeout); |
9f0c14b2 MT |
234 | } |
235 | return UniValue(UniValue::VNULL); | |
236 | } | |
f8f61a6d | 237 | |
b7c685b8 | 238 | bool uni_get_bool(UniValue uv, bool def) |
239 | { | |
240 | try | |
241 | { | |
996f6c33 | 242 | if (uv.isStr()) |
243 | { | |
244 | std::string boolStr; | |
245 | if ((boolStr = uni_get_str(uv, def ? "true" : "false")) == "true" || boolStr == "1") | |
246 | { | |
247 | return true; | |
248 | } | |
249 | else if (boolStr == "false" || boolStr == "0") | |
250 | { | |
251 | return false; | |
252 | } | |
253 | return def; | |
254 | } | |
255 | else | |
256 | { | |
257 | return uv.get_bool(); | |
258 | } | |
b7c685b8 | 259 | } |
260 | catch(const std::exception& e) | |
261 | { | |
262 | return def; | |
263 | } | |
264 | } | |
265 | ||
f8f61a6d | 266 | int32_t uni_get_int(UniValue uv, int32_t def) |
267 | { | |
268 | try | |
269 | { | |
270 | return uv.get_int(); | |
271 | } | |
272 | catch(const std::exception& e) | |
273 | { | |
274 | return def; | |
275 | } | |
276 | } | |
277 | ||
278 | int64_t uni_get_int64(UniValue uv, int64_t def) | |
279 | { | |
280 | try | |
281 | { | |
282 | return uv.get_int64(); | |
283 | } | |
284 | catch(const std::exception& e) | |
285 | { | |
286 | return def; | |
287 | } | |
288 | } | |
289 | ||
290 | std::string uni_get_str(UniValue uv, std::string def) | |
291 | { | |
292 | try | |
293 | { | |
294 | return uv.get_str(); | |
295 | } | |
296 | catch(const std::exception& e) | |
297 | { | |
298 | return def; | |
299 | } | |
300 | } | |
301 | ||
302 | std::vector<UniValue> uni_getValues(UniValue uv, std::vector<UniValue> def) | |
303 | { | |
304 | try | |
305 | { | |
306 | return uv.getValues(); | |
307 | } | |
308 | catch(const std::exception& e) | |
309 | { | |
310 | return def; | |
311 | } | |
312 | } | |
313 | ||
ef54c6e1 | 314 | UniValue CCrossChainRPCData::ToUniValue() const |
315 | { | |
316 | UniValue obj(UniValue::VOBJ); | |
317 | obj.push_back(Pair("host", host)); | |
318 | obj.push_back(Pair("port", port)); | |
319 | obj.push_back(Pair("credentials", credentials)); | |
320 | return obj; | |
321 | } | |
322 | ||
56fe75cb | 323 | CNodeData::CNodeData(const UniValue &obj) |
324 | { | |
325 | networkAddress = uni_get_str(find_value(obj, "networkaddress")); | |
326 | CTxDestination dest = DecodeDestination(uni_get_str(find_value(obj, "nodeidentity"))); | |
327 | if (dest.which() != COptCCParams::ADDRTYPE_ID) | |
328 | { | |
329 | nodeIdentity = uint160(); | |
330 | } | |
331 | else | |
332 | { | |
333 | nodeIdentity = GetDestinationID(dest); | |
334 | } | |
335 | } | |
336 | ||
337 | CNodeData::CNodeData(std::string netAddr, std::string paymentAddr) : | |
338 | networkAddress(netAddr) | |
339 | { | |
340 | nodeIdentity = GetDestinationID(DecodeDestination(paymentAddr)); | |
341 | } | |
342 | ||
343 | UniValue CNodeData::ToUniValue() const | |
344 | { | |
345 | UniValue obj(UniValue::VOBJ); | |
346 | obj.push_back(Pair("networkaddress", networkAddress)); | |
347 | obj.push_back(Pair("nodeidentity", EncodeDestination(CIdentityID(nodeIdentity)))); | |
348 | return obj; | |
349 | } | |
350 | ||
2f416b17 | 351 | uint160 CurrencyNameToChainID(std::string currencyStr) |
352 | { | |
353 | std::string extraName; | |
354 | uint160 retVal; | |
355 | currencyStr = TrimSpaces(currencyStr); | |
356 | if (!currencyStr.size()) | |
357 | { | |
358 | return retVal; | |
359 | } | |
360 | ParseSubNames(currencyStr, extraName, true); | |
361 | if (currencyStr.back() == '@' || extraName != "") | |
362 | { | |
363 | return retVal; | |
364 | } | |
365 | CTxDestination currencyDest = DecodeDestination(currencyStr); | |
366 | if (currencyDest.which() == COptCCParams::ADDRTYPE_INVALID) | |
367 | { | |
368 | currencyDest = DecodeDestination(currencyStr + "@"); | |
369 | } | |
370 | if (currencyDest.which() != COptCCParams::ADDRTYPE_INVALID) | |
371 | { | |
372 | retVal = GetDestinationID(currencyDest); | |
373 | } | |
374 | return retVal; | |
375 | } | |
56fe75cb | 376 | |
377 | CCurrencyDefinition::CCurrencyDefinition(const UniValue &obj) | |
378 | { | |
379 | try | |
380 | { | |
381 | nVersion = PBAAS_VERSION; | |
382 | name = std::string(uni_get_str(find_value(obj, "name")), 0, (KOMODO_ASSETCHAIN_MAXLEN - 1)); | |
56fe75cb | 383 | |
384 | std::string parentStr = uni_get_str(find_value(obj, "parent")); | |
2f416b17 | 385 | if (parentStr != "") |
56fe75cb | 386 | { |
387 | CTxDestination parentDest = DecodeDestination(parentStr); | |
388 | parent = GetDestinationID(parentDest); | |
389 | // if we have a destination, but it is invalid, the json for this definition cannot be valid | |
390 | if (parentDest.which() == COptCCParams::ADDRTYPE_INVALID) | |
391 | { | |
392 | nVersion = PBAAS_VERSION_INVALID; | |
393 | } | |
394 | } | |
395 | ||
2f416b17 | 396 | name = CleanName(name, parent); |
397 | ||
56fe75cb | 398 | options = (uint32_t)uni_get_int64(find_value(obj, "options")); |
399 | ||
3b45a268 | 400 | idRegistrationAmount = AmountFromValueNoErr(find_value(obj, "idregistrationprice")); |
56fe75cb | 401 | idReferralLevels = uni_get_int(find_value(obj, "idreferrallevels")); |
402 | ||
403 | UniValue notaryArr = find_value(obj, "notaries"); | |
8f02d50d | 404 | minNotariesConfirm = 0; |
56fe75cb | 405 | if (notaryArr.isArray()) |
406 | { | |
407 | for (int i = 0; i < notaryArr.size(); i++) | |
408 | { | |
409 | CIdentityID notaryID; | |
410 | CTxDestination notaryDest = DecodeDestination(uni_get_str(notaryArr[i])); | |
411 | notaryID = GetDestinationID(notaryDest); | |
412 | // if we have a destination, but it is invalid, the json for this definition cannot be valid | |
413 | if (notaryID.IsNull()) | |
414 | { | |
415 | nVersion = PBAAS_VERSION_INVALID; | |
416 | } | |
417 | else | |
418 | { | |
419 | notaries.push_back(notaryID); | |
420 | } | |
421 | } | |
422 | minNotariesConfirm = uni_get_int(find_value(obj, "minnotariesconfirm")); | |
423 | } | |
424 | billingPeriod = uni_get_int(find_value(obj, "billingperiod")); | |
3b45a268 | 425 | notarizationReward = AmountFromValueNoErr(find_value(obj, "notarizationreward")); |
56fe75cb | 426 | |
427 | startBlock = uni_get_int(find_value(obj, "startblock")); | |
428 | endBlock = uni_get_int(find_value(obj, "endblock")); | |
429 | ||
2f416b17 | 430 | proofProtocol = (EProofProtocol)uni_get_int(find_value(obj, "proofprotocol"), (int32_t)PROOF_PBAASMMR); |
431 | notarizationProtocol = (ENotarizationProtocol)uni_get_int(find_value(obj, "notarizationprotocol"), (int32_t)NOTARIZATION_AUTO); | |
3b45a268 | 432 | int32_t totalReserveWeight = AmountFromValueNoErr(find_value(obj, "reserveratio")); |
56fe75cb | 433 | UniValue currencyArr = find_value(obj, "currencies"); |
434 | UniValue weightArr = find_value(obj, "weights"); | |
435 | UniValue conversionArr = find_value(obj, "conversions"); | |
436 | UniValue minPreconvertArr = find_value(obj, "minpreconversion"); | |
437 | UniValue maxPreconvertArr = find_value(obj, "maxpreconversion"); | |
438 | UniValue initialContributionArr = find_value(obj, "initialcontributions"); | |
439 | UniValue preConversionsArr = find_value(obj, "preconversions"); | |
440 | ||
441 | bool convertible = false; | |
442 | ||
443 | if (currencyArr.isArray() && currencyArr.size()) | |
444 | { | |
445 | // if weights are defined, make sure they are defined correctly, if not, define them | |
446 | if (weightArr.isArray() && weightArr.size()) | |
447 | { | |
448 | if (weightArr.size() != currencyArr.size()) | |
449 | { | |
450 | LogPrintf("%s: reserve currency weights must be specified for all currencies\n", __func__); | |
451 | nVersion = PBAAS_VERSION_INVALID; | |
452 | } | |
453 | else | |
454 | { | |
455 | for (int i = 0; i < currencyArr.size(); i++) | |
456 | { | |
3b45a268 | 457 | int32_t weight = (int32_t)AmountFromValueNoErr(weightArr[i]); |
56fe75cb | 458 | if (weight <= 0) |
459 | { | |
460 | nVersion = PBAAS_VERSION_INVALID; | |
461 | break; | |
462 | } | |
463 | weights.push_back(weight); | |
464 | } | |
465 | } | |
466 | } | |
467 | else if (totalReserveWeight) | |
468 | { | |
469 | uint32_t oneWeight = totalReserveWeight / SATOSHIDEN; | |
470 | uint32_t mod = totalReserveWeight % SATOSHIDEN; | |
471 | for (int i = 0; i < currencyArr.size(); i++) | |
472 | { | |
473 | // distribute remainder of weight among first come currencies | |
474 | int32_t weight = oneWeight; | |
475 | if (mod > 0) | |
476 | { | |
477 | weight++; | |
478 | mod--; | |
479 | } | |
480 | weights.push_back(weight); | |
481 | } | |
482 | } | |
483 | ||
484 | // if we have weights, we can be a reserve currency | |
485 | if (weights.size()) | |
486 | { | |
487 | // if we are a reserve currency, explicit conversion values are not valid | |
488 | // and are based on non-zero, initial contributions relative to supply | |
489 | if ((conversionArr.isArray() && conversionArr.size()) || | |
490 | !initialContributionArr.isArray() || | |
491 | initialContributionArr.size() != currencyArr.size() || | |
492 | weights.size() != currencyArr.size() || | |
493 | !IsReserve()) | |
494 | { | |
495 | LogPrintf("%s: reserve currencies must have weights, initial contributions in every currency, and no explicit conversion rates\n", __func__); | |
496 | nVersion = PBAAS_VERSION_INVALID; | |
497 | } | |
498 | else | |
499 | { | |
500 | convertible = true; | |
501 | } | |
502 | } | |
503 | else | |
504 | { | |
505 | // if we are not a reserve currency, we either have a conversion vector, or we are not convertible at all | |
506 | if (conversionArr.isArray() && conversionArr.size() && conversionArr.size() != currencyArr.size()) | |
507 | { | |
508 | LogPrintf("%s: non-reserve currencies must define all conversion rates for supported currencies if they define any\n", __func__); | |
509 | nVersion = PBAAS_VERSION_INVALID; | |
510 | } | |
511 | else if (initialContributionArr.isArray() && initialContributionArr.size() != currencyArr.size()) | |
512 | { | |
513 | LogPrintf("%s: initial contributions for currencies must all be specified if any are specified\n", __func__); | |
514 | nVersion = PBAAS_VERSION_INVALID; | |
515 | } | |
516 | else | |
517 | { | |
518 | convertible = true; | |
519 | } | |
520 | } | |
521 | ||
522 | if (convertible && nVersion != PBAAS_VERSION_INVALID) | |
523 | { | |
524 | if (minPreconvertArr.isArray() && minPreconvertArr.size() && minPreconvertArr.size() != currencyArr.size()) | |
525 | { | |
526 | LogPrintf("%s: currencies with minimum conversion required must define all minimums if they define any\n", __func__); | |
527 | nVersion = PBAAS_VERSION_INVALID; | |
528 | } | |
529 | if (maxPreconvertArr.isArray() && maxPreconvertArr.size() && maxPreconvertArr.size() != currencyArr.size()) | |
530 | { | |
531 | LogPrintf("%s: currencies that include maximum conversions on pre-launch must specify all maximums\n", __func__); | |
532 | nVersion = PBAAS_VERSION_INVALID; | |
533 | } | |
534 | if (initialContributionArr.isArray() && initialContributionArr.size() && initialContributionArr.size() != currencyArr.size()) | |
535 | { | |
536 | LogPrintf("%s: currencies that include initial contributions in one currency on pre-launch must specify all currency amounts\n", __func__); | |
537 | nVersion = PBAAS_VERSION_INVALID; | |
538 | } | |
539 | } | |
540 | ||
541 | bool isInitialContributions = initialContributionArr.isArray() && initialContributionArr.size(); | |
542 | bool isPreconvertMin = minPreconvertArr.isArray() && minPreconvertArr.size(); | |
543 | bool isPreconvertMax = maxPreconvertArr.isArray() && maxPreconvertArr.size(); | |
544 | bool explicitConversions = conversionArr.isArray() && conversionArr.size(); | |
545 | bool havePreConversions = preConversionsArr.isArray() && preConversionsArr.size(); | |
546 | ||
547 | if (IsReserve() && explicitConversions) | |
548 | { | |
549 | LogPrintf("%s: cannot specify explicit conversion values for reserve currencies\n", __func__); | |
550 | nVersion = PBAAS_VERSION_INVALID; | |
551 | } | |
552 | ||
553 | for (int i = 0; nVersion != PBAAS_VERSION_INVALID && i < currencyArr.size(); i++) | |
554 | { | |
2f416b17 | 555 | uint160 currencyID = CurrencyNameToChainID(uni_get_str(currencyArr[i])); |
56fe75cb | 556 | // if we have a destination, but it is invalid, the json for this definition cannot be valid |
557 | if (currencyID.IsNull()) | |
558 | { | |
559 | nVersion = PBAAS_VERSION_INVALID; | |
560 | break; | |
561 | } | |
562 | else | |
563 | { | |
564 | currencies.push_back(currencyID); | |
565 | } | |
566 | ||
567 | if (isInitialContributions) | |
568 | { | |
3b45a268 | 569 | int64_t contrib = AmountFromValueNoErr(initialContributionArr[i]); |
56fe75cb | 570 | if (IsReserve() && contrib < MIN_RESERVE_CONTRIBUTION) |
571 | { | |
572 | LogPrintf("%s: all fractional reserve currencies must start with %s minimum initial contribution in each currency\n", __func__, ValueFromAmount(MIN_RESERVE_CONTRIBUTION).write().c_str()); | |
573 | nVersion = PBAAS_VERSION_INVALID; | |
574 | break; | |
575 | } | |
576 | contributions.push_back(contrib); | |
577 | preconverted.push_back(contrib); | |
578 | } | |
579 | ||
580 | if (havePreConversions && i < preConversionsArr.size()) | |
581 | { | |
3b45a268 | 582 | int64_t contrib = AmountFromValueNoErr(preConversionsArr[i]); |
56fe75cb | 583 | preconverted.push_back(contrib); |
584 | } | |
585 | ||
586 | int64_t minPre = 0; | |
587 | if (isPreconvertMin) | |
588 | { | |
3b45a268 | 589 | minPre = AmountFromValueNoErr(minPreconvertArr[i]); |
56fe75cb | 590 | if (minPre < 0) |
591 | { | |
592 | LogPrintf("%s: minimum preconversions for any currency may not be less than 0\n", __func__); | |
593 | nVersion = PBAAS_VERSION_INVALID; | |
594 | break; | |
595 | } | |
596 | minPreconvert.push_back(minPre); | |
597 | } | |
598 | if (isPreconvertMax) | |
599 | { | |
3b45a268 | 600 | int64_t maxPre = AmountFromValueNoErr(maxPreconvertArr[i]); |
56fe75cb | 601 | if (maxPre < 0 || maxPre < minPre) |
602 | { | |
603 | LogPrintf("%s: maximum preconversions for any currency may not be less than 0 or minimum\n", __func__); | |
604 | nVersion = PBAAS_VERSION_INVALID; | |
605 | break; | |
606 | } | |
607 | maxPreconvert.push_back(maxPre); | |
608 | } | |
609 | if (explicitConversions) | |
610 | { | |
3b45a268 | 611 | int64_t conversion = AmountFromValueNoErr(conversionArr[i]); |
56fe75cb | 612 | if (conversion < 0) |
613 | { | |
614 | LogPrintf("%s: conversions for any currency must be greater than 0\n", __func__); | |
615 | nVersion = PBAAS_VERSION_INVALID; | |
616 | break; | |
617 | } | |
618 | conversions.push_back(conversion); | |
619 | } | |
620 | } | |
621 | } | |
622 | ||
3b45a268 | 623 | preAllocationRatio = AmountFromValueNoErr(find_value(obj, "preallocationratio")); |
56fe75cb | 624 | |
625 | UniValue preallocationArr = find_value(obj, "preallocation"); | |
626 | if (preallocationArr.isArray()) | |
627 | { | |
628 | for (int i = 0; i < preallocationArr.size(); i++) | |
629 | { | |
630 | std::vector<std::string> preallocationKey = preallocationArr[i].getKeys(); | |
631 | std::vector<UniValue> preallocationValue = preallocationArr[i].getValues(); | |
632 | if (preallocationKey.size() != 1 || preallocationValue.size() != 1) | |
633 | { | |
634 | LogPrintf("%s: each preallocation entry must contain one destination identity and one amount\n", __func__); | |
2f416b17 | 635 | printf("%s: each preallocation entry must contain one destination identity and one amount\n", __func__); |
56fe75cb | 636 | nVersion = PBAAS_VERSION_INVALID; |
637 | break; | |
638 | } | |
639 | ||
640 | CTxDestination preallocDest = DecodeDestination(preallocationKey[0]); | |
31609f35 | 641 | |
2f416b17 | 642 | if (preallocDest.which() != COptCCParams::ADDRTYPE_ID && preallocDest.which() != COptCCParams::ADDRTYPE_INVALID) |
56fe75cb | 643 | { |
2f416b17 | 644 | LogPrintf("%s: preallocation destination must be an identity\n", __func__); |
56fe75cb | 645 | nVersion = PBAAS_VERSION_INVALID; |
646 | break; | |
647 | } | |
648 | ||
3b45a268 | 649 | CAmount preAllocAmount = AmountFromValueNoErr(preallocationValue[0]); |
2f416b17 | 650 | if (preAllocAmount <= 0) |
56fe75cb | 651 | { |
2f416b17 | 652 | LogPrintf("%s: preallocation values must be greater than zero\n", __func__); |
56fe75cb | 653 | nVersion = PBAAS_VERSION_INVALID; |
654 | break; | |
655 | } | |
656 | preAllocation.push_back(make_pair(CIdentityID(GetDestinationID(preallocDest)), preAllocAmount)); | |
657 | } | |
658 | } | |
659 | ||
660 | auto vEras = uni_getValues(find_value(obj, "eras")); | |
661 | if (vEras.size() > ASSETCHAINS_MAX_ERAS) | |
662 | { | |
663 | vEras.resize(ASSETCHAINS_MAX_ERAS); | |
664 | } | |
665 | ||
666 | for (auto era : vEras) | |
667 | { | |
668 | rewards.push_back(uni_get_int64(find_value(era, "reward"))); | |
669 | rewardsDecay.push_back(uni_get_int64(find_value(era, "decay"))); | |
670 | halving.push_back(uni_get_int64(find_value(era, "halving"))); | |
671 | eraEnd.push_back(uni_get_int64(find_value(era, "eraend"))); | |
672 | } | |
673 | } | |
674 | catch (exception e) | |
675 | { | |
676 | LogPrintf("%s: exception reading currency definition JSON\n", __func__, e.what()); | |
677 | nVersion = PBAAS_VERSION_INVALID; | |
678 | } | |
679 | } | |
680 | ||
681 | int64_t CCurrencyDefinition::CalculateRatioOfValue(int64_t value, int64_t ratio) | |
682 | { | |
683 | arith_uint256 bigAmount(value); | |
684 | static const arith_uint256 bigSatoshi(SATOSHIDEN); | |
685 | ||
686 | int64_t retVal = ((bigAmount * arith_uint256(ratio)) / bigSatoshi).GetLow64(); | |
687 | return retVal; | |
688 | } | |
689 | ||
690 | // this will only return an accurate result after total preconversion has been updated and before any emission | |
691 | int64_t CCurrencyDefinition::GetTotalPreallocation() const | |
692 | { | |
693 | CAmount totalPreconvertedNative = 0; | |
694 | CAmount totalPreallocatedNative = 0; | |
695 | if (preAllocationRatio == 0) | |
696 | { | |
697 | for (auto onePreallocation : preAllocation) | |
698 | { | |
699 | totalPreallocatedNative += onePreallocation.second; | |
700 | } | |
701 | return totalPreallocatedNative; | |
702 | } | |
703 | for (auto oneConversion : preconverted) | |
704 | { | |
705 | totalPreconvertedNative += oneConversion; | |
706 | } | |
707 | if (!totalPreconvertedNative || preAllocationRatio >= SATOSHIDEN) | |
708 | { | |
709 | return 0; | |
710 | } | |
711 | arith_uint256 bigAmount(totalPreconvertedNative); | |
712 | static const arith_uint256 bigSatoshi(SATOSHIDEN); | |
713 | arith_uint256 conversionPercent(preAllocationRatio); | |
714 | ||
715 | CAmount newAmount = ((bigAmount * bigSatoshi) / (bigSatoshi - conversionPercent)).GetLow64(); | |
716 | CAmount retAmount = CalculateRatioOfValue(newAmount, preAllocationRatio); | |
717 | newAmount = totalPreconvertedNative + retAmount; | |
718 | ||
719 | retAmount = CalculateRatioOfValue(newAmount, preAllocationRatio); // again to account for minimum fee | |
720 | retAmount += totalPreconvertedNative - (newAmount - retAmount); // add any additional difference | |
721 | return retAmount; | |
722 | } | |
723 | ||
724 | std::vector<std::pair<uint160, int64_t>> CCurrencyDefinition::GetPreAllocationAmounts() const | |
725 | { | |
726 | if (!preAllocationRatio) | |
727 | { | |
728 | return preAllocation; | |
729 | } | |
730 | if (preAllocationRatio >= SATOSHIDEN) | |
731 | { | |
732 | return std::vector<std::pair<uint160, int64_t>>(); | |
733 | } | |
734 | int64_t totalPreAllocation = GetTotalPreallocation(); | |
735 | int64_t totalPreAllocUnits = 0; | |
736 | for (auto &onePreAlloc : preAllocation) | |
737 | { | |
738 | totalPreAllocUnits += onePreAlloc.second; | |
739 | } | |
740 | static arith_uint256 bigSatoshi(SATOSHIDEN); | |
741 | ||
742 | std::vector<std::pair<uint160, int64_t>> retVal(preAllocation.size()); | |
743 | for (int i = 0; i < preAllocation.size(); i++) | |
744 | { | |
745 | if (i == preAllocation.size() - 1) | |
746 | { | |
747 | retVal[i] = make_pair(preAllocation[i].first, totalPreAllocation); | |
748 | } | |
749 | else | |
750 | { | |
751 | CAmount subRatio = ((arith_uint256(preAllocation[i].second) * bigSatoshi) / totalPreAllocUnits).GetLow64(); | |
752 | CAmount thisPreAlloc = CalculateRatioOfValue(totalPreAllocation, subRatio); | |
753 | retVal[i] = make_pair(preAllocation[i].first, thisPreAlloc); | |
754 | totalPreAllocation -= thisPreAlloc; | |
755 | } | |
756 | } | |
757 | return retVal; | |
758 | } | |
759 | ||
760 | UniValue CCurrencyDefinition::ToUniValue() const | |
761 | { | |
762 | UniValue obj(UniValue::VOBJ); | |
763 | ||
764 | obj.push_back(Pair("name", name)); | |
765 | obj.push_back(Pair("version", (int64_t)nVersion)); | |
197568cb | 766 | obj.push_back(Pair("options", (int64_t)options)); |
56fe75cb | 767 | obj.push_back(Pair("parent", EncodeDestination(CIdentityID(parent)))); |
768 | obj.push_back(Pair("systemid", EncodeDestination(CIdentityID(systemID)))); | |
769 | obj.push_back(Pair("currencyid", EncodeDestination(CIdentityID(GetID())))); | |
770 | obj.push_back(Pair("notarizationprotocol", (int)notarizationProtocol)); | |
771 | obj.push_back(Pair("proofprotocol", (int)proofProtocol)); | |
772 | ||
773 | obj.push_back(Pair("idregistrationprice", idRegistrationAmount)); | |
774 | obj.push_back(Pair("idreferrallevels", idReferralLevels)); | |
775 | ||
776 | // notaries are identities that perform specific functions for the currency's operation | |
777 | // related to notarizing an external currency source, as well as proving imports | |
778 | if (notaries.size()) | |
779 | { | |
780 | UniValue notaryArr(UniValue::VARR); | |
781 | for (auto ¬ary : notaries) | |
782 | { | |
783 | notaryArr.push_back(EncodeDestination(CIdentityID(notary))); | |
784 | } | |
785 | obj.push_back(Pair("notaries", notaryArr)); | |
786 | } | |
787 | obj.push_back(Pair("minnotariesconfirm", minNotariesConfirm)); | |
788 | ||
789 | obj.push_back(Pair("billingperiod", billingPeriod)); | |
790 | obj.push_back(Pair("notarizationreward", notarizationReward)); | |
791 | obj.push_back(Pair("startblock", (int32_t)startBlock)); | |
792 | obj.push_back(Pair("endblock", (int32_t)endBlock)); | |
793 | ||
794 | // notaries are identities that perform specific functions for the currency's operation | |
795 | // related to notarizing an external currency source, as well as proving imports | |
796 | if (currencies.size()) | |
797 | { | |
798 | UniValue currencyArr(UniValue::VARR); | |
799 | for (auto ¤cy : currencies) | |
800 | { | |
801 | currencyArr.push_back(EncodeDestination(CIdentityID(currency))); | |
802 | } | |
803 | obj.push_back(Pair("currencies", currencyArr)); | |
804 | } | |
805 | ||
806 | if (weights.size()) | |
807 | { | |
808 | UniValue weightArr(UniValue::VARR); | |
809 | for (auto &weight : weights) | |
810 | { | |
811 | weightArr.push_back(ValueFromAmount(weight)); | |
812 | } | |
813 | obj.push_back(Pair("weights", weightArr)); | |
814 | } | |
815 | ||
816 | if (conversions.size()) | |
817 | { | |
818 | UniValue conversionArr(UniValue::VARR); | |
819 | for (auto &conversion : conversions) | |
820 | { | |
821 | conversionArr.push_back(ValueFromAmount(conversion)); | |
822 | } | |
823 | obj.push_back(Pair("conversions", conversionArr)); | |
824 | } | |
825 | ||
826 | if (minPreconvert.size()) | |
827 | { | |
828 | UniValue minPreconvertArr(UniValue::VARR); | |
829 | for (auto &oneMin : minPreconvert) | |
830 | { | |
831 | minPreconvertArr.push_back(ValueFromAmount(oneMin)); | |
832 | } | |
833 | obj.push_back(Pair("minpreconversion", minPreconvertArr)); | |
834 | } | |
835 | ||
836 | if (maxPreconvert.size()) | |
837 | { | |
838 | UniValue maxPreconvertArr(UniValue::VARR); | |
839 | for (auto &oneMax : maxPreconvert) | |
840 | { | |
841 | maxPreconvertArr.push_back(ValueFromAmount(oneMax)); | |
842 | } | |
843 | obj.push_back(Pair("maxpreconversion", maxPreconvertArr)); | |
844 | } | |
845 | ||
56fe75cb | 846 | if (preAllocationRatio) |
847 | { | |
848 | obj.push_back(Pair("preallocationratio", ValueFromAmount(preAllocationRatio))); | |
849 | } | |
850 | ||
851 | if (preAllocation.size()) | |
852 | { | |
853 | UniValue preAllocationArr(UniValue::VARR); | |
854 | for (auto &onePreAllocation : preAllocation) | |
855 | { | |
856 | UniValue onePreAlloc(UniValue::VOBJ); | |
857 | onePreAlloc.push_back(Pair(EncodeDestination(CIdentityID(onePreAllocation.first)), ValueFromAmount(onePreAllocation.second))); | |
858 | preAllocationArr.push_back(onePreAlloc); | |
859 | } | |
2f416b17 | 860 | obj.push_back(Pair("preallocation", preAllocationArr)); |
56fe75cb | 861 | } |
862 | ||
863 | if (contributions.size()) | |
864 | { | |
865 | UniValue initialContributionArr(UniValue::VARR); | |
866 | for (auto &oneCurContributions : contributions) | |
867 | { | |
868 | initialContributionArr.push_back(ValueFromAmount(oneCurContributions)); | |
869 | } | |
870 | obj.push_back(Pair("initialcontributions", initialContributionArr)); | |
871 | } | |
872 | ||
873 | if (preconverted.size()) | |
874 | { | |
875 | UniValue preconversionArr(UniValue::VARR); | |
876 | for (auto &onePreconversion : preconverted) | |
877 | { | |
878 | preconversionArr.push_back(ValueFromAmount(onePreconversion)); | |
879 | } | |
880 | obj.push_back(Pair("preconversions", preconversionArr)); | |
881 | } | |
882 | ||
883 | UniValue eraArr(UniValue::VARR); | |
884 | for (int i = 0; i < rewards.size(); i++) | |
885 | { | |
886 | UniValue era(UniValue::VOBJ); | |
887 | era.push_back(Pair("reward", rewards.size() > i ? rewards[i] : (int64_t)0)); | |
888 | era.push_back(Pair("decay", rewardsDecay.size() > i ? rewardsDecay[i] : (int64_t)0)); | |
889 | era.push_back(Pair("halving", halving.size() > i ? (int32_t)halving[i] : (int32_t)0)); | |
890 | era.push_back(Pair("eraend", eraEnd.size() > i ? (int32_t)eraEnd[i] : (int32_t)0)); | |
891 | eraArr.push_back(era); | |
892 | } | |
893 | obj.push_back(Pair("eras", eraArr)); | |
894 | return obj; | |
895 | } | |
896 |