UNPKG

14.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const client_common_1 = require("@neo-one/client-common");
5const utils_1 = require("@neo-one/utils");
6const lodash_1 = tslib_1.__importDefault(require("lodash"));
7const AssetType_1 = require("../AssetType");
8const constants_1 = require("../constants");
9const errors_1 = require("../errors");
10const ScriptContainer_1 = require("../ScriptContainer");
11const utils_2 = require("../utils");
12const Witness_1 = require("../Witness");
13const attribute_1 = require("./attribute");
14const common_1 = require("./common");
15const Input_1 = require("./Input");
16const Output_1 = require("./Output");
17const TransactionType_1 = require("./TransactionType");
18const getUtilityValue = ({ outputs, utilityToken, }) => outputs
19 .filter((output) => client_common_1.common.uInt256Equal(output.asset, utilityToken.hash))
20 .reduce((acc, output) => acc.add(output.value), utils_2.utils.ZERO);
21function TransactionBase(Base) {
22 class TransactionBaseClass extends Base {
23 constructor() {
24 super(...arguments);
25 this.equals = utils_2.utils.equals(this.constructor, this, (other) => client_common_1.common.uInt256Equal(this.hash, other.hash));
26 this.toKeyString = utils_2.utils.toKeyString(TransactionBase, () => this.hashHex);
27 this.getSortedScriptHashesForVerifying = utils_2.utils.lazyAsync(async (options) => {
28 const hashes = await this.getScriptHashesForVerifying(options);
29 return [...hashes].sort();
30 });
31 this.sizeInternal = utils_2.utils.lazy(() => client_common_1.IOHelper.sizeOfUInt8 +
32 client_common_1.IOHelper.sizeOfArray(this.attributes, (attribute) => attribute.size) +
33 client_common_1.IOHelper.sizeOfArray(this.inputs, (input) => input.size) +
34 client_common_1.IOHelper.sizeOfArray(this.outputs, (output) => output.size) +
35 client_common_1.IOHelper.sizeOfArray(this.scripts, (script) => script.size) +
36 this.sizeExclusive());
37 this.networkFee = utils_2.utils.lazyAsync(async (context) => {
38 const { getOutput, utilityToken } = context;
39 const outputsForInputs = await Promise.all(this.inputs.map(getOutput));
40 const inputValue = getUtilityValue({
41 outputs: outputsForInputs,
42 utilityToken,
43 });
44 const outputValue = getUtilityValue({
45 outputs: this.outputs,
46 utilityToken,
47 });
48 const result = inputValue.sub(outputValue).sub(this.getSystemFee(context));
49 return result.lt(utils_2.utils.ZERO) ? utils_2.utils.ZERO : result;
50 });
51 this.getReferencesInternal = utils_2.utils.lazyAsync(async ({ getOutput }) => Promise.all(this.inputs.map(async (input) => getOutput(input))));
52 this.getTransactionResultsInternal = utils_2.utils.lazyAsync(async ({ getOutput }) => {
53 const inputOutputs = await this.getReferences({ getOutput });
54 const mutableResults = {};
55 const addOutputs = (outputs, negative) => {
56 outputs.forEach((output) => {
57 const key = client_common_1.common.uInt256ToHex(output.asset);
58 let result = mutableResults[key];
59 if (result === undefined) {
60 mutableResults[key] = result = utils_2.utils.ZERO;
61 }
62 mutableResults[key] = result.add(negative === true ? output.value.neg() : output.value);
63 });
64 };
65 addOutputs(inputOutputs);
66 addOutputs(this.outputs, true);
67 return lodash_1.default.pickBy(mutableResults, (value) => value !== undefined && !value.eq(utils_2.utils.ZERO));
68 });
69 this.baseGetScriptHashesForVerifyingInternal = utils_2.utils.lazyAsync(async ({ getOutput, getAsset }) => {
70 const [inputHashes, outputHashes] = await Promise.all([
71 Promise.all(this.inputs.map(async (input) => {
72 const output = await getOutput(input);
73 return client_common_1.common.uInt160ToHex(output.address);
74 })),
75 Promise.all(this.outputs.map(async (output) => {
76 const asset = await getAsset({ hash: output.asset });
77 if (client_common_1.hasFlag(asset.type, AssetType_1.AssetType.DutyFlag)) {
78 return client_common_1.common.uInt160ToHex(output.address);
79 }
80 return undefined;
81 })).then((hashes) => hashes.filter(utils_1.utils.notNull)),
82 ]);
83 const attributeHashes = this.attributes
84 .map((attribute) => attribute instanceof attribute_1.UInt160Attribute && attribute.usage === attribute_1.AttributeUsage.Script
85 ? client_common_1.common.uInt160ToHex(attribute.value)
86 : undefined)
87 .filter(utils_1.utils.notNull);
88 return new Set([...inputHashes, ...outputHashes, ...attributeHashes]);
89 });
90 this.sizeExclusive = () => 0;
91 }
92 static deserializeTransactionBaseStartWireBase({ reader, }) {
93 const type = client_common_1.assertTransactionType(reader.readUInt8());
94 const version = reader.readUInt8();
95 return { type, version };
96 }
97 static deserializeTransactionBaseEndWireBase(options) {
98 const { reader } = options;
99 const attributes = reader.readArray(() => attribute_1.deserializeAttributeWireBase(options), client_common_1.MAX_TRANSACTION_ATTRIBUTES);
100 const inputs = reader.readArray(() => Input_1.Input.deserializeWireBase(options));
101 const outputs = reader.readArray(() => Output_1.Output.deserializeWireBase(options), utils_2.utils.USHORT_MAX_NUMBER + 1);
102 const scripts = reader.readArray(() => Witness_1.Witness.deserializeWireBase(options));
103 return { attributes, inputs, outputs, scripts };
104 }
105 static deserializeWireBase(_options) {
106 throw new Error('Not Implemented');
107 }
108 static deserializeWire(options) {
109 return this.deserializeWireBase({
110 context: options.context,
111 reader: new utils_2.BinaryReader(options.buffer),
112 });
113 }
114 get size() {
115 return this.sizeInternal();
116 }
117 async serializeTransactionBaseJSON(context) {
118 const [networkFee, transactionData] = await Promise.all([
119 this.getNetworkFee(context.feeContext),
120 context.tryGetTransactionData(this),
121 ]);
122 return {
123 txid: client_common_1.common.uInt256ToString(this.hashHex),
124 size: this.size,
125 version: this.version,
126 attributes: this.attributes.map((attribute) => attribute.serializeJSON(context)),
127 vin: this.inputs.map((input) => input.serializeJSON(context)),
128 vout: this.outputs.map((output, index) => output.serializeJSON(context, index)),
129 scripts: this.scripts.map((script) => script.serializeJSON(context)),
130 sys_fee: client_common_1.JSONHelper.writeFixed8(this.getSystemFee(context.feeContext)),
131 net_fee: client_common_1.JSONHelper.writeFixed8(networkFee),
132 data: transactionData === undefined
133 ? undefined
134 : {
135 blockHash: client_common_1.common.uInt256ToString(transactionData.blockHash),
136 blockIndex: transactionData.startHeight,
137 transactionIndex: transactionData.index,
138 globalIndex: client_common_1.JSONHelper.writeUInt64(transactionData.globalIndex),
139 },
140 };
141 }
142 async serializeJSON(_context) {
143 throw new Error('Not Implemented');
144 }
145 async getNetworkFee(context) {
146 return this.networkFee(context);
147 }
148 getSystemFee({ fees }) {
149 const fee = fees[this.type];
150 return fee === undefined ? utils_2.utils.ZERO : fee;
151 }
152 async getReferences(options) {
153 return this.getReferencesInternal(options);
154 }
155 async getTransactionResults(options) {
156 return this.getTransactionResultsInternal(options);
157 }
158 async getScriptHashesForVerifying(options) {
159 return this.baseGetScriptHashesForVerifyingInternal(options);
160 }
161 async verify(options) {
162 if (this.size > constants_1.MAX_TRANSACTION_SIZE) {
163 throw new errors_1.VerifyError('Transaction too large.');
164 }
165 const { memPool = [] } = options;
166 if (common_1.hasDuplicateInputs(this.inputs)) {
167 throw new errors_1.VerifyError('Duplicate inputs');
168 }
169 if (memPool.some((tx) => !tx.equals(this) && common_1.hasIntersectingInputs(tx.inputs, this.inputs))) {
170 throw new errors_1.VerifyError('Input already exists in mempool');
171 }
172 if (this.attributes.filter((attribute) => attribute.usage === attribute_1.AttributeUsage.ECDH02 || attribute.usage === attribute_1.AttributeUsage.ECDH03).length > 1) {
173 throw new errors_1.VerifyError('Too many ECDH attributes.');
174 }
175 const [results] = await Promise.all([
176 this.verifyScripts(options),
177 this.verifyDoubleSpend(options),
178 this.verifyOutputs(options),
179 this.verifyTransactionResults(options),
180 ]);
181 return results;
182 }
183 async verifyDoubleSpend({ isSpent }) {
184 const isDoubleSpend = await Promise.all(this.inputs.map(isSpent));
185 if (isDoubleSpend.some((value) => value)) {
186 throw new errors_1.VerifyError('Transaction is a double spend');
187 }
188 }
189 async verifyOutputs({ getAsset, currentHeight }) {
190 const outputsGrouped = Object.entries(lodash_1.default.groupBy(this.outputs, (output) => client_common_1.common.uInt256ToHex(output.asset)));
191 const hasInvalidOutputs = await Promise.all(outputsGrouped.map(async ([assetHex, outputs]) => {
192 const asset = await getAsset({ hash: client_common_1.common.hexToUInt256(assetHex) });
193 if (asset.expiration <= currentHeight + 1 &&
194 asset.type !== AssetType_1.AssetType.GoverningToken &&
195 asset.type !== AssetType_1.AssetType.UtilityToken) {
196 return true;
197 }
198 return outputs.some((output) => !output.value.mod(utils_2.utils.TEN.pow(utils_2.utils.EIGHT.subn(asset.precision))).eq(utils_2.utils.ZERO));
199 }));
200 if (hasInvalidOutputs.some((value) => value)) {
201 throw new errors_1.VerifyError('Transaction has invalid output');
202 }
203 }
204 async verifyTransactionResults({ getOutput, utilityToken, governingToken, fees, registerValidatorFee, }) {
205 const results = await this.getTransactionResults({ getOutput });
206 const resultsDestroy = Object.entries(results).filter(([_key, value]) => value.gt(utils_2.utils.ZERO));
207 if (resultsDestroy.length > 1 ||
208 (resultsDestroy.length === 1 &&
209 !client_common_1.common.uInt256Equal(client_common_1.common.hexToUInt256(resultsDestroy[0][0]), utilityToken.hash))) {
210 throw new errors_1.VerifyError('Invalid destroyed output.');
211 }
212 const feeContext = {
213 getOutput,
214 governingToken,
215 utilityToken,
216 fees,
217 registerValidatorFee,
218 };
219 const systemFee = this.getSystemFee(feeContext);
220 if (systemFee.gt(utils_2.utils.ZERO) && (resultsDestroy.length === 0 || resultsDestroy[0][1].lt(systemFee))) {
221 throw new errors_1.VerifyError('Not enough output value for system fee.');
222 }
223 const resultsIssue = Object.entries(results).filter(([__, value]) => value.lt(utils_2.utils.ZERO));
224 switch (this.type) {
225 case TransactionType_1.TransactionType.Miner:
226 case TransactionType_1.TransactionType.Claim:
227 if (resultsIssue.some(([assetHex]) => !client_common_1.common.uInt256Equal(client_common_1.common.hexToUInt256(assetHex), utilityToken.hash))) {
228 throw new errors_1.VerifyError('Invalid miner/claim result');
229 }
230 break;
231 case TransactionType_1.TransactionType.Issue:
232 if (resultsIssue.some(([assetHex, __]) => client_common_1.common.uInt256Equal(client_common_1.common.hexToUInt256(assetHex), utilityToken.hash))) {
233 throw new errors_1.VerifyError('Invalid issue result');
234 }
235 break;
236 default:
237 if (resultsIssue.length > 0) {
238 throw new errors_1.VerifyError('Invalid results.');
239 }
240 }
241 }
242 async verifyScripts({ getAsset, getOutput, verifyScript, }) {
243 const hashesArr = await this.getSortedScriptHashesForVerifying({
244 getAsset,
245 getOutput,
246 });
247 if (hashesArr.length !== this.scripts.length) {
248 throw new errors_1.VerifyError(`Invalid witnesses. Found ${hashesArr.length} hashes and ${this.scripts.length} scripts.`);
249 }
250 const hashes = hashesArr.map((value) => client_common_1.common.hexToUInt160(value));
251 return Promise.all(lodash_1.default.zip(hashes, this.scripts).map(async ([hash, witness]) => verifyScript({
252 scriptContainer: {
253 type: ScriptContainer_1.ScriptContainerType.Transaction,
254 value: this,
255 },
256 hash: hash,
257 witness: witness,
258 })));
259 }
260 }
261 TransactionBaseClass.WitnessConstructor = Witness_1.Witness;
262 return TransactionBaseClass;
263}
264exports.TransactionBase = TransactionBase;
265
266//# sourceMappingURL=TransactionBase.js.map