1 package io.bytom.api;
\r
3 import io.bytom.common.Utils;
\r
4 import io.bytom.exception.MapTransactionException;
\r
5 import io.bytom.exception.SerializeTransactionException;
\r
6 import io.bytom.exception.SignTransactionException;
\r
7 import io.bytom.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
17 * Created by liqiang on 2018/10/24.
\r
20 public class Transaction {
\r
22 private String txID;
\r
26 private Integer version;
\r
30 private Integer size;
\r
34 private Integer timeRange;
\r
37 * List of specified inputs for a transaction.
\r
39 private List<BaseInput> inputs;
\r
42 * List of specified outputs for a transaction.
\r
44 private List<Output> outputs;
\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
54 this.mapTransaction();
\r
58 public static class Builder {
\r
60 private Integer version = 1;
\r
62 private Integer size = 0;
\r
64 private Integer timeRange;
\r
66 private List<BaseInput> inputs;
\r
67 private List<Output> outputs;
\r
70 this.inputs = new ArrayList<>();
\r
71 this.outputs = new ArrayList<>();
\r
74 public Builder addInput(BaseInput input) {
\r
75 this.inputs.add(input);
\r
79 public Builder addOutput(Output output) {
\r
80 this.outputs.add(output);
\r
84 public Builder setTimeRange(int timeRange) {
\r
85 this.timeRange = timeRange;
\r
89 public Transaction build() {
\r
90 return new Transaction(this);
\r
94 private void sign() {
\r
95 for (BaseInput input : inputs) {
\r
97 input.buildWitness(txID);
\r
98 } catch (Exception e) {
\r
99 e.printStackTrace();
\r
100 throw new SignTransactionException(e);
\r
105 public String rawTransaction() {
\r
106 ByteArrayOutputStream stream = new ByteArrayOutputStream();
\r
110 Utils.writeVarint(version, stream);
\r
112 Utils.writeVarint(timeRange, stream);
\r
114 Utils.writeVarint(inputs.size(), stream);
\r
115 for (BaseInput input : inputs) {
\r
116 stream.write(input.serializeInput());
\r
119 Utils.writeVarint(outputs.size(), stream);
\r
120 for (Output output : outputs) {
\r
121 stream.write(output.serializeOutput());
\r
123 } catch (IOException e) {
\r
124 e.printStackTrace();
\r
125 throw new SerializeTransactionException(e);
\r
127 return Hex.toHexString(stream.toByteArray());
\r
130 private void validate() {
\r
131 if (version == null) {
\r
132 throw new IllegalArgumentException("the version of transaction must be specified.");
\r
134 if (timeRange == null) {
\r
135 throw new IllegalArgumentException("the time range of transaction must be specified.");
\r
137 if (size == null) {
\r
138 throw new IllegalArgumentException("the size range of transaction must be specified.");
\r
141 inputs.forEach(BaseInput::validate);
\r
144 private void mapTransaction() {
\r
145 Map<Hash, Entry> entryMap = new HashMap<>();
\r
146 ValueSource[] muxSources = new ValueSource[inputs.size()];
\r
147 List<InputEntry> inputEntries = new ArrayList<>();
\r
150 for (int i = 0; i < inputs.size(); i++) {
\r
151 BaseInput input = inputs.get(i);
\r
152 InputEntry inputEntry = input.convertInputEntry(entryMap, i);
\r
153 Hash spendID = addEntry(entryMap, inputEntry);
\r
154 input.setInputID(spendID.toString());
\r
156 muxSources[i] = new ValueSource(spendID, input.getAssetAmount(), 0);
\r
157 inputEntries.add(inputEntry);
\r
160 Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51}));
\r
161 Hash muxID = addEntry(entryMap, mux);
\r
162 for (InputEntry inputEntry : inputEntries) {
\r
163 inputEntry.setDestination(muxID, inputEntry.ordinal, entryMap);
\r
166 List<Hash> resultIDList = new ArrayList<>();
\r
167 for (int i = 0; i < outputs.size(); i++) {
\r
168 Output output = outputs.get(i);
\r
170 AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount);
\r
171 ValueSource src = new ValueSource(muxID, amount, i);
\r
174 if (output.controlProgram.startsWith("6a")) {
\r
175 Retirement retirement = new Retirement(src, i);
\r
176 resultID = addEntry(entryMap, retirement);
\r
178 Program prog = new Program(1, Hex.decode(output.controlProgram));
\r
179 OutputEntry oup = new OutputEntry(src, prog, i);
\r
180 resultID = addEntry(entryMap, oup);
\r
183 resultIDList.add(resultID);
\r
184 output.id = resultID.toString();
\r
186 ValueDestination destination = new ValueDestination(resultID, src.value, 0);
\r
187 mux.witnessDestinations.add(destination);
\r
190 TxHeader txHeader = new TxHeader(version, size, timeRange, resultIDList.toArray(new Hash[]{}));
\r
191 Hash txID = addEntry(entryMap, txHeader);
\r
192 this.txID = txID.toString();
\r
194 } catch (Exception e) {
\r
195 throw new MapTransactionException(e);
\r
199 private Hash addEntry(Map<Hash, Entry> entryMap, Entry entry) {
\r
200 Hash id = entry.entryID();
\r
201 entryMap.put(id, entry);
\r
205 public String getTxID() {
\r
209 public Integer getVersion() {
\r
213 public Integer getSize() {
\r
217 public Integer getTimeRange() {
\r
221 public List<BaseInput> getInputs() {
\r
225 public List<Output> getOutputs() {
\r