-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathTransaction.java
More file actions
347 lines (298 loc) · 13.5 KB
/
Transaction.java
File metadata and controls
347 lines (298 loc) · 13.5 KB
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
package com.gxchain.client.graphenej.objects;
import com.google.common.primitives.Bytes;
import com.google.gson.*;
import com.gxchain.client.graphenej.Util;
import com.gxchain.client.graphenej.enums.OperationType;
import com.gxchain.client.graphenej.interfaces.ByteSerializable;
import com.gxchain.client.graphenej.interfaces.JsonSerializable;
import com.gxchain.client.graphenej.operations.AccountCreateOperation;
import com.gxchain.client.graphenej.operations.BaseOperation;
import com.gxchain.client.graphenej.operations.LimitOrderCreateOperation;
import com.gxchain.client.graphenej.operations.TransferOperation;
import com.gxchain.client.util.TxSerializerUtil;
import com.gxchain.common.signature.SignatureUtil;
import com.gxchain.common.signature.utils.StringUtils;
import com.gxchain.common.signature.utils.Wif;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Type;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
/**
* Class used to represent a generic Graphene transaction.
*/
@Slf4j
public class Transaction implements ByteSerializable, JsonSerializable {
private static final Logger LOGGER = LoggerFactory.getLogger(Transaction.class);
private static final long serialVersionUID = -2169174657812805629L;
private final Logger logger = LoggerFactory.getLogger(Transaction.class);
/* Default expiration time */
public static final int DEFAULT_EXPIRATION_TIME = 120;
/* Constant field names used for serialization/deserialization purposes */
public static final String KEY_CHAIN_ID = "chain_id";
public static final String KEY_EXPIRATION = "expiration";
public static final String KEY_SIGNATURES = "signatures";
public static final String KEY_OPERATIONS = "operations";
public static final String KEY_EXTENSIONS = "extensions";
public static final String KEY_REF_BLOCK_NUM = "ref_block_num";
public static final String KEY_REF_BLOCK_PREFIX = "ref_block_prefix";
private ECKey privateKey;
private BlockData blockData;
private List<BaseOperation> operations;
private Extensions extensions;
private String chainId;
@Getter
private String signature;
private byte[] tx_buffer;
/**
* BlockTransaction constructor.
*
* @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction.
* @param blockData : Block data containing important information used to sign a transaction.
* @param operationList : List of operations to include in the transaction.
*/
public Transaction(ECKey privateKey, BlockData blockData, List<BaseOperation> operationList) {
this.privateKey = privateKey;
this.blockData = blockData;
this.operations = operationList;
this.extensions = new Extensions();
}
/**
* Constructor used to build a BlockTransaction object without a private key. This kind of object
* is used to represent a transaction data that we don't intend to serialize and sign.
*
* @param chainId
* @param blockData: Block data instance, containing information about the location of this transaction in the blockchain.
* @param operationList: The list of operations included in this transaction.
* @param signature
*/
public Transaction(String chainId, BlockData blockData, List<BaseOperation> operationList, String signature) {
this.chainId = chainId;
this.blockData = blockData;
this.operations = operationList;
this.signature = signature;
}
/**
* BlockTransaction constructor.
*
* @param wif: The user's private key in the base58 format.
* @param operation_list: List of operations to include in the transaction.
*/
public Transaction(String wif, List<BaseOperation> operation_list) {
this(DumpedPrivateKey.fromBase58(null, wif).getKey(), null, operation_list);
}
/**
* BlockTransaction constructor.
*
* @param wif: The user's private key in the base58 format.
* @param block_data: Block data containing important information used to sign a transaction.
* @param operation_list: List of operations to include in the transaction.
*/
public Transaction(String wif, BlockData block_data, List<BaseOperation> operation_list) {
this(DumpedPrivateKey.fromBase58(null, wif).getKey(), block_data, operation_list);
}
/**
* Constructor used to build a BlockTransaction object without a private key. This kind of object
* is used to represent a transaction data that we don't intend to serialize and sign.
*
* @param blockData: Block data instance, containing information about the location of this transaction in the blockchain.
* @param operationList: The list of operations included in this transaction.
*/
public Transaction(BlockData blockData, List<BaseOperation> operationList) {
this.blockData = blockData;
this.operations = operationList;
}
/**
* Updates the block data
*
* @param blockData: New block data
*/
public void setBlockData(BlockData blockData) {
this.blockData = blockData;
}
/**
* Updates the fees for all operations in this transaction.
*
* @param fees: New fees to apply
*/
public void setFees(List<AssetAmount> fees) {
for (int i = 0; i < operations.size(); i++) {
operations.get(i).setFee(fees.get(i));
}
}
public ECKey getPrivateKey() {
return this.privateKey;
}
public List<BaseOperation> getOperations() {
return this.operations;
}
/**
* This method is used to query whether the instance has a private key.
*
* @return
*/
public boolean hasPrivateKey() {
return this.privateKey != null;
}
/**
* Obtains a signature of this transaction. Please note that due to the current reliance on
* bitcoinj to generate the signatures, and due to the fact that it uses deterministic
* ecdsa signatures, we are slightly modifying the expiration time of the transaction while
* we look for a signature that will be accepted by the graphene network.
* <p>
* This should then be called before any other serialization method.
*
* @return: A valid signature of the current transaction.
*/
public byte[] getGrapheneSignature() {
return SignatureUtil.signature(this.toBytes(), new Wif(privateKey).toString());
}
/**
* Method that creates a serialized byte array with compact information about this transaction
* that is needed for the creation of a signature.
*
* @return: byte array with serialized information about this transaction.
*/
public byte[] toBytes() {
// Creating a List of Bytes and adding the first bytes from the chain apiId
List<Byte> byteArray = new ArrayList<Byte>();
byteArray.addAll(Bytes.asList(Util.hexToBytes(getChainId())));
// Adding the block data
byteArray.addAll(Bytes.asList(this.blockData.toBytes()));
// Adding the number of operations
byteArray.add((byte) this.operations.size());
// Adding all the operations
for (BaseOperation operation : operations) {
byteArray.add(operation.getId());
byteArray.addAll(Bytes.asList(operation.toBytes()));
}
// Adding extensions byte
byteArray.addAll(Bytes.asList(this.extensions.toBytes()));
return Bytes.toArray(byteArray);
}
public String calculateTxid() {
return Util.bytesToHex(Sha256Hash.hash(TxSerializerUtil.serializeTransaction(this.toJsonObjectNoSign()))).substring(0, 40);
}
@Override
public String toJsonString() {
return toJsonObject().toString();
}
@Override
public JsonObject toJsonObject() {
// Getting the signature before anything else,
// since this might change the transaction expiration data slightly
if (StringUtils.isEmpty(this.signature)) {
long l1 = System.currentTimeMillis();
byte[] signature = getGrapheneSignature();
logger.info("signature consuming " + (System.currentTimeMillis() - l1) + " ms");
this.signature = Util.bytesToHex(signature);
}
JsonObject obj = toJsonObjectNoSign();
// Adding signatures
JsonArray signatureArray = new JsonArray();
signatureArray.add(this.signature);
obj.add(KEY_SIGNATURES, signatureArray);
return obj;
}
public JsonObject toJsonObjectNoSign() {
JsonObject obj = new JsonObject();
// Adding block data
obj.addProperty(KEY_REF_BLOCK_NUM, blockData.getRefBlockNum());
obj.addProperty(KEY_REF_BLOCK_PREFIX, blockData.getRefBlockPrefix());
// Formatting expiration time
Date expirationTime = new Date(blockData.getExpiration());
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
// Adding expiration
obj.addProperty(KEY_EXPIRATION, dateFormat.format(expirationTime));
JsonArray operationsArray = new JsonArray();
for (BaseOperation operation : operations) {
operationsArray.add(operation.toJsonObject());
}
// Adding operations
obj.add(KEY_OPERATIONS, operationsArray);
// Adding signatures
JsonArray signatureArray = new JsonArray();
obj.add(KEY_SIGNATURES, signatureArray);
// Adding extensions
obj.add(KEY_EXTENSIONS, new JsonArray());
return obj;
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
/**
* Class used to encapsulate the procedure to be followed when converting a transaction from a
* java object to its JSON string format representation.
*/
public static class TransactionSerializer implements JsonSerializer<Transaction> {
@Override
public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) {
return transaction.toJsonObject();
}
}
/**
* Static inner class used to encapsulate the procedure to be followed when converting a transaction from its
* JSON string format representation into a java object instance.
*/
public static class TransactionDeserializer implements JsonDeserializer<Transaction> {
@Override
public Transaction deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
// Parsing block data information
int refBlockNum = jsonObject.get(KEY_REF_BLOCK_NUM).getAsInt();
long refBlockPrefix = jsonObject.get(KEY_REF_BLOCK_PREFIX).getAsLong();
String expiration = jsonObject.get(KEY_EXPIRATION).getAsString();
SimpleDateFormat dateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
Date expirationDate = dateFormat.parse(expiration, new ParsePosition(0));
BlockData blockData = new BlockData(refBlockNum, refBlockPrefix, expirationDate.getTime());
// Parsing chainId
String chainId = null == jsonObject.get(KEY_CHAIN_ID) ? null : jsonObject.get(KEY_CHAIN_ID).getAsString();
// Parsing signatures
String signature = null;
try {
signature = null == jsonObject.get(KEY_SIGNATURES) ? null : jsonObject.get(KEY_SIGNATURES).getAsString();
} catch (Exception e) {
}
// Parsing operation list
BaseOperation operation = null;
ArrayList<BaseOperation> operationList = new ArrayList<>();
try {
for (JsonElement jsonOperation : jsonObject.get(KEY_OPERATIONS).getAsJsonArray()) {
int operationId = jsonOperation.getAsJsonArray().get(0).getAsInt();
if (operationId == OperationType.TRANSFER_OPERATION.getCode()) {
operation = context.deserialize(jsonOperation, TransferOperation.class);
} else if (operationId == OperationType.LIMIT_ORDER_CREATE_OPERATION.getCode()) {
operation = context.deserialize(jsonOperation, LimitOrderCreateOperation.class);
} else if (operationId == OperationType.ACCOUNT_CREATE_OPERATION.getCode()) {
operation = context.deserialize(jsonOperation, AccountCreateOperation.class);
}
if (operation != null) {
operationList.add(operation);
}
operation = null;
}
return new Transaction(chainId, blockData, operationList, signature);
} catch (Exception e) {
LOGGER.info("Exception. Msg: " + e.getMessage());
for (StackTraceElement el : e.getStackTrace()) {
LOGGER.info(el.getFileName() + "#" + el.getMethodName() + ":" + el.getLineNumber());
}
}
return new Transaction(chainId, blockData, operationList, signature);
}
}
}