]>
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> | |
32 | #include <stdio.h> | |
33 | ||
34 | #include <event2/buffer.h> | |
35 | #include <event2/keyvalq_struct.h> | |
36 | #include "support/events.h" | |
37 | ||
38 | #include <univalue.h> | |
39 | ||
40 | #include "uint256.h" | |
41 | #include "hash.h" | |
42 | #include "pbaas/crosschainrpc.h" | |
43 | ||
44 | using namespace std; | |
45 | ||
9f0c14b2 MT |
46 | extern string PBAAS_HOST; |
47 | extern string PBAAS_USERPASS; | |
48 | extern int32_t PBAAS_PORT; | |
49 | ||
b2a98c42 MT |
50 | // |
51 | // Exception thrown on connection error. This error is used to determine | |
52 | // when to wait if -rpcwait is given. | |
53 | // | |
54 | class CConnectionFailed : public std::runtime_error | |
55 | { | |
56 | public: | |
57 | ||
58 | explicit inline CConnectionFailed(const std::string& msg) : | |
59 | std::runtime_error(msg) | |
60 | {} | |
61 | ||
62 | }; | |
63 | ||
64 | /** Reply structure for request_done to fill in */ | |
65 | struct HTTPReply | |
66 | { | |
67 | HTTPReply(): status(0), error(-1) {} | |
68 | ||
69 | int status; | |
70 | int error; | |
71 | std::string body; | |
72 | }; | |
73 | ||
74 | const char *http_errorstring(int code) | |
75 | { | |
76 | switch(code) { | |
77 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
78 | case EVREQ_HTTP_TIMEOUT: | |
79 | return "timeout reached"; | |
80 | case EVREQ_HTTP_EOF: | |
81 | return "EOF reached"; | |
82 | case EVREQ_HTTP_INVALID_HEADER: | |
83 | return "error while reading header, or invalid header"; | |
84 | case EVREQ_HTTP_BUFFER_ERROR: | |
85 | return "error encountered while reading or writing"; | |
86 | case EVREQ_HTTP_REQUEST_CANCEL: | |
87 | return "request was canceled"; | |
88 | case EVREQ_HTTP_DATA_TOO_LONG: | |
89 | return "response body is larger than allowed"; | |
90 | #endif | |
91 | default: | |
92 | return "unknown"; | |
93 | } | |
94 | } | |
95 | ||
96 | static void http_request_done(struct evhttp_request *req, void *ctx) | |
97 | { | |
98 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
99 | ||
100 | if (req == NULL) { | |
101 | /* If req is NULL, it means an error occurred while connecting: the | |
102 | * error code will have been passed to http_error_cb. | |
103 | */ | |
104 | reply->status = 0; | |
105 | return; | |
106 | } | |
107 | ||
108 | reply->status = evhttp_request_get_response_code(req); | |
109 | ||
110 | struct evbuffer *buf = evhttp_request_get_input_buffer(req); | |
111 | if (buf) | |
112 | { | |
113 | size_t size = evbuffer_get_length(buf); | |
114 | const char *data = (const char*)evbuffer_pullup(buf, size); | |
115 | if (data) | |
116 | reply->body = std::string(data, size); | |
117 | evbuffer_drain(buf, size); | |
118 | } | |
119 | } | |
120 | ||
121 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
122 | static void http_error_cb(enum evhttp_request_error err, void *ctx) | |
123 | { | |
124 | HTTPReply *reply = static_cast<HTTPReply*>(ctx); | |
125 | reply->error = err; | |
126 | } | |
127 | #endif | |
128 | ||
9f0c14b2 | 129 | static CCrossChainRPCData LoadFromConfig(std::string name) |
b2a98c42 | 130 | { |
9f0c14b2 MT |
131 | map<string, string> settings; |
132 | map<string, vector<string>> settingsmulti; | |
133 | CCrossChainRPCData ret; | |
134 | ||
135 | // if we are requested to automatically load the information from the Verus chain, do it if we can find the daemon | |
136 | if (ReadConfigFile(name, settings, settingsmulti)) | |
137 | { | |
138 | auto rpcuser = settings.find("-rpcuser"); | |
139 | auto rpcpwd = settings.find("-rpcpassword"); | |
140 | auto rpcport = settings.find("-rpcport"); | |
141 | auto rpchost = settings.find("-rpchost"); | |
142 | ret.credentials = rpcuser != settings.end() ? rpcuser->second + ":" : ""; | |
143 | ret.credentials += rpcpwd != settings.end() ? rpcpwd->second : ""; | |
144 | ret.port = rpcport != settings.end() ? atoi(rpcport->second) : (name == "VRSC" ? 27486 : 0); | |
145 | ret.host = rpchost != settings.end() ? rpchost->second : "127.0.0.1"; | |
146 | } | |
147 | return ret; | |
b2a98c42 MT |
148 | } |
149 | ||
150 | // credentials for now are "user:password" | |
151 | UniValue RPCCall(const string& strMethod, const UniValue& params, const string credentials, int port, const string host, int timeout) | |
152 | { | |
153 | // Used for inter-daemon communicatoin to enable merge mining and notarization without a client | |
154 | // | |
155 | ||
156 | // Obtain event base | |
157 | raii_event_base base = obtain_event_base(); | |
158 | ||
159 | // Synchronously look up hostname | |
160 | raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port); | |
161 | evhttp_connection_set_timeout(evcon.get(), timeout); | |
162 | ||
163 | HTTPReply response; | |
164 | raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response); | |
165 | if (req == NULL) | |
166 | throw std::runtime_error("create http request failed"); | |
167 | #if LIBEVENT_VERSION_NUMBER >= 0x02010300 | |
168 | evhttp_request_set_error_cb(req.get(), http_error_cb); | |
169 | #endif | |
170 | ||
171 | struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get()); | |
172 | assert(output_headers); | |
173 | evhttp_add_header(output_headers, "Host", host.c_str()); | |
174 | evhttp_add_header(output_headers, "Connection", "close"); | |
175 | evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(credentials)).c_str()); | |
176 | ||
177 | // Attach request data | |
178 | std::string strRequest = JSONRPCRequest(strMethod, params, 1); | |
179 | struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get()); | |
180 | assert(output_buffer); | |
181 | evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); | |
182 | ||
183 | int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); | |
184 | req.release(); // ownership moved to evcon in above call | |
185 | if (r != 0) { | |
186 | throw CConnectionFailed("send http request failed"); | |
187 | } | |
188 | ||
189 | event_base_dispatch(base.get()); | |
190 | ||
191 | if (response.status == 0) | |
192 | 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)); | |
193 | else if (response.status == HTTP_UNAUTHORIZED) | |
194 | throw std::runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); | |
195 | else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) | |
196 | throw std::runtime_error(strprintf("server returned HTTP error %d", response.status)); | |
197 | else if (response.body.empty()) | |
198 | throw std::runtime_error("no response from server"); | |
199 | ||
200 | // Parse reply | |
201 | UniValue valReply(UniValue::VSTR); | |
202 | if (!valReply.read(response.body)) | |
203 | throw std::runtime_error("couldn't parse reply from server"); | |
204 | const UniValue& reply = valReply.get_obj(); | |
205 | if (reply.empty()) | |
206 | throw std::runtime_error("expected reply to have result, error and id properties"); | |
207 | ||
208 | return reply; | |
209 | } | |
9f0c14b2 | 210 | |
2156d3d0 | 211 | UniValue RPCCallRoot(const string& strMethod, const UniValue& params, int timeout) |
9f0c14b2 MT |
212 | { |
213 | string host, credentials; | |
214 | int port; | |
215 | map<string, string> settings; | |
216 | map<string, vector<string>> settingsmulti; | |
217 | ||
218 | if (PBAAS_HOST != "" && PBAAS_PORT != 0) | |
219 | { | |
220 | return RPCCall(strMethod, params, PBAAS_USERPASS, PBAAS_PORT, PBAAS_HOST); | |
221 | } | |
222 | else if (ReadConfigFile(PBAAS_TESTMODE ? "VRSCTEST" : "VRSC", settings, settingsmulti)) | |
223 | { | |
7af5cf39 | 224 | PBAAS_USERPASS = settingsmulti.find("-rpcuser")->second[0] + ":" + settingsmulti.find("-rpcpassword")->second[0]; |
225 | PBAAS_PORT = atoi(settingsmulti.find("-rpcport")->second[0]); | |
226 | PBAAS_HOST = settingsmulti.find("-rpchost")->second[0]; | |
227 | if (!PBAAS_HOST.size()) | |
9f0c14b2 | 228 | { |
7af5cf39 | 229 | PBAAS_HOST = "127.0.0.1"; |
9f0c14b2 | 230 | } |
2156d3d0 | 231 | return RPCCall(strMethod, params, credentials, port, host, timeout); |
9f0c14b2 MT |
232 | } |
233 | return UniValue(UniValue::VNULL); | |
234 | } | |
f8f61a6d | 235 | |
236 | int32_t uni_get_int(UniValue uv, int32_t def) | |
237 | { | |
238 | try | |
239 | { | |
240 | return uv.get_int(); | |
241 | } | |
242 | catch(const std::exception& e) | |
243 | { | |
244 | return def; | |
245 | } | |
246 | } | |
247 | ||
248 | int64_t uni_get_int64(UniValue uv, int64_t def) | |
249 | { | |
250 | try | |
251 | { | |
252 | return uv.get_int64(); | |
253 | } | |
254 | catch(const std::exception& e) | |
255 | { | |
256 | return def; | |
257 | } | |
258 | } | |
259 | ||
260 | std::string uni_get_str(UniValue uv, std::string def) | |
261 | { | |
262 | try | |
263 | { | |
264 | return uv.get_str(); | |
265 | } | |
266 | catch(const std::exception& e) | |
267 | { | |
268 | return def; | |
269 | } | |
270 | } | |
271 | ||
272 | std::vector<UniValue> uni_getValues(UniValue uv, std::vector<UniValue> def) | |
273 | { | |
274 | try | |
275 | { | |
276 | return uv.getValues(); | |
277 | } | |
278 | catch(const std::exception& e) | |
279 | { | |
280 | return def; | |
281 | } | |
282 | } | |
283 | ||
ef54c6e1 | 284 | UniValue CCrossChainRPCData::ToUniValue() const |
285 | { | |
286 | UniValue obj(UniValue::VOBJ); | |
287 | obj.push_back(Pair("host", host)); | |
288 | obj.push_back(Pair("port", port)); | |
289 | obj.push_back(Pair("credentials", credentials)); | |
290 | return obj; | |
291 | } | |
292 | ||
db336898 | 293 | uint160 CCrossChainRPCData::GetConditionID(uint160 cid, int32_t condition) |
294 | { | |
db336898 | 295 | CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); |
296 | hw << condition; | |
297 | hw << cid; | |
298 | uint256 chainHash = hw.GetHash(); | |
db336898 | 299 | return Hash160(chainHash.begin(), chainHash.end()); |
300 | } | |
301 | ||
302 | uint160 CCrossChainRPCData::GetConditionID(std::string name, int32_t condition) | |
303 | { | |
304 | uint160 cid = GetChainID(name); | |
db336898 | 305 | |
306 | CHashWriter hw(SER_GETHASH, PROTOCOL_VERSION); | |
307 | hw << condition; | |
308 | hw << cid; | |
309 | uint256 chainHash = hw.GetHash(); | |
db336898 | 310 | return Hash160(chainHash.begin(), chainHash.end()); |
311 | } |