OSDN Git Service

54a59970922953a61ff268599f854f3d427d0d76
[bytom/bytom-java-sdk.git] / tx-signer / src / main / java / io / bytom / offline / api / Transaction.java
1 package io.bytom.offline.api;\r
2 \r
3 import io.bytom.offline.common.Utils;\r
4 import io.bytom.offline.exception.MapTransactionException;\r
5 import io.bytom.offline.exception.SerializeTransactionException;\r
6 import io.bytom.offline.exception.SignTransactionException;\r
7 import io.bytom.offline.types.*;\r
8 import org.bouncycastle.util.encoders.Hex;\r
9 import java.io.ByteArrayOutputStream;\r
10 import java.io.IOException;\r
11 import java.util.ArrayList;\r
12 import java.util.HashMap;\r
13 import java.util.List;\r
14 import java.util.Map;\r
15 \r
16 /**\r
17  * Created by liqiang on 2018/10/24.\r
18  */\r
19 \r
20 public class Transaction {\r
21 \r
22     private String txID;\r
23     /**\r
24      * version\r
25      */\r
26     private Integer version;\r
27     /**\r
28      * size\r
29      */\r
30     private Integer size;\r
31     /**\r
32      * time_range\r
33      */\r
34     private Integer timeRange;\r
35 \r
36     /**\r
37      * List of specified inputs for a transaction.\r
38      */\r
39     private List<BaseInput> inputs;\r
40 \r
41     /**\r
42      * List of specified outputs for a transaction.\r
43      */\r
44     private List<Output> outputs;\r
45 \r
46     public Transaction(Builder builder) {\r
47         this.inputs = builder.inputs;\r
48         this.outputs = builder.outputs;\r
49         this.version = builder.version;\r
50         this.size = builder.size;\r
51         this.timeRange = builder.timeRange;\r
52 \r
53         this.validate();\r
54         this.mapTx();\r
55         this.sign();\r
56     }\r
57 \r
58     public static class Builder {\r
59 \r
60         private Integer version = 1;\r
61 \r
62         private Integer size = 0;\r
63 \r
64         private Integer timeRange;\r
65 \r
66         private List<BaseInput> inputs;\r
67         private List<Output> outputs;\r
68 \r
69         public Builder() {\r
70             this.inputs = new ArrayList<>();\r
71             this.outputs = new ArrayList<>();\r
72         }\r
73 \r
74         public Builder addInput(BaseInput input) {\r
75             this.inputs.add(input);\r
76             return this;\r
77         }\r
78 \r
79         public Builder addOutput(Output output) {\r
80             this.outputs.add(output);\r
81             return this;\r
82         }\r
83 \r
84         public Builder setTimeRange(int timeRange) {\r
85             this.timeRange = timeRange;\r
86             return this;\r
87         }\r
88 \r
89         public Transaction build() {\r
90             return new Transaction(this);\r
91         }\r
92     }\r
93 \r
94     private void sign() {\r
95         for (BaseInput input : inputs) {\r
96             try {\r
97                 input.buildWitness(txID);\r
98             } catch (Exception e) {\r
99                 e.printStackTrace();\r
100                 throw new SignTransactionException(e);\r
101             }\r
102         }\r
103     }\r
104 \r
105     public String rawTransaction() {\r
106         ByteArrayOutputStream stream = new ByteArrayOutputStream();\r
107         try {\r
108             stream.write(7);\r
109 \r
110             Utils.writeVarint(version, stream);\r
111 \r
112             Utils.writeVarint(timeRange, stream);\r
113 \r
114             Utils.writeVarint(inputs.size(), stream);\r
115             for (BaseInput input : inputs) {\r
116                 stream.write(input.serializeInput());\r
117             }\r
118 \r
119             Utils.writeVarint(outputs.size(), stream);\r
120             for (Output output : outputs) {\r
121                 stream.write(output.serializeOutput());\r
122             }\r
123         } catch (IOException e) {\r
124             e.printStackTrace();\r
125             throw new SerializeTransactionException(e);\r
126         }\r
127         return Hex.toHexString(stream.toByteArray());\r
128     }\r
129 \r
130     private void validate() {\r
131         if (version == null) {\r
132             throw new IllegalArgumentException("the version of transaction must be specified.");\r
133         }\r
134         if (timeRange == null) {\r
135             throw new IllegalArgumentException("the time range of transaction must be specified.");\r
136         }\r
137         if (size == null) {\r
138             throw new IllegalArgumentException("the size range of transaction must be specified.");\r
139         }\r
140 \r
141         for (BaseInput input : inputs) {\r
142             input.validate();\r
143         }\r
144     }\r
145 \r
146     private void mapTx() {\r
147         Map<Hash, Entry> entryMap = new HashMap<>();\r
148         ValueSource[] muxSources = new ValueSource[inputs.size()];\r
149         List<InputEntry> inputEntries = new ArrayList<>();\r
150 \r
151         try {\r
152             for (int i = 0; i < inputs.size(); i++) {\r
153                 BaseInput input = inputs.get(i);\r
154                 InputEntry inputEntry =  input.toInputEntry(entryMap, i);\r
155                 Hash spendID = addEntry(entryMap, inputEntry);\r
156                 input.setInputID(spendID.toString());\r
157 \r
158                 muxSources[i] = new ValueSource(spendID, input.getAssetAmount(), 0);\r
159                 inputEntries.add(inputEntry);\r
160             }\r
161 \r
162             Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51}));\r
163             Hash muxID = addEntry(entryMap, mux);\r
164             for (InputEntry inputEntry : inputEntries) {\r
165                 inputEntry.setDestination(muxID, inputEntry.getOrdinal(), entryMap);\r
166             }\r
167 \r
168             List<Hash> resultIDList = new ArrayList<>();\r
169             for (int i = 0; i < outputs.size(); i++) {\r
170                 Output output = outputs.get(i);\r
171 \r
172                 AssetAmount amount = new AssetAmount(new AssetID(output.getAssetId()), output.getAmount());\r
173                 ValueSource src = new ValueSource(muxID, amount, i);\r
174 \r
175                 Hash resultID;\r
176                 if (output.getControlProgram().startsWith("6a")) {\r
177                     Retirement retirement = new Retirement(src, i);\r
178                     resultID = addEntry(entryMap, retirement);\r
179                 } else {\r
180                     Program program = new Program(1, Hex.decode(output.getControlProgram()));\r
181                     OutputEntry oup = new OutputEntry(src, program, i);\r
182                     resultID = addEntry(entryMap, oup);\r
183                 }\r
184 \r
185                 resultIDList.add(resultID);\r
186                 output.setId(resultID.toString());\r
187 \r
188                 ValueDestination destination = new ValueDestination(resultID, src.getValue(), 0);\r
189                 mux.getWitnessDestinations().add(destination);\r
190             }\r
191 \r
192             TxHeader txHeader = new TxHeader(version, size, timeRange, resultIDList.toArray(new Hash[]{}));\r
193             Hash txID = addEntry(entryMap, txHeader);\r
194             this.txID = txID.toString();\r
195 \r
196         } catch (Exception e) {\r
197             throw new MapTransactionException(e);\r
198         }\r
199     }\r
200 \r
201     private Hash addEntry(Map<Hash, Entry> entryMap, Entry entry) {\r
202         Hash id = entry.entryID();\r
203         entryMap.put(id, entry);\r
204         return id;\r
205     }\r
206 \r
207     public String getTxID() {\r
208         return txID;\r
209     }\r
210 \r
211     public Integer getVersion() {\r
212         return version;\r
213     }\r
214 \r
215     public Integer getSize() {\r
216         return size;\r
217     }\r
218 \r
219     public Integer getTimeRange() {\r
220         return timeRange;\r
221     }\r
222 \r
223     public List<BaseInput> getInputs() {\r
224         return inputs;\r
225     }\r
226 \r
227     public List<Output> getOutputs() {\r
228         return outputs;\r
229     }\r
230 }\r