evernode-js-client 0.4.53 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +14 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/clean-pkg.sh +4 -0
- package/npm-readme.md +4 -0
- package/package.json +16 -1
- package/remove-versions.sh +10 -0
- package/src/clients/base-evernode-client.js +567 -0
- package/src/clients/host-client.js +357 -0
- package/src/clients/registry-client.js +52 -0
- package/src/clients/tenant-client.js +264 -0
- package/src/defaults.js +21 -0
- package/src/eccrypto.js +258 -0
- package/src/encryption-helper.js +41 -0
- package/src/event-emitter.js +45 -0
- package/src/evernode-common.js +103 -0
- package/src/evernode-helpers.js +14 -0
- package/src/firestore/firestore-handler.js +309 -0
- package/src/index.js +37 -0
- package/src/state-helpers.js +283 -0
- package/src/transaction-helper.js +62 -0
- package/src/util-helpers.js +48 -0
- package/src/xfl-helpers.js +130 -0
- package/src/xrpl-account.js +473 -0
- package/src/xrpl-api.js +275 -0
- package/src/xrpl-common.js +17 -0
- package/test/package-lock.json +884 -0
- package/test/package.json +9 -0
- package/test/test.js +379 -0
- package/index.js +0 -15166
@@ -0,0 +1,309 @@
|
|
1
|
+
const https = require('https');
|
2
|
+
const { DefaultValues } = require('../defaults');
|
3
|
+
|
4
|
+
const FirestoreOperations = {
|
5
|
+
EQUAL: 'EQUAL',
|
6
|
+
AND: 'AND'
|
7
|
+
}
|
8
|
+
|
9
|
+
class FirestoreHandler {
|
10
|
+
#projectId = null;
|
11
|
+
#collectionPrefix = null;
|
12
|
+
|
13
|
+
constructor(options = {}) {
|
14
|
+
this.#projectId = options.stateIndexId || DefaultValues.stateIndexId;
|
15
|
+
this.#collectionPrefix = options.collectionPrefix || DefaultValues.registryAddress;
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Convert given document value object to real format and snake_case key to camelCase.
|
20
|
+
* @param key Name of the property.
|
21
|
+
* @param value Value to be parsed.
|
22
|
+
* @returns Parsed key and value.
|
23
|
+
*/
|
24
|
+
#parseValue(key, value) {
|
25
|
+
// Convert snake_case to camelCase.
|
26
|
+
const ccKey = key.replace(/_([a-z])/g, function (g) { return g[1].toUpperCase(); });
|
27
|
+
const type = Object.keys(value)[0];
|
28
|
+
let parsed;
|
29
|
+
switch (type) {
|
30
|
+
case 'integerValue':
|
31
|
+
parsed = parseInt(value[type]);
|
32
|
+
break;
|
33
|
+
case 'floatValue':
|
34
|
+
parsed = parseFloat(value[type]);
|
35
|
+
break;
|
36
|
+
case 'mapValue':
|
37
|
+
parsed = {};
|
38
|
+
for (const [subKey, subValue] of Object.entries(value[type].fields)) {
|
39
|
+
const field = this.#parseValue(subKey, subValue);
|
40
|
+
parsed[field.key] = field.value;
|
41
|
+
}
|
42
|
+
break;
|
43
|
+
default:
|
44
|
+
parsed = value[type];
|
45
|
+
break;
|
46
|
+
}
|
47
|
+
return { key: ccKey, value: parsed };
|
48
|
+
}
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Get values filtered according to the given body.
|
52
|
+
* @param body Body to parsed in to the query reqest.
|
53
|
+
* @returns Result set.
|
54
|
+
*/
|
55
|
+
async #runQuery(body) {
|
56
|
+
const url = this.buildApiPath(null, null, true);
|
57
|
+
return await this.sendRequest('POST', url, null, body);
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Get documents from a collection.
|
62
|
+
* @param collectionId Name of the collection.
|
63
|
+
* @param pageSize Optianal page size if result set needed to be paginated
|
64
|
+
* @param nextPageToken Next page token of a paginated result set.
|
65
|
+
* @returns Result set.
|
66
|
+
*/
|
67
|
+
async #read(collectionId, pageSize = null, nextPageToken = null) {
|
68
|
+
if (!collectionId)
|
69
|
+
throw { type: 'Validation Error', message: 'collectionId is required' };
|
70
|
+
|
71
|
+
const url = this.buildApiPath(collectionId);
|
72
|
+
let params = (pageSize || nextPageToken) ? {} : null;
|
73
|
+
if (pageSize)
|
74
|
+
params = { pageSize: pageSize };
|
75
|
+
if (nextPageToken)
|
76
|
+
params = { pageToken: nextPageToken, ...params };
|
77
|
+
|
78
|
+
return await this.sendRequest('GET', url, params);
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Get documents from a collection with filtering support.
|
83
|
+
* @param collectionId Name of the collection.
|
84
|
+
* @param filter Optional filters to filter documents.
|
85
|
+
* @param pageSize Optianal page size if result set needed to be paginated
|
86
|
+
* @param nextPageToken Next page token of a paginated result set.
|
87
|
+
* @returns Parsed readable result set.
|
88
|
+
*/
|
89
|
+
async #getDocuments(collectionId, filters = null, pageSize = null, nextPageToken = null) {
|
90
|
+
if (filters && (pageSize || nextPageToken))
|
91
|
+
throw { type: 'Validation Error', message: 'Pagination isn\'t supported with filter.' };
|
92
|
+
|
93
|
+
let data;
|
94
|
+
// If there are filters send requst in query mode.
|
95
|
+
if (filters) {
|
96
|
+
// Prepare the query body from given filters.
|
97
|
+
let where = {
|
98
|
+
compositeFilter: {
|
99
|
+
filters: Object.entries(filters).map(([key, value]) => {
|
100
|
+
const field = this.convertValue(key, value);
|
101
|
+
return {
|
102
|
+
fieldFilter: {
|
103
|
+
field: { fieldPath: field.key },
|
104
|
+
op: FirestoreOperations.EQUAL,
|
105
|
+
value: field.value
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}),
|
109
|
+
op: filters.operator ? filters.operator : FirestoreOperations.AND
|
110
|
+
}
|
111
|
+
};
|
112
|
+
for (const [key, value] of Object.entries(filters)) {
|
113
|
+
const field = this.convertValue(key, value);
|
114
|
+
let fieldFilter = {
|
115
|
+
field: { fieldPath: field.key },
|
116
|
+
op: FirestoreOperations.EQUAL,
|
117
|
+
value: field.value
|
118
|
+
}
|
119
|
+
where.compositeFilter.filters.push({ fieldFilter: fieldFilter });
|
120
|
+
}
|
121
|
+
|
122
|
+
let body = {
|
123
|
+
structuredQuery: {
|
124
|
+
where: where,
|
125
|
+
from: [{ collectionId: collectionId }]
|
126
|
+
}
|
127
|
+
};
|
128
|
+
data = await this.#runQuery(body);
|
129
|
+
data = data ? JSON.parse(data) : [];
|
130
|
+
if (data && data.length && data[0].document) {
|
131
|
+
return data.map(d => {
|
132
|
+
return this.parseDocument(d.document);
|
133
|
+
});
|
134
|
+
}
|
135
|
+
}
|
136
|
+
else {
|
137
|
+
data = await this.#read(collectionId, pageSize, nextPageToken);
|
138
|
+
data = data ? JSON.parse(data) : {};
|
139
|
+
if (data.documents && data.documents.length) {
|
140
|
+
const list = data.documents.map(d => {
|
141
|
+
return this.parseDocument(d);
|
142
|
+
});
|
143
|
+
return data.nextPageToken ? {
|
144
|
+
data: list,
|
145
|
+
nextPageToken: data.nextPageToken
|
146
|
+
} : list;
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
return [];
|
151
|
+
}
|
152
|
+
|
153
|
+
getCollectionId(collection) {
|
154
|
+
// Document if is generated with given prefix.
|
155
|
+
return `${this.#collectionPrefix}_${collection}`;
|
156
|
+
}
|
157
|
+
|
158
|
+
/**
|
159
|
+
* Send http requst.
|
160
|
+
* @param httpMethod GET/POST/PATCH/DELETE
|
161
|
+
* @param url Url for the request.
|
162
|
+
* @param params Optional query params.
|
163
|
+
* @param data Optional request body.
|
164
|
+
* @param options Optional options.
|
165
|
+
* @returns Result set.
|
166
|
+
*/
|
167
|
+
async sendRequest(httpMethod, url, params = null, data = null, options = null) {
|
168
|
+
const urlObj = new URL(url);
|
169
|
+
// Populate uri params to the URL object.
|
170
|
+
if (params) {
|
171
|
+
for (const [key, value] of Object.entries(params)) {
|
172
|
+
if (value) {
|
173
|
+
// If value is a array, populate it with same name.
|
174
|
+
if (typeof value === 'object') {
|
175
|
+
for (const val of value) {
|
176
|
+
if (val)
|
177
|
+
urlObj.searchParams.append(key, val);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
else
|
181
|
+
urlObj.searchParams.set(key, value);
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
let reqOptions = {
|
187
|
+
method: httpMethod,
|
188
|
+
protocol: urlObj.protocol,
|
189
|
+
hostname: urlObj.hostname,
|
190
|
+
port: urlObj.port,
|
191
|
+
path: `${urlObj.pathname}?${urlObj.searchParams.toString()}`,
|
192
|
+
...(options ? options : {})
|
193
|
+
};
|
194
|
+
|
195
|
+
return new Promise(async (resolve, reject) => {
|
196
|
+
// Send request and collect data chuncks to a buffer. Then return the result set on end event.
|
197
|
+
// Resolve only if the status is 200.
|
198
|
+
const req = https.request(reqOptions, (res) => {
|
199
|
+
let resData = '';
|
200
|
+
res.on('data', (d) => {
|
201
|
+
resData += d;
|
202
|
+
});
|
203
|
+
res.on('end', async () => {
|
204
|
+
if (res.statusCode === 200) {
|
205
|
+
resolve(resData);
|
206
|
+
return;
|
207
|
+
}
|
208
|
+
reject({ status: res.statusCode, data: resData });
|
209
|
+
return;
|
210
|
+
});
|
211
|
+
}).on('error', async (e) => {
|
212
|
+
reject(e);
|
213
|
+
return;
|
214
|
+
});
|
215
|
+
|
216
|
+
if (data)
|
217
|
+
req.write(typeof data === 'object' ? JSON.stringify(data) : data);
|
218
|
+
|
219
|
+
req.end();
|
220
|
+
});
|
221
|
+
}
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Generate url for firestore.
|
225
|
+
* @param collectionId Optional collection name.
|
226
|
+
* @param documentId Optional document name.
|
227
|
+
* @param isQuery Whether a query request or not.
|
228
|
+
* @returns The generated url.
|
229
|
+
*/
|
230
|
+
buildApiPath(collectionId = null, documentId = null, isQuery = false) {
|
231
|
+
let path = `https://firestore.googleapis.com/v1/projects/${this.#projectId}/databases/(default)/documents`;
|
232
|
+
if (collectionId)
|
233
|
+
path += `/${collectionId.toString()}`;
|
234
|
+
if (documentId)
|
235
|
+
path += `/${documentId.toString()}`;
|
236
|
+
if (isQuery)
|
237
|
+
path += ':runQuery';
|
238
|
+
return path;
|
239
|
+
}
|
240
|
+
|
241
|
+
/**
|
242
|
+
* Generate firestore document value object from given key and value, Convert camelCase key to snake_case.
|
243
|
+
* @param key Name of the value.
|
244
|
+
* @param value Value to be parsed.
|
245
|
+
* @returns Generated firestore document object.
|
246
|
+
*/
|
247
|
+
convertValue(key, value) {
|
248
|
+
// Convert camelCase to snake_case.
|
249
|
+
const uKey = key.replace(/([A-Z])/g, function (g) { return `_${g[0].toLocaleLowerCase()}`; });
|
250
|
+
let val = {};
|
251
|
+
let type
|
252
|
+
switch (typeof value) {
|
253
|
+
case 'number':
|
254
|
+
type = (value % 1 > 0 ? 'float' : 'integer');
|
255
|
+
val = value;
|
256
|
+
break;
|
257
|
+
case 'object':
|
258
|
+
type = 'map';
|
259
|
+
val = {
|
260
|
+
fields: {}
|
261
|
+
}
|
262
|
+
// Prepare the firestore write body with the given data object.
|
263
|
+
for (const [subKey, subValue] of Object.entries(value)) {
|
264
|
+
const field = this.convertValue(subKey, subValue);
|
265
|
+
val.fields[field.key] = field.value;
|
266
|
+
}
|
267
|
+
break;
|
268
|
+
default:
|
269
|
+
type = 'string';
|
270
|
+
val = value;
|
271
|
+
break;
|
272
|
+
}
|
273
|
+
type = `${type}Value`;
|
274
|
+
let obj = {};
|
275
|
+
obj[type] = val;
|
276
|
+
return { key: uKey, value: obj };
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Convert the firestore document to human readable simplified json object.
|
281
|
+
* @param document Firestore document.
|
282
|
+
* @returns Simplified json object for the document.
|
283
|
+
*/
|
284
|
+
parseDocument(document) {
|
285
|
+
let item = {
|
286
|
+
id: document.name.split('/').pop(),
|
287
|
+
createTime: new Date(document.createTime),
|
288
|
+
updateTime: new Date(document.updateTime)
|
289
|
+
};
|
290
|
+
for (const [key, value] of Object.entries(document.fields)) {
|
291
|
+
const field = this.#parseValue(key, value);
|
292
|
+
item[field.key] = field.value;
|
293
|
+
}
|
294
|
+
return item;
|
295
|
+
}
|
296
|
+
|
297
|
+
async getHosts(filters = null, pageSize = null, nextPageToken = null) {
|
298
|
+
return await this.#getDocuments(this.getCollectionId('hosts'), filters, pageSize, nextPageToken);
|
299
|
+
}
|
300
|
+
|
301
|
+
async getConfigs(filters = null, pageSize = null, nextPageToken = null) {
|
302
|
+
return await this.#getDocuments(this.getCollectionId('configs'), filters, pageSize, nextPageToken);
|
303
|
+
}
|
304
|
+
}
|
305
|
+
|
306
|
+
module.exports = {
|
307
|
+
FirestoreHandler,
|
308
|
+
FirestoreOperations
|
309
|
+
}
|
package/src/index.js
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
const { Defaults } = require('./defaults');
|
2
|
+
const { RegistryClient, RegistryEvents } = require("./clients/registry-client");
|
3
|
+
const { TenantClient, TenantEvents } = require("./clients/tenant-client");
|
4
|
+
const { HostClient, HostEvents } = require("./clients/host-client");
|
5
|
+
const { XrplApi } = require('./xrpl-api');
|
6
|
+
const { XrplApiEvents, XrplConstants } = require('./xrpl-common');
|
7
|
+
const { XrplAccount } = require('./xrpl-account');
|
8
|
+
const { EvernodeConstants, HookStateKeys, MemoTypes } = require('./evernode-common');
|
9
|
+
const { XflHelpers } = require('./xfl-helpers');
|
10
|
+
const { FirestoreHandler } = require('./firestore/firestore-handler');
|
11
|
+
const { StateHelpers } = require('./state-helpers');
|
12
|
+
const { UtilHelpers } = require('./util-helpers');
|
13
|
+
const { TransactionHelper } = require('./transaction-helper');
|
14
|
+
const { EncryptionHelper } = require('./encryption-helper');
|
15
|
+
|
16
|
+
module.exports = {
|
17
|
+
RegistryClient,
|
18
|
+
RegistryEvents,
|
19
|
+
TenantClient,
|
20
|
+
TenantEvents,
|
21
|
+
HostClient,
|
22
|
+
HostEvents,
|
23
|
+
XrplApi,
|
24
|
+
XrplApiEvents,
|
25
|
+
XrplConstants,
|
26
|
+
XrplAccount,
|
27
|
+
EvernodeConstants,
|
28
|
+
Defaults,
|
29
|
+
XflHelpers,
|
30
|
+
StateHelpers,
|
31
|
+
FirestoreHandler,
|
32
|
+
UtilHelpers,
|
33
|
+
TransactionHelper,
|
34
|
+
EncryptionHelper,
|
35
|
+
HookStateKeys,
|
36
|
+
MemoTypes
|
37
|
+
}
|
@@ -0,0 +1,283 @@
|
|
1
|
+
const codec = require('ripple-address-codec');
|
2
|
+
const { Buffer } = require('buffer');
|
3
|
+
const { HookStateKeys, EvernodeConstants } = require('./evernode-common');
|
4
|
+
const { XflHelpers } = require('./xfl-helpers');
|
5
|
+
const crypto = require("crypto");
|
6
|
+
|
7
|
+
const NFTOKEN_PREFIX = '00000000';
|
8
|
+
|
9
|
+
const EPOCH_OFFSET = 0;
|
10
|
+
const SAVED_MOMENT_OFFSET = 1;
|
11
|
+
const PREV_MOMENT_ACTIVE_HOST_COUNT_OFFSET = 5;
|
12
|
+
const CUR_MOMENT_ACTIVE_HOST_COUNT_OFFSET = 9;
|
13
|
+
const EPOCH_POOL_OFFSET = 13;
|
14
|
+
|
15
|
+
const EPOCH_COUNT_OFFSET = 0;
|
16
|
+
const FIRST_EPOCH_REWARD_QUOTA_OFFSET = 1;
|
17
|
+
const EPOCH_REWARD_AMOUNT_OFFSET = 5;
|
18
|
+
const REWARD_START_MOMENT_OFFSET = 9;
|
19
|
+
|
20
|
+
const HOST_TOKEN_ID_OFFSET = 0;
|
21
|
+
const HOST_COUNTRY_CODE_OFFSET = 32;
|
22
|
+
const HOST_RESERVED_OFFSET = 34;
|
23
|
+
const HOST_DESCRIPTION_OFFSET = 42;
|
24
|
+
const HOST_REG_LEDGER_OFFSET = 68;
|
25
|
+
const HOST_REG_FEE_OFFSET = 76;
|
26
|
+
const HOST_TOT_INS_COUNT_OFFSET = 84;
|
27
|
+
const HOST_ACT_INS_COUNT_OFFSET = 88;
|
28
|
+
const HOST_HEARTBEAT_LEDGER_IDX_OFFSET = 92;
|
29
|
+
const HOST_VERSION_OFFSET = 100;
|
30
|
+
|
31
|
+
const HOST_ADDRESS_OFFSET = 0;
|
32
|
+
const HOST_CPU_MODEL_NAME_OFFSET = 20;
|
33
|
+
const HOST_CPU_COUNT_OFFSET = 60;
|
34
|
+
const HOST_CPU_SPEED_OFFSET = 62;
|
35
|
+
const HOST_CPU_MICROSEC_OFFSET = 64;
|
36
|
+
const HOST_RAM_MB_OFFSET = 68;
|
37
|
+
const HOST_DISK_MB_OFFSET = 72;
|
38
|
+
|
39
|
+
const STATE_KEY_TYPES = {
|
40
|
+
TOKEN_ID: 2,
|
41
|
+
HOST_ADDR: 3
|
42
|
+
}
|
43
|
+
|
44
|
+
const EVERNODE_PREFIX = 'EVR';
|
45
|
+
const HOST_ADDR_KEY_ZERO_COUNT = 8;
|
46
|
+
const HOOK_STATE_LEDGER_TYPE_PREFIX = 118; // Decimal value of ASCII 'v'
|
47
|
+
|
48
|
+
class StateHelpers {
|
49
|
+
static StateTypes = {
|
50
|
+
TOKEN_ID: 'tokenId',
|
51
|
+
HOST_ADDR: 'hostAddr',
|
52
|
+
SIGLETON: 'singleton',
|
53
|
+
CONFIGURATION: 'configuration'
|
54
|
+
}
|
55
|
+
|
56
|
+
static decodeHostAddressState(stateKeyBuf, stateDataBuf) {
|
57
|
+
return {
|
58
|
+
address: codec.encodeAccountID(stateKeyBuf.slice(12)),
|
59
|
+
nfTokenId: stateDataBuf.slice(HOST_TOKEN_ID_OFFSET, HOST_COUNTRY_CODE_OFFSET).toString('hex').toUpperCase(),
|
60
|
+
countryCode: stateDataBuf.slice(HOST_COUNTRY_CODE_OFFSET, HOST_RESERVED_OFFSET).toString(),
|
61
|
+
description: stateDataBuf.slice(HOST_DESCRIPTION_OFFSET, HOST_REG_LEDGER_OFFSET).toString().replace(/\0/g, ''),
|
62
|
+
registrationLedger: Number(stateDataBuf.readBigUInt64BE(HOST_REG_LEDGER_OFFSET)),
|
63
|
+
registrationFee: Number(stateDataBuf.readBigUInt64BE(HOST_REG_FEE_OFFSET)),
|
64
|
+
maxInstances: stateDataBuf.readUInt32BE(HOST_TOT_INS_COUNT_OFFSET),
|
65
|
+
activeInstances: stateDataBuf.readUInt32BE(HOST_ACT_INS_COUNT_OFFSET),
|
66
|
+
lastHeartbeatLedger: Number(stateDataBuf.readBigUInt64BE(HOST_HEARTBEAT_LEDGER_IDX_OFFSET)),
|
67
|
+
version: `${stateDataBuf.readUInt8(HOST_VERSION_OFFSET)}.${stateDataBuf.readUInt8(HOST_VERSION_OFFSET + 1)}.${stateDataBuf.readUInt8(HOST_VERSION_OFFSET + 2)}`
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
static decodeTokenIdState(stateDataBuf) {
|
72
|
+
return {
|
73
|
+
address: codec.encodeAccountID(stateDataBuf.slice(HOST_ADDRESS_OFFSET, HOST_CPU_MODEL_NAME_OFFSET)),
|
74
|
+
cpuModelName: stateDataBuf.slice(HOST_CPU_MODEL_NAME_OFFSET, HOST_CPU_COUNT_OFFSET).toString().replace(/\x00+$/, ''), // Remove trailing \x00 characters.
|
75
|
+
cpuCount: stateDataBuf.readUInt16BE(HOST_CPU_COUNT_OFFSET),
|
76
|
+
cpuMHz: stateDataBuf.readUInt16BE(HOST_CPU_SPEED_OFFSET),
|
77
|
+
cpuMicrosec: stateDataBuf.readUInt32BE(HOST_CPU_MICROSEC_OFFSET),
|
78
|
+
ramMb: stateDataBuf.readUInt32BE(HOST_RAM_MB_OFFSET),
|
79
|
+
diskMb: stateDataBuf.readUInt32BE(HOST_DISK_MB_OFFSET)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
static decodeStateData(stateKey, stateData) {
|
84
|
+
const hexKey = stateKey.toString('hex').toUpperCase();
|
85
|
+
if (Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
|
86
|
+
return {
|
87
|
+
type: this.StateTypes.HOST_ADDR,
|
88
|
+
key: hexKey,
|
89
|
+
...this.decodeHostAddressState(stateKey, stateData)
|
90
|
+
}
|
91
|
+
}
|
92
|
+
else if (Buffer.from(HookStateKeys.PREFIX_HOST_TOKENID, 'hex').compare(stateKey, 0, 4) === 0) {
|
93
|
+
// Generate the address state key.
|
94
|
+
const addressKeyBuf = Buffer.alloc(32, 0);
|
95
|
+
Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').copy(addressKeyBuf);
|
96
|
+
stateData.copy(addressKeyBuf, 12, HOST_ADDRESS_OFFSET, HOST_CPU_MODEL_NAME_OFFSET)
|
97
|
+
return {
|
98
|
+
type: this.StateTypes.TOKEN_ID,
|
99
|
+
key: hexKey,
|
100
|
+
addressKey: addressKeyBuf.toString('hex').toUpperCase(),
|
101
|
+
...this.decodeTokenIdState(stateData)
|
102
|
+
}
|
103
|
+
}
|
104
|
+
else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0) {
|
105
|
+
return {
|
106
|
+
type: this.StateTypes.SIGLETON,
|
107
|
+
key: hexKey,
|
108
|
+
value: stateData.readUInt32BE()
|
109
|
+
}
|
110
|
+
}
|
111
|
+
else if (Buffer.from(HookStateKeys.MOMENT_BASE_IDX, 'hex').compare(stateKey) === 0) {
|
112
|
+
return {
|
113
|
+
type: this.StateTypes.SIGLETON,
|
114
|
+
key: hexKey,
|
115
|
+
value: Number(stateData.readBigInt64BE())
|
116
|
+
}
|
117
|
+
}
|
118
|
+
else if (Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.MAX_REG, 'hex').compare(stateKey) === 0) {
|
119
|
+
return {
|
120
|
+
type: this.StateTypes.SIGLETON,
|
121
|
+
key: hexKey,
|
122
|
+
value: Number(stateData.readBigUInt64BE())
|
123
|
+
}
|
124
|
+
}
|
125
|
+
else if (Buffer.from(HookStateKeys.EVR_ISSUER_ADDR, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.FOUNDATION_ADDR, 'hex').compare(stateKey) === 0) {
|
126
|
+
return {
|
127
|
+
type: this.StateTypes.CONFIGURATION,
|
128
|
+
key: hexKey,
|
129
|
+
value: codec.encodeAccountID(stateData)
|
130
|
+
}
|
131
|
+
}
|
132
|
+
else if (Buffer.from(HookStateKeys.MOMENT_SIZE, 'hex').compare(stateKey) === 0 ||
|
133
|
+
Buffer.from(HookStateKeys.HOST_HEARTBEAT_FREQ, 'hex').compare(stateKey) === 0 ||
|
134
|
+
Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0) {
|
135
|
+
return {
|
136
|
+
type: this.StateTypes.CONFIGURATION,
|
137
|
+
key: hexKey,
|
138
|
+
value: stateData.readUInt16BE()
|
139
|
+
}
|
140
|
+
}
|
141
|
+
else if (Buffer.from(HookStateKeys.MINT_LIMIT, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.FIXED_REG_FEE, 'hex').compare(stateKey) === 0) {
|
142
|
+
return {
|
143
|
+
type: this.StateTypes.CONFIGURATION,
|
144
|
+
key: hexKey,
|
145
|
+
value: Number(stateData.readBigUInt64BE())
|
146
|
+
}
|
147
|
+
}
|
148
|
+
else if (Buffer.from(HookStateKeys.PURCHASER_TARGET_PRICE, 'hex').compare(stateKey) === 0) {
|
149
|
+
const xfl = stateData.readBigInt64BE(0);
|
150
|
+
const val = XflHelpers.toString(xfl);
|
151
|
+
return {
|
152
|
+
type: this.StateTypes.CONFIGURATION,
|
153
|
+
key: hexKey,
|
154
|
+
value: val
|
155
|
+
}
|
156
|
+
}
|
157
|
+
else if (Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0) {
|
158
|
+
return {
|
159
|
+
type: this.StateTypes.CONFIGURATION,
|
160
|
+
key: hexKey,
|
161
|
+
value: {
|
162
|
+
epochCount: stateData.readUInt8(EPOCH_COUNT_OFFSET),
|
163
|
+
firstEpochRewardQuota: stateData.readUInt32BE(FIRST_EPOCH_REWARD_QUOTA_OFFSET),
|
164
|
+
epochRewardAmount: stateData.readUInt32BE(EPOCH_REWARD_AMOUNT_OFFSET),
|
165
|
+
rewardStartMoment: stateData.readUInt32BE(REWARD_START_MOMENT_OFFSET)
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
else if (Buffer.from(HookStateKeys.REWARD_INFO, 'hex').compare(stateKey) === 0) {
|
170
|
+
return {
|
171
|
+
type: this.StateTypes.SIGLETON,
|
172
|
+
key: hexKey,
|
173
|
+
value: {
|
174
|
+
epoch: stateData.readUInt8(EPOCH_OFFSET),
|
175
|
+
savedMoment: stateData.readUInt32BE(SAVED_MOMENT_OFFSET),
|
176
|
+
prevMomentActiveHostCount: stateData.readUInt32BE(PREV_MOMENT_ACTIVE_HOST_COUNT_OFFSET),
|
177
|
+
curMomentActiveHostCount: stateData.readUInt32BE(CUR_MOMENT_ACTIVE_HOST_COUNT_OFFSET),
|
178
|
+
epochPool: XflHelpers.toString(stateData.readBigInt64BE(EPOCH_POOL_OFFSET))
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
else if (Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0) {
|
183
|
+
return {
|
184
|
+
type: this.StateTypes.CONFIGURATION,
|
185
|
+
key: hexKey,
|
186
|
+
value: stateData.readUInt16BE()
|
187
|
+
}
|
188
|
+
}
|
189
|
+
else
|
190
|
+
throw { type: 'Validation Error', message: 'Invalid state key.' };
|
191
|
+
}
|
192
|
+
|
193
|
+
static decodeStateKey(stateKey) {
|
194
|
+
const hexKey = stateKey.toString('hex').toUpperCase();
|
195
|
+
if (Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
|
196
|
+
return {
|
197
|
+
key: hexKey,
|
198
|
+
type: this.StateTypes.HOST_ADDR
|
199
|
+
};
|
200
|
+
}
|
201
|
+
else if (Buffer.from(HookStateKeys.PREFIX_HOST_TOKENID, 'hex').compare(stateKey, 0, 4) === 0) {
|
202
|
+
return {
|
203
|
+
key: hexKey,
|
204
|
+
type: this.StateTypes.TOKEN_ID
|
205
|
+
};
|
206
|
+
}
|
207
|
+
else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0 ||
|
208
|
+
Buffer.from(HookStateKeys.MOMENT_BASE_IDX, 'hex').compare(stateKey) === 0 ||
|
209
|
+
Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 ||
|
210
|
+
Buffer.from(HookStateKeys.MAX_REG, 'hex').compare(stateKey) === 0 ||
|
211
|
+
Buffer.from(HookStateKeys.REWARD_INFO, 'hex').compare(stateKey) === 0) {
|
212
|
+
return {
|
213
|
+
key: hexKey,
|
214
|
+
type: this.STATE_TYPES.SIGLETON
|
215
|
+
};
|
216
|
+
}
|
217
|
+
else if (Buffer.from(HookStateKeys.EVR_ISSUER_ADDR, 'hex').compare(stateKey) === 0 ||
|
218
|
+
Buffer.from(HookStateKeys.FOUNDATION_ADDR, 'hex').compare(stateKey) === 0 ||
|
219
|
+
Buffer.from(HookStateKeys.MOMENT_SIZE, 'hex').compare(stateKey) === 0 ||
|
220
|
+
Buffer.from(HookStateKeys.PURCHASER_TARGET_PRICE, 'hex').compare(stateKey) === 0 ||
|
221
|
+
Buffer.from(HookStateKeys.HOST_HEARTBEAT_FREQ, 'hex').compare(stateKey) ||
|
222
|
+
Buffer.from(HookStateKeys.MINT_LIMIT, 'hex').compare(stateKey) === 0 ||
|
223
|
+
Buffer.from(HookStateKeys.FIXED_REG_FEE, 'hex').compare(stateKey) === 0 ||
|
224
|
+
Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0 ||
|
225
|
+
Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0 ||
|
226
|
+
Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0) {
|
227
|
+
return {
|
228
|
+
key: hexKey,
|
229
|
+
type: this.STATE_TYPES.CONFIGURATION
|
230
|
+
};
|
231
|
+
}
|
232
|
+
else
|
233
|
+
throw { type: 'Validation Error', message: 'Invalid state key.' };
|
234
|
+
}
|
235
|
+
|
236
|
+
static generateTokenIdStateKey(nfTokenId) {
|
237
|
+
// 1 byte - Key Type.
|
238
|
+
let buf = Buffer.allocUnsafe(1);
|
239
|
+
buf.writeUInt8(STATE_KEY_TYPES.TOKEN_ID);
|
240
|
+
|
241
|
+
const nfTokenIdBuf = Buffer.from(nfTokenId, "hex");
|
242
|
+
const stateKeyBuf = (Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, nfTokenIdBuf.slice(4, 32)]));
|
243
|
+
return stateKeyBuf.toString('hex').toUpperCase();
|
244
|
+
}
|
245
|
+
|
246
|
+
static generateHostAddrStateKey(address) {
|
247
|
+
// 1 byte - Key Type.
|
248
|
+
// 8 bytes - Zeros.
|
249
|
+
let buf = Buffer.allocUnsafe(9);
|
250
|
+
buf.writeUInt8(STATE_KEY_TYPES.HOST_ADDR);
|
251
|
+
for (let i = 0; i < HOST_ADDR_KEY_ZERO_COUNT; i++) {
|
252
|
+
buf.writeUInt8(0, i + 1);
|
253
|
+
}
|
254
|
+
|
255
|
+
const addrBuf = Buffer.from(codec.decodeAccountID(address), "hex");
|
256
|
+
const stateKeyBuf = Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, addrBuf]);
|
257
|
+
return stateKeyBuf.toString('hex').toUpperCase();
|
258
|
+
}
|
259
|
+
|
260
|
+
static getHookStateIndex(hookAccount, stateKey, hookNamespace = EvernodeConstants.HOOK_NAMESPACE) {
|
261
|
+
const typeBuf = Buffer.allocUnsafe(2);
|
262
|
+
typeBuf.writeInt16BE(HOOK_STATE_LEDGER_TYPE_PREFIX);
|
263
|
+
|
264
|
+
const accIdBuf = codec.decodeAccountID(hookAccount);
|
265
|
+
const stateKeyBuf = Buffer.from(stateKey, 'hex');
|
266
|
+
const namespaceBuf = Buffer.from(hookNamespace, 'hex');
|
267
|
+
|
268
|
+
let hash = crypto.createHash('sha512');
|
269
|
+
|
270
|
+
let data = hash.update(typeBuf);
|
271
|
+
data = hash.update(accIdBuf);
|
272
|
+
data = hash.update(stateKeyBuf);
|
273
|
+
data = hash.update(namespaceBuf);
|
274
|
+
|
275
|
+
const digest = data.digest('hex');
|
276
|
+
// Get the first 32 bytes of hash.
|
277
|
+
return digest.substring(0, 64).toUpperCase();
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
module.exports = {
|
282
|
+
StateHelpers
|
283
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
const { MemoFormats } = require('./evernode-common');
|
2
|
+
|
3
|
+
class TransactionHelper {
|
4
|
+
|
5
|
+
// Convert memos from our object type to xrpl lib object type.
|
6
|
+
static formatMemos(memos) {
|
7
|
+
return memos ? memos.filter(m => m.type).map(m => {
|
8
|
+
const data = (m.format === MemoFormats.HEX) ? m.data :
|
9
|
+
TransactionHelper.asciiToHex((typeof m.data === "object") ? JSON.stringify(m.data) : m.data)
|
10
|
+
return {
|
11
|
+
Memo: {
|
12
|
+
MemoType: TransactionHelper.asciiToHex(m.type),
|
13
|
+
MemoFormat: TransactionHelper.asciiToHex(m.format),
|
14
|
+
MemoData: data
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}) : [];
|
18
|
+
}
|
19
|
+
|
20
|
+
// Convert memos from xrpl lib object type to our object type.
|
21
|
+
static deserializeMemos(memos) {
|
22
|
+
if (!memos)
|
23
|
+
return [];
|
24
|
+
|
25
|
+
return memos.filter(m => m.Memo).map(m => {
|
26
|
+
const format = m.Memo.MemoFormat ? TransactionHelper.hexToASCII(m.Memo.MemoFormat) : null;
|
27
|
+
const data = m.Memo.MemoData ?
|
28
|
+
((format === MemoFormats.HEX) ? m.Memo.MemoData : TransactionHelper.hexToASCII(m.Memo.MemoData)) : null;
|
29
|
+
return {
|
30
|
+
type: m.Memo.MemoType ? TransactionHelper.hexToASCII(m.Memo.MemoType) : null,
|
31
|
+
format: format,
|
32
|
+
data: data
|
33
|
+
}
|
34
|
+
})
|
35
|
+
}
|
36
|
+
|
37
|
+
static hexToASCII(hex) {
|
38
|
+
if (!hex)
|
39
|
+
return "";
|
40
|
+
|
41
|
+
let str = "";
|
42
|
+
for (let n = 0; n < hex.length; n += 2) {
|
43
|
+
str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
|
44
|
+
}
|
45
|
+
return str;
|
46
|
+
}
|
47
|
+
|
48
|
+
static asciiToHex(str) {
|
49
|
+
if (!str)
|
50
|
+
return "";
|
51
|
+
|
52
|
+
let hex = "";
|
53
|
+
for (let n = 0; n < str.length; n++) {
|
54
|
+
hex += str.charCodeAt(n).toString(16)
|
55
|
+
}
|
56
|
+
return hex;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
module.exports = {
|
61
|
+
TransactionHelper
|
62
|
+
}
|