-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathExtrinsicBuilder.swift
379 lines (306 loc) · 12.1 KB
/
ExtrinsicBuilder.swift
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
import Foundation
import BigInt
import IrohaCrypto
public protocol ExtrinsicBuilderProtocol: AnyObject {
func with<A: Codable>(address: A) throws -> Self
func with(nonce: UInt32) -> Self
func with(era: Era, blockHash: String) -> Self
func with(tip: BigUInt) -> Self
func with(batchType: ExtrinsicBatch) -> Self
func with(runtimeJsonContext: RuntimeJsonContext) -> Self
func with(signaturePayloadFormat: ExtrinsicSignaturePayloadFormat) -> Self
func adding<T: RuntimeCallable>(call: T) throws -> Self
func adding(rawCall: Data) throws -> Self
func adding(extrinsicExtension: ExtrinsicExtension) -> Self
func wrappingCalls(for mapClosure: (JSON) throws -> JSON) throws -> Self
func getCalls() -> [JSON]
func reset() -> Self
func signing(by signer: (Data) throws -> Data,
of type: CryptoType,
using encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol) throws -> Self
func signing(
by signer: (Data) throws -> JSON,
using encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws -> Self
func buildRawSignature(
using signer: (Data) throws -> Data,
encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws -> Data
func buildSignaturePayload(
encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws -> Data
func build(encodingBy encoder: DynamicScaleEncoding, metadata: RuntimeMetadataProtocol) throws -> Data
}
public extension ExtrinsicBuilderProtocol {
func with(shouldUseAtomicBatch: Bool) -> Self {
with(batchType: shouldUseAtomicBatch ? .atomic : .untilFail)
}
}
public enum ExtrinsicBuilderError: Error {
case missingCall
case missingNonce
case missingAddress
case unsupportedSignedExtension(_ value: String)
case unsupportedBatch
}
public class ExtrinsicBuilder {
private let specVersion: UInt32
private let transactionVersion: UInt32
private let genesisHash: String
private var calls: [JSON]
private var blockHash: String
private var address: JSON?
private var nonce: UInt32
private var era: Era
private var tip: BigUInt
private var signature: ExtrinsicSignature?
private var signaturePayloadFormat: ExtrinsicSignaturePayloadFormat = .regular
private var batchType: ExtrinsicBatch = .atomic
private var runtimeJsonContext: RuntimeJsonContext?
private var additionalExtensions: [ExtrinsicExtension] = []
public init(specVersion: UInt32,
transactionVersion: UInt32,
genesisHash: String) {
self.specVersion = specVersion
self.transactionVersion = transactionVersion
self.genesisHash = genesisHash
self.blockHash = genesisHash
self.era = .immortal
self.tip = 0
self.nonce = 0
self.calls = []
}
private func prepareExtrinsicCall(for metadata: RuntimeMetadataProtocol) throws -> JSON {
guard !calls.isEmpty else {
throw ExtrinsicBuilderError.missingCall
}
guard calls.count > 1 else {
return calls[0]
}
let callName: String
switch batchType {
case .atomic:
if metadata.getCall(from: KnowRuntimeModule.Utility.name, with: KnowRuntimeModule.Utility.batchAll) != nil {
callName = KnowRuntimeModule.Utility.batchAll
} else {
callName = KnowRuntimeModule.Utility.batchAtomic
}
case .untilFail:
callName = KnowRuntimeModule.Utility.batch
case .ignoreFails:
callName = KnowRuntimeModule.Utility.forceBatch
}
let call = RuntimeCall(moduleName: KnowRuntimeModule.Utility.name,
callName: callName,
args: BatchArgs(calls: calls))
guard metadata.getCall(from: call.moduleName, with: call.callName) != nil else {
throw ExtrinsicBuilderError.unsupportedBatch
}
return try call.toScaleCompatibleJSON(with: runtimeJsonContext?.toRawContext())
}
private func createExtra() throws -> ExtrinsicExtra {
var extra = ExtrinsicExtra()
try extra.setEra(era)
extra.setNonce(nonce)
extra.setTip(tip)
for extrinsicExtension in additionalExtensions {
extrinsicExtension.setAdditionalExtra(to: &extra, context: runtimeJsonContext?.toRawContext())
}
return extra
}
private func appendExtraToPayload(encodingBy encoder: DynamicScaleEncoding) throws {
let extra = try createExtra()
try encoder.append(extra, ofType: GenericType.extrinsicExtra.name, with: runtimeJsonContext?.toRawContext())
}
private func appendAdditionalSigned(
encodingBy encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws {
for checkString in metadata.getSignedExtensions() {
guard let check = ExtrinsicCheck(rawValue: checkString) else {
continue
}
switch check {
case .genesis:
try encoder.appendBytes(json: .stringValue(genesisHash))
case .mortality:
try encoder.appendBytes(json: .stringValue(blockHash))
case .specVersion:
try encoder.append(encodable: specVersion)
case .txVersion:
try encoder.append(encodable: transactionVersion)
default:
continue
}
}
}
private func prepareParitySignerSignaturePayload(
encodingBy encoder: DynamicScaleEncoding,
using metadata: RuntimeMetadataProtocol
) throws -> Data {
let call = try prepareExtrinsicCall(for: metadata)
let callEncoder = encoder.newEncoder()
try callEncoder.append(json: call, type: GenericType.call.name)
let encodedCall = try callEncoder.encode()
try encoder.append(encodable: encodedCall)
try appendExtraToPayload(encodingBy: encoder)
try appendAdditionalSigned(encodingBy: encoder, metadata: metadata)
return try encoder.encode()
}
private func prepareRegularSignaturePayload(
encodingBy encoder: DynamicScaleEncoding,
using metadata: RuntimeMetadataProtocol
) throws -> Data {
let payload = try prepareNotHashedSignaturePayload(encodingBy: encoder, using: metadata)
return try ExtrinsicSignatureConverter.convertExtrinsicPayloadToRegular(payload)
}
private func prepareNotHashedSignaturePayload(
encodingBy encoder: DynamicScaleEncoding,
using metadata: RuntimeMetadataProtocol
) throws -> Data {
let call = try prepareExtrinsicCall(for: metadata)
try encoder.append(json: call, type: GenericType.call.name)
try appendExtraToPayload(encodingBy: encoder)
try appendAdditionalSigned(encodingBy: encoder, metadata: metadata)
let payload = try encoder.encode()
return payload
}
private func prepareSignaturePayload(
encodingBy encoder: DynamicScaleEncoding,
using metadata: RuntimeMetadataProtocol
) throws -> Data {
switch signaturePayloadFormat {
case .regular:
return try prepareRegularSignaturePayload(encodingBy: encoder, using: metadata)
case .paritySigner:
return try prepareParitySignerSignaturePayload(encodingBy: encoder, using: metadata)
case .extrinsicPayload:
return try prepareNotHashedSignaturePayload(encodingBy: encoder, using: metadata)
}
}
}
extension ExtrinsicBuilder: ExtrinsicBuilderProtocol {
public func with<A: Codable>(address: A) throws -> Self {
self.address = try address.toScaleCompatibleJSON(with: runtimeJsonContext?.toRawContext())
self.signature = nil
return self
}
public func with(nonce: UInt32) -> Self {
self.nonce = nonce
self.signature = nil
return self
}
public func with(era: Era, blockHash: String) -> Self {
self.era = era
self.blockHash = blockHash
self.signature = nil
return self
}
public func with(tip: BigUInt) -> Self {
self.tip = tip
self.signature = nil
return self
}
public func with(batchType: ExtrinsicBatch) -> Self {
self.batchType = batchType
return self
}
public func with(runtimeJsonContext: RuntimeJsonContext) -> Self {
self.runtimeJsonContext = runtimeJsonContext
return self
}
public func with(signaturePayloadFormat: ExtrinsicSignaturePayloadFormat) -> Self {
self.signaturePayloadFormat = signaturePayloadFormat
return self
}
public func adding<T: RuntimeCallable>(call: T) throws -> Self {
let json = try call.toScaleCompatibleJSON(with: runtimeJsonContext?.toRawContext())
calls.append(json)
return self
}
public func adding(rawCall: Data) throws -> Self {
let json = JSON.stringValue(rawCall.toHex())
calls.append(json)
return self
}
public func adding(extrinsicExtension: ExtrinsicExtension) -> Self {
additionalExtensions.append(extrinsicExtension)
return self
}
public func wrappingCalls(for mapClosure: (JSON) throws -> JSON) throws -> Self {
let newCalls = try calls.map { try mapClosure($0) }
self.calls = newCalls
return self
}
public func getCalls() -> [JSON] {
calls
}
public func reset() -> Self {
calls = []
return self
}
public func signing(by signer: (Data) throws -> Data,
of type: CryptoType,
using encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol) throws -> Self {
guard let address = address else {
throw ExtrinsicBuilderError.missingAddress
}
let data = try prepareSignaturePayload(encodingBy: encoder, using: metadata)
let rawSignature = try signer(data)
let signature: MultiSignature
switch type {
case .sr25519:
signature = .sr25519(data: rawSignature)
case .ed25519:
signature = .ed25519(data: rawSignature)
case .ecdsa:
signature = .ecdsa(data: rawSignature)
}
let sigJson = try signature.toScaleCompatibleJSON(with: runtimeJsonContext?.toRawContext())
let extra = try createExtra()
self.signature = ExtrinsicSignature(address: address,
signature: sigJson,
extra: extra)
return self
}
public func signing(
by signer: (Data) throws -> JSON,
using encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws -> Self {
guard let address = address else {
throw ExtrinsicBuilderError.missingAddress
}
let data = try prepareSignaturePayload(encodingBy: encoder, using: metadata)
let sigJson = try signer(data)
let extra = try createExtra()
self.signature = ExtrinsicSignature(address: address, signature: sigJson, extra: extra)
return self
}
public func buildRawSignature(
using signer: (Data) throws -> Data,
encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws -> Data {
let data = try prepareSignaturePayload(encodingBy: encoder, using: metadata)
return try signer(data)
}
public func buildSignaturePayload(
encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol
) throws -> Data {
try prepareSignaturePayload(encodingBy: encoder, using: metadata)
}
public func build(encodingBy encoder: DynamicScaleEncoding,
metadata: RuntimeMetadataProtocol) throws -> Data {
let call = try prepareExtrinsicCall(for: metadata)
let extrinsic = Extrinsic(signature: signature, call: call)
try encoder.append(extrinsic, ofType: GenericType.extrinsic.name, with: runtimeJsonContext?.toRawContext())
return try encoder.encode()
}
}