@tetherto/mdk-core 0.0.0 → 0.12.1

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.
Files changed (54) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +572 -0
  3. package/index.js +519 -0
  4. package/lib/address-policy.js +124 -0
  5. package/lib/local-discovery.js +321 -0
  6. package/lib/services/actions.service.js +68 -0
  7. package/lib/services/alerts.service.js +207 -0
  8. package/lib/services/comments.service.js +118 -0
  9. package/lib/services/log-history.service.js +307 -0
  10. package/lib/services/logs.service.js +249 -0
  11. package/lib/services/pool-utils/constants.js +20 -0
  12. package/lib/services/pool-utils/time.js +49 -0
  13. package/lib/services/pool.service.js +397 -0
  14. package/lib/services/provisioning.service.js +420 -0
  15. package/lib/services/settings.service.js +26 -0
  16. package/lib/services/snaps.service.js +181 -0
  17. package/lib/services/stats.service.js +205 -0
  18. package/lib/stats/README.md +80 -0
  19. package/lib/stats/index.js +57 -0
  20. package/lib/stats/ops/alerts_aggr.js +40 -0
  21. package/lib/stats/ops/alerts_group_cnt.js +43 -0
  22. package/lib/stats/ops/arr_concat.js +27 -0
  23. package/lib/stats/ops/array_obj_calc.js +41 -0
  24. package/lib/stats/ops/avg.js +42 -0
  25. package/lib/stats/ops/cnt.js +19 -0
  26. package/lib/stats/ops/group.js +38 -0
  27. package/lib/stats/ops/group_avg.js +53 -0
  28. package/lib/stats/ops/group_cnt.js +38 -0
  29. package/lib/stats/ops/group_max.js +40 -0
  30. package/lib/stats/ops/group_multiple_stats.js +40 -0
  31. package/lib/stats/ops/group_sum.js +40 -0
  32. package/lib/stats/ops/index.js +35 -0
  33. package/lib/stats/ops/nested_obj_concat.js +54 -0
  34. package/lib/stats/ops/obj_concat.js +33 -0
  35. package/lib/stats/ops/sum.js +27 -0
  36. package/lib/stats/utils.js +99 -0
  37. package/lib/templates/alerts.js +128 -0
  38. package/lib/templates/stats.js +476 -0
  39. package/lib/things/constants.js +65 -0
  40. package/lib/things/container.js +84 -0
  41. package/lib/things/miner.js +190 -0
  42. package/lib/things/powermeter.js +30 -0
  43. package/lib/things/sensor.js +18 -0
  44. package/lib/things/thing.js +93 -0
  45. package/lib/utils.js +603 -0
  46. package/lib/worker-infra.js +221 -0
  47. package/package.json +64 -1
  48. package/services.js +246 -0
  49. package/utils/compose-yaml.js +56 -0
  50. package/utils/constants.js +30 -0
  51. package/utils/index.js +29 -0
  52. package/utils/initialize.js +148 -0
  53. package/utils/service-bootstrap.js +271 -0
  54. package/worker.js +9 -0
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const lUtils = require('../utils')
4
+
5
+ function groupMax (op, cur, entry, ext) {
6
+ if (!cur) {
7
+ cur = { res: lUtils.createResMap() }
8
+ }
9
+
10
+ if (!entry) {
11
+ return cur
12
+ }
13
+
14
+ if (op.filter && !op.filter(entry)) {
15
+ return cur
16
+ }
17
+
18
+ if (!op.group) {
19
+ return cur
20
+ }
21
+ const groupName = op.group(entry, ext)
22
+
23
+ if (!groupName || !lUtils.isSafeKey(groupName)) {
24
+ return cur
25
+ }
26
+
27
+ if (!cur.res[groupName]) {
28
+ cur.res[groupName] = 0
29
+ }
30
+
31
+ const val = lUtils.getVal(entry, op.src) || 0
32
+
33
+ cur.res[groupName] = cur.res[groupName] === undefined ? val : Math.max(cur.res[groupName], val)
34
+
35
+ return cur
36
+ }
37
+
38
+ module.exports = {
39
+ calc: groupMax
40
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const lUtils = require('../utils')
4
+
5
+ function groupMultipleStats (op, cur, entry, ext) {
6
+ if (!cur) {
7
+ cur = { res: lUtils.createResMap() }
8
+ }
9
+
10
+ if (!entry) {
11
+ return cur
12
+ }
13
+
14
+ if (!op.srcs || !Array.isArray(op.srcs)) {
15
+ return cur
16
+ }
17
+
18
+ if (op.filter && !op.filter(entry)) {
19
+ return cur
20
+ }
21
+
22
+ if (!op.group) {
23
+ return cur
24
+ }
25
+ const groupName = op.group(entry, ext)
26
+
27
+ if (!groupName || !lUtils.isSafeKey(groupName)) {
28
+ return cur
29
+ }
30
+
31
+ cur.res[groupName] = op.srcs.reduce(
32
+ (prev, src) => ({ ...prev, [src.name]: lUtils.getVal(entry, src.src) }),
33
+ {}
34
+ )
35
+ return cur
36
+ }
37
+
38
+ module.exports = {
39
+ calc: groupMultipleStats
40
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const lUtils = require('../utils')
4
+
5
+ function groupSum (op, cur, entry, ext) {
6
+ if (!cur) {
7
+ cur = { res: lUtils.createResMap() }
8
+ }
9
+
10
+ if (!entry) {
11
+ return cur
12
+ }
13
+
14
+ if (op.filter && !op.filter(entry)) {
15
+ return cur
16
+ }
17
+
18
+ if (!op.group) {
19
+ return cur
20
+ }
21
+ const groupName = op.group(entry, ext)
22
+
23
+ if (!groupName || !lUtils.isSafeKey(groupName)) {
24
+ return cur
25
+ }
26
+
27
+ if (!cur.res[groupName]) {
28
+ cur.res[groupName] = 0
29
+ }
30
+
31
+ const val = lUtils.getVal(entry, op.src)
32
+
33
+ cur.res[groupName] += val ? +val : 0
34
+
35
+ return cur
36
+ }
37
+
38
+ module.exports = {
39
+ calc: groupSum
40
+ }
@@ -0,0 +1,35 @@
1
+ 'use strict'
2
+
3
+ // Static registry of stats ops. Op names come from stats specs (`op.op`,
4
+ // `objOps[].op`, `nestedOps[].op`), so they are never turned into a require
5
+ // path: an unknown name is refused instead of resolved on disk. Entries are
6
+ // lazy because array_obj_calc and nested_obj_concat look ops up themselves.
7
+ const OPS = {
8
+ alerts_aggr: () => require('./alerts_aggr'),
9
+ alerts_group_cnt: () => require('./alerts_group_cnt'),
10
+ arr_concat: () => require('./arr_concat'),
11
+ array_obj_calc: () => require('./array_obj_calc'),
12
+ avg: () => require('./avg'),
13
+ cnt: () => require('./cnt'),
14
+ group: () => require('./group'),
15
+ group_avg: () => require('./group_avg'),
16
+ group_cnt: () => require('./group_cnt'),
17
+ group_max: () => require('./group_max'),
18
+ group_multiple_stats: () => require('./group_multiple_stats'),
19
+ group_sum: () => require('./group_sum'),
20
+ nested_obj_concat: () => require('./nested_obj_concat'),
21
+ obj_concat: () => require('./obj_concat'),
22
+ sum: () => require('./sum')
23
+ }
24
+
25
+ function getOp (name) {
26
+ if (typeof name !== 'string' || !Object.hasOwn(OPS, name)) {
27
+ throw new Error(`ERR_STATS_OP_UNKNOWN: ${String(name).slice(0, 64)}`)
28
+ }
29
+ return OPS[name]()
30
+ }
31
+
32
+ module.exports = {
33
+ getOp,
34
+ OP_NAMES: Object.freeze(Object.keys(OPS))
35
+ }
@@ -0,0 +1,54 @@
1
+ 'use strict'
2
+
3
+ const lUtils = require('../utils')
4
+ const { getOp } = require('./index')
5
+
6
+ function nestedObjConcat (op, cur, entry, ext) {
7
+ if (!cur) {
8
+ cur = { res: lUtils.createResMap() }
9
+ }
10
+
11
+ if (!entry) {
12
+ return cur
13
+ }
14
+
15
+ if (op.filter && !op.filter(entry)) {
16
+ return cur
17
+ }
18
+
19
+ const val = lUtils.getVal(entry, op.src)
20
+ for (const keys in val) {
21
+ if (!lUtils.isSafeKey(keys)) continue
22
+ if (!cur.res[keys]) {
23
+ cur.res[keys] = val[keys]
24
+ } else {
25
+ const currObj = cur.res[keys]
26
+ const newObj = val[keys]
27
+ if (typeof newObj === 'object' && op.nestedOps) {
28
+ concatNestedObj(currObj, newObj, op.nestedOps)
29
+ }
30
+ }
31
+ }
32
+
33
+ return cur
34
+ }
35
+
36
+ const concatNestedObj = (currObj, newObj, ops) => {
37
+ if (!currObj || typeof currObj !== 'object') return
38
+ for (const key in newObj) {
39
+ if (!lUtils.isSafeKey(key)) continue
40
+ const keyOp = ops.find(v => v.src === key)
41
+ if (keyOp) {
42
+ const calcFn = getOp(keyOp.op)
43
+ const calcRes = calcFn.calc(keyOp, currObj[key] && { res: currObj[key] }, newObj)
44
+ currObj[key] = calcRes.res
45
+ }
46
+ if (typeof newObj[key] === 'object' && !Array.isArray(newObj[key])) {
47
+ concatNestedObj(currObj[key], newObj[key], ops)
48
+ }
49
+ }
50
+ }
51
+
52
+ module.exports = {
53
+ calc: nestedObjConcat
54
+ }
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const lUtils = require('../utils')
4
+
5
+ function objConcat (op, cur, entry, ext) {
6
+ if (!cur) {
7
+ cur = { res: lUtils.createResMap() }
8
+ }
9
+
10
+ if (!entry) {
11
+ return cur
12
+ }
13
+
14
+ if (op.filter && !op.filter(entry)) {
15
+ return cur
16
+ }
17
+
18
+ const val = lUtils.getVal(entry, op.src)
19
+ for (const keys in val) {
20
+ if (!lUtils.isSafeKey(keys)) continue
21
+ if (!cur.res[keys]) {
22
+ cur.res[keys] = val[keys]
23
+ } else {
24
+ cur.res[keys] += val[keys]
25
+ }
26
+ }
27
+
28
+ return cur
29
+ }
30
+
31
+ module.exports = {
32
+ calc: objConcat
33
+ }
@@ -0,0 +1,27 @@
1
+ 'use strict'
2
+
3
+ const lUtils = require('./../utils')
4
+
5
+ function sum (op, cur, entry, ext) {
6
+ if (!cur) {
7
+ cur = { res: 0 }
8
+ }
9
+
10
+ if (!entry) {
11
+ return cur
12
+ }
13
+
14
+ if (op.filter && !op.filter(entry, ext)) {
15
+ return cur
16
+ }
17
+
18
+ const val = lUtils.getVal(entry, op.src)
19
+
20
+ cur.res += val ? +val : 0
21
+
22
+ return cur
23
+ }
24
+
25
+ module.exports = {
26
+ calc: sum
27
+ }
@@ -0,0 +1,99 @@
1
+ 'use strict'
2
+
3
+ // Keypaths are a small, fixed grammar — `a.b.c`, `a[0].b`, `a['k']` — walked
4
+ // segment by segment. They used to be compiled with `new Function`, which made
5
+ // any caller-influenced keypath arbitrary code; the walker below cannot do more
6
+ // than read properties, and refuses the prototype-reaching names outright.
7
+ const FORBIDDEN_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype'])
8
+ const SEGMENT_RE = /^(?:\.?([A-Za-z_$][\w$]*)|\[(\d+)\]|\[(?:'([^'\\]*)'|"([^"\\]*)")\])/
9
+ const MAX_KEYPATH_LENGTH = 512
10
+ const MAX_CACHED_KEYPATHS = 1024
11
+
12
+ const _parsed = new Map()
13
+
14
+ function parseKeypath (keypath) {
15
+ const cached = _parsed.get(keypath)
16
+ if (cached) return cached
17
+
18
+ if (keypath.length > MAX_KEYPATH_LENGTH) throw new Error('ERR_STATS_KEYPATH_INVALID: keypath too long')
19
+
20
+ const segments = []
21
+ let rest = keypath
22
+ while (rest.length) {
23
+ const m = SEGMENT_RE.exec(rest)
24
+ // A leading `.` is only valid between segments, never first.
25
+ if (!m || (segments.length === 0 && rest[0] === '.')) {
26
+ throw new Error(`ERR_STATS_KEYPATH_INVALID: ${keypath}`)
27
+ }
28
+ const seg = m[1] ?? m[2] ?? m[3] ?? m[4]
29
+ if (FORBIDDEN_SEGMENTS.has(seg)) {
30
+ throw new Error(`ERR_STATS_KEYPATH_FORBIDDEN: ${keypath}`)
31
+ }
32
+ segments.push(seg)
33
+ rest = rest.slice(m[0].length)
34
+ }
35
+ if (!segments.length) throw new Error(`ERR_STATS_KEYPATH_INVALID: ${keypath}`)
36
+
37
+ if (_parsed.size >= MAX_CACHED_KEYPATHS) _parsed.clear()
38
+ _parsed.set(keypath, segments)
39
+ return segments
40
+ }
41
+
42
+ // Same semantics as the old compiled `root.<keypath>` inside try/catch: any
43
+ // missing (null/undefined) link yields undefined instead of throwing.
44
+ function pathResolve (keypath) {
45
+ const segments = parseKeypath(keypath)
46
+ return (root) => {
47
+ let cur = root
48
+ for (const seg of segments) {
49
+ if (cur === null || cur === undefined) return undefined
50
+ cur = cur[seg]
51
+ }
52
+ return cur
53
+ }
54
+ }
55
+
56
+ function getVal (entry, src) {
57
+ if (!src) {
58
+ return null
59
+ }
60
+
61
+ if (typeof src === 'function') {
62
+ return src(entry)
63
+ }
64
+
65
+ if (typeof src === 'string') {
66
+ return pathResolve(src)(entry)
67
+ }
68
+
69
+ return null
70
+ }
71
+
72
+ // Result maps in the stats ops are keyed by data (device `info.container`,
73
+ // alert severities, snapshot object keys). They are created without a
74
+ // prototype and these names are skipped, so a hostile key can neither reach
75
+ // nor pollute Object.prototype.
76
+ function isSafeKey (key) {
77
+ return typeof key !== 'symbol' && !FORBIDDEN_SEGMENTS.has(String(key))
78
+ }
79
+
80
+ function createResMap () {
81
+ return Object.create(null)
82
+ }
83
+
84
+ const groupBy = (field) => {
85
+ return (entry, ext) => {
86
+ if (!ext) {
87
+ return null
88
+ }
89
+ return getVal(ext, field)
90
+ }
91
+ }
92
+
93
+ module.exports = {
94
+ getVal,
95
+ groupBy,
96
+ parseKeypath,
97
+ isSafeKey,
98
+ createResMap
99
+ }
@@ -0,0 +1,128 @@
1
+ 'use strict'
2
+
3
+ const { isValidSnap, isOffline } = require('../utils')
4
+
5
+ // Base alert spec data per device family. Workers compose their own
6
+ // { specs } template object from these — the exported objects are never
7
+ // mutated: one process can host several worker families, and a mutated module
8
+ // singleton would leak one family's specs into all of them.
9
+
10
+ function isValidPoolConfigSnap (ctx, snap) {
11
+ return (
12
+ isValidSnap(snap) &&
13
+ !isOffline(snap) &&
14
+ ctx.thingConf.pools &&
15
+ ctx.thingConf.pools.length > 0 &&
16
+ snap.config.pool_config &&
17
+ snap.config.pool_config.length > 0
18
+ )
19
+ }
20
+
21
+ function isIpPoolUsername (ip, minerPools, confPools) {
22
+ for (let i = 0; i < confPools.length; i++) {
23
+ const formattedIP = ip.replace(/\./g, 'x')
24
+ const username = minerPools[i].username
25
+
26
+ if (!username.includes(formattedIP)) {
27
+ return false
28
+ }
29
+ }
30
+ return true
31
+ }
32
+
33
+ function isCorrectPoolUsername (id, minerPools, confPools) {
34
+ for (let i = 0; i < confPools.length; i++) {
35
+ const username = minerPools[i].username
36
+ if (!username.includes(id)) {
37
+ return false
38
+ }
39
+ }
40
+ return true
41
+ }
42
+
43
+ function isConfigWorkerNameInPoolUsername (minerPools, confPools) {
44
+ for (let i = 0; i < confPools.length; i++) {
45
+ const workerName = confPools[i].worker_name
46
+ const username = minerPools[i].username
47
+
48
+ if (!username.includes(workerName)) {
49
+ return false
50
+ }
51
+ }
52
+ return true
53
+ }
54
+
55
+ function areMinerPoolsUrlsCorrectlySetup (minerPools, confPools) {
56
+ return confPools.every((pool, i) => minerPools[i]?.url === pool.url)
57
+ }
58
+
59
+ const DEFAULT = {}
60
+
61
+ const MINER_DEFAULT = {
62
+ ...DEFAULT,
63
+ wrong_miner_pool: {
64
+ valid: (ctx, snap) => {
65
+ return isValidPoolConfigSnap(ctx, snap)
66
+ },
67
+ probe: (ctx, snap) => {
68
+ const configPools = ctx.thingConf.pools
69
+ const minerPools = snap.config.pool_config
70
+ return !areMinerPoolsUrlsCorrectlySetup(minerPools, configPools)
71
+ }
72
+ },
73
+ wrong_miner_subaccount: {
74
+ valid: (ctx, snap) => {
75
+ return isValidPoolConfigSnap(ctx, snap)
76
+ },
77
+ probe: (ctx, snap) => {
78
+ const configPools = ctx.thingConf.pools
79
+ const minerPools = snap.config.pool_config
80
+ if (configPools.length > 0 && minerPools.length > 0) {
81
+ return !isConfigWorkerNameInPoolUsername(minerPools, configPools)
82
+ }
83
+ return false
84
+ }
85
+ },
86
+ wrong_worker_name: {
87
+ valid: (ctx, snap) => {
88
+ return isValidPoolConfigSnap(ctx, snap)
89
+ },
90
+ probe: (ctx, snap) => {
91
+ const configPools = ctx.thingConf.pools
92
+ const minerPools = snap.config.pool_config
93
+ const id = ctx.id
94
+ const ip = snap.config.network_config.ip_address
95
+ if (configPools.length > 0 && minerPools.length > 0) {
96
+ return (
97
+ !isCorrectPoolUsername(id, minerPools, configPools) &&
98
+ !isIpPoolUsername(ip, minerPools, configPools)
99
+ )
100
+ }
101
+ return false
102
+ }
103
+ },
104
+ ip_worker_name: {
105
+ valid: (ctx, snap) => {
106
+ return isValidPoolConfigSnap(ctx, snap)
107
+ },
108
+ probe: (ctx, snap) => {
109
+ const configPools = ctx.thingConf.pools
110
+ const minerPools = snap.config.pool_config
111
+ const ip = snap.config.network_config.ip_address
112
+
113
+ if (configPools.length > 0 && minerPools.length > 0) {
114
+ return isIpPoolUsername(ip, minerPools, configPools)
115
+ }
116
+ return false
117
+ }
118
+ }
119
+ }
120
+
121
+ module.exports = {
122
+ specs: {
123
+ default: DEFAULT,
124
+ miner_default: MINER_DEFAULT,
125
+ container_default: DEFAULT,
126
+ powermeter_default: {}
127
+ }
128
+ }