This repository has been archived by the owner on Jul 18, 2020. It is now read-only.
/
utils.js
164 lines (145 loc) · 4.19 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// @flow
import cbor from 'cbor'
import borc from 'borc'
import bs58 from 'bs58'
import blake from 'blakejs'
import type { TxInputType, TxType } from './tx'
type TxIdHexType = string
type TxBodyHexType = string
const getUtxoId = (input: TxInputType) => `${input.txId}${input.idx}`
const structUtxo = (
receiver: string,
amount: number,
utxoHash: string,
txIndex: number = 0,
blockNum: ?number = 0,
) => ({
utxo_id: `${utxoHash}${txIndex}`,
tx_hash: utxoHash,
tx_index: txIndex,
receiver,
amount,
block_num: blockNum,
})
/**
* We need to use this function cuz there are some extra-long addresses
* existing on Cardano mainnet. Some of them exceed 10K characters in length,
* and Postgres can't store it.
* We don't care about making these non-standard addresses spendable, so any address
* over 1K characters is just truncated.
*/
const fixLongAddress = (address: string): string => (address && address.length > 1000
? `${address.substr(0, 497)}...${address.substr(address.length - 500, 500)}`
: address)
const getTxsUtxos = (txs: Array<TxType>) => txs.reduce((res, tx) => {
const { id, outputs, blockNum } = tx
outputs.forEach((output, index) => {
const utxo = structUtxo(
fixLongAddress(output.address), output.value, id, index, blockNum)
res[`${id}${index}`] = utxo
})
return res
}, {})
const decodedTxToBase = (decodedTx) => {
if (Array.isArray(decodedTx)) {
// eslint-disable-next-line default-case
switch (decodedTx.length) {
case 2: {
const signed = decodedTx
return signed[0]
}
case 3: {
const base = decodedTx
return base
}
}
}
throw new Error(`Unexpected decoded tx structure! ${JSON.stringify(decodedTx)}`)
}
type CborEncoderType = {
encode: Function
}
class CborIndefiniteLengthArray {
elements: Array<{}>
cborEncoder: CborEncoderType
constructor(elements, cborEncoder) {
this.elements = elements
this.cborEncoder = cborEncoder
}
encodeCBOR(encoder) {
return encoder.push(
Buffer.concat([
Buffer.from([0x9f]), // indefinite array prefix
...this.elements.map((e) => this.cborEncoder.encode(e)),
Buffer.from([0xff]), // end of array
]),
)
}
}
const selectCborEncoder = (outputs): CborEncoderType => {
const maxAddressLen = Math.max(...outputs.map(([[taggedAddress]]) => taggedAddress.value.length))
if (maxAddressLen > 5000) {
return borc
}
return cbor
}
const packRawTxIdAndBody = (decodedTxBody): [TxIdHexType, TxBodyHexType] => {
if (!decodedTxBody) {
throw new Error('Cannot decode inputs from undefined transaction!')
}
try {
const [inputs, outputs, attributes] = decodedTxToBase(decodedTxBody)
const cborEncoder: CborEncoderType = selectCborEncoder(outputs)
const enc = cborEncoder.encode([
new CborIndefiniteLengthArray(inputs, cborEncoder),
new CborIndefiniteLengthArray(outputs, cborEncoder),
attributes,
])
const txId = blake.blake2bHex(enc, null, 32)
const txBody = enc.toString('hex')
return [txId, txBody]
} catch (e) {
throw new Error(`Failed to convert raw transaction to ID! ${JSON.stringify(e)}`)
}
}
const rawTxToObj = (tx: Array<any>, extraData: {
blockHash: ?string,
blockNum: ?number,
txOrdinal: ?number,
txTime: Date,
}): TxType => {
const [[inputs, outputs], witnesses] = tx
const [txId, txBody] = packRawTxIdAndBody(tx)
return {
isGenesis: false,
id: txId,
inputs: inputs.map(inp => {
const [type, tagged] = inp
const [inputTxId, idx] = cbor.decode(tagged.value)
return { type, txId: inputTxId.toString('hex'), idx }
}),
outputs: outputs.map(out => {
const [address, value] = out
return { address: bs58.encode(cbor.encode(address)), value }
}),
witnesses: witnesses.map(w => {
const [type, tagged] = w
return { type, sign: cbor.decode(tagged.value) }
}),
txBody,
...extraData,
}
}
const headerToId = (header: string, type: number) => {
const headerData = cbor.encode([type, header])
const id = blake.blake2bHex(headerData, null, 32)
return id
}
export default {
structUtxo,
getUtxoId,
fixLongAddress,
getTxsUtxos,
rawTxToObj,
headerToId,
}