-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmutation_resolver.mjs
232 lines (203 loc) · 7.14 KB
/
mutation_resolver.mjs
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
import serialize from "eosio-wasm-js/serialize.mjs";
import serialize_transaction_header from "eosio-wasm-js/transaction_header.mjs";
import { GraphQLError } from "graphql";
const default_config = {
blocksBehind: 3,
expireSeconds: 30,
max_net_usage_words: 0,
max_cpu_usage_ms: 0
};
const validate_actions = () => {
throw new GraphQLError(`Invalid AntelopeQL query.`, {
extensions: {
why: "AntelopeQL enforces one action per object in the list to preserve the top to bottom execution order.",
example: "actions: [{ action1: … }, { action2: … }]"
}
});
};
/**
* Serializes GraphQL mutation actions into binary instructions.
* @param {Array<Object>} actions List of actions to serialize.
* @param {Array<Object>} ast_list Abstract syntax tree list of data to serialize.
* @returns {String} Serialized transaction body as hexadecimal string.
*/
async function get_transaction_body(actions, ast_list) {
let actions_list_to_serialize = [];
for (const action of actions) {
if (Object.values(action).length > 1) validate_actions(action);
const [contract] = Object.keys(action);
const [values] = Object.values(action);
const action_fields = Object.keys(values);
if (action_fields.length > 1) validate_actions(action);
actions_list_to_serialize.push(
...action_fields.map((action) => ({
contract,
action,
data: values[action]
}))
);
}
let _actions = [];
let _context_free_actions = [];
let transaction_extensions = "00";
for (const action of actions_list_to_serialize) {
const {
contract,
action: action_name,
data: { authorization, ...data }
} = action;
const build_serialize_list = async (data, instructions) => {
let serialize_list = [];
for (const instruction of instructions) {
const { $info, name, type } = instruction;
const datum = data[name];
const next_instruction = ast_list[contract][type]; // Indicates that the AST type is not serialisable, but is another type on the AST list.
// Handles ABI variant types see https://en.cppreference.com/w/cpp/utility/variant
if ($info.variant) {
if (Object.keys(data).length > 1)
throw new Error(`Must only include one type for variant.`);
if (!datum) continue;
else
serialize_list.push({
type: "varuint32",
value: instructions.findIndex((i) => i.type == type)
});
}
if ($info.binary_ex) $info.optional = false; // Binary extentions are optional (GraphQL types) but should not serialize an optional type.
if ($info.optional)
serialize_list.push({ type: "bool", value: datum != undefined }); // Add an optional item to serialize list.
if ($info.list)
if (datum !== undefined)
serialize_list.push({ type: "varuint32", value: datum.length }); // Add an length of list to serialize list.
// Indicates that we need to recursion loop through each data item.
if (next_instruction)
if ($info.list) {
if (datum != undefined && !$info.binary_ex)
for await (const d of datum)
serialize_list.push(
...(await build_serialize_list(await d, next_instruction))
);
}
// None list recursion
else
serialize_list.push(
...(await build_serialize_list(datum, next_instruction))
);
// Indicates that the list of data can be serilaized and so is pushed into serialize_list.
else if ($info.list && datum !== undefined)
for await (const d of datum) serialize_list.push({ type, value: d });
else if (datum !== undefined)
serialize_list.push({ type, value: datum }); // Native eoio types than can be serialized.
}
return serialize_list;
};
const hex_string = await build_serialize_list(
data,
ast_list[contract][action_name]
).then(async (list) => {
let hex_string = "";
for await (const { type, value } of list)
hex_string += await serialize[type](await value);
return hex_string;
});
if (authorization?.length)
_actions.push({
account: contract.replace(/_/gmu, "."),
action: action_name.replace(/_/gmu, "."),
authorization,
data,
hex_data: hex_string
});
else
_context_free_actions.push({
account: contract.replace(/_/gmu, "."),
action: action_name.replace(/_/gmu, "."),
authorization: [],
data,
hex_data: hex_string
});
}
return {
context_free_actions: _context_free_actions.map(
({ action: name, ...action }) => ({ ...action, name })
),
actions: _actions.map(({ action: name, ...action }) => ({
...action,
name
})),
transaction_extensions: [],
transaction_body:
serialize.actions(_context_free_actions) +
serialize.actions(_actions) +
transaction_extensions
};
}
/**
* Mutation resolver for serializing EOSIO transactions.
* @param {Object} args Args.
* @param {Object} args.actions Actions list to be serialized.
* @param {Object} [args.configuration] Action configuaration.
* @param {AntelopeQLRPC} network AntelopeQL context contain fetch and url string.
* @param {Object} ast_list Abstract syntax tree list of the contract actions.
* @returns {Object} Transaction object.
*/
async function mutation_resolver(
{ actions, configuration = default_config },
network,
ast_list
) {
if (configuration.max_cpu_usage_ms > 0xff)
throw new Error("Invalid max_cpu_usage_ms value (maximum 255).");
if (configuration.max_net_usage_words > 0xffffffff)
throw new Error(
"Invalid max_net_usage_words value (maximum 4,294,967,295)."
);
const { fetch, rpc_url, ...fetchOptions } = network;
const { transaction_body, ...transaction_list } = await get_transaction_body(
actions,
ast_list
);
const { chain_id, head_block_num } = await fetch(
`${rpc_url}/v1/chain/get_info`,
{
method: "POST",
...fetchOptions
}
).then((req) => req.json());
const block_num_or_id = head_block_num - configuration.blocksBehind;
const { timestamp, block_num, ref_block_prefix } = await fetch(
`${rpc_url}/v1/chain/get_block`,
{
method: "POST",
...fetchOptions,
body: JSON.stringify({
block_num_or_id
})
}
).then((req) => req.json());
// // TaPoS expiry time.
const expiration =
Math.round(Date.parse(timestamp + "Z") / 1000) +
configuration.expireSeconds;
const txn_header = {
expiration,
ref_block_num: block_num & 0xffff,
ref_block_prefix,
max_net_usage_words: configuration.max_net_usage_words,
max_cpu_usage_ms: configuration.max_cpu_usage_ms,
delay_sec: 0
};
// Generates a transaction header for a Antelope transaction.
const transaction_header = serialize_transaction_header(txn_header);
txn_header.expiration = timestamp;
return {
chain_id,
transaction_header,
transaction_body,
transaction: {
...txn_header,
...transaction_list
}
};
}
export default mutation_resolver;