package io.bytom.api;\r
\r
-import com.google.gson.annotations.SerializedName;\r
-import io.bytom.common.ParameterizedTypeImpl;\r
-import io.bytom.common.SuccessRespon;\r
import io.bytom.common.Utils;\r
-import io.bytom.exception.BytomException;\r
-import io.bytom.http.Client;\r
-import org.apache.log4j.Logger;\r
+import io.bytom.types.*;\r
+import org.bouncycastle.util.encoders.Hex;\r
\r
-import java.lang.reflect.Type;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.IOException;\r
import java.util.ArrayList;\r
import java.util.HashMap;\r
import java.util.List;\r
\r
public class Transaction {\r
\r
- @SerializedName("tx_id")\r
public String txID;\r
/**\r
* version\r
/**\r
* time_range\r
*/\r
- @SerializedName("time_range")\r
public Integer timeRange;\r
\r
/**\r
- * status\r
- */\r
- public Integer fee;\r
-\r
- /**\r
* List of specified inputs for a transaction.\r
*/\r
- public List<AnnotatedInput> inputs;\r
+ public List<BaseInput> inputs;\r
\r
/**\r
* List of specified outputs for a transaction.\r
*/\r
- public List<AnnotatedOutput> outputs;\r
-\r
- // public InputWitnessComponent inputWitnessComponent;\r
- private static Logger logger = Logger.getLogger(Transaction.class);\r
-\r
- public String toJson() {\r
- return Utils.serializer.toJson(this);\r
+ public List<Output> outputs;\r
+\r
+ public Transaction(Builder builder) {\r
+ this.inputs = builder.inputs;\r
+ this.outputs = builder.outputs;\r
+ this.version = builder.version;\r
+ this.size = builder.size;\r
+ this.timeRange = builder.timeRange;\r
+ mapTx();\r
+ sign();\r
}\r
\r
- public static Transaction fromJson(String json) {\r
- return Utils.serializer.fromJson(json, Transaction.class);\r
- }\r
-\r
- public static Transaction fromSuccessRespon(String json) {\r
- Type responType = new ParameterizedTypeImpl(SuccessRespon.class, new Class[]{Transaction.class});\r
- SuccessRespon<Transaction> result = Utils.serializer.fromJson(json, responType);\r
- return result.dataObject;\r
- }\r
+ public static class Builder {\r
\r
- public static Transaction decode(Client client, String txId) throws BytomException {\r
- Map<String, Object> req = new HashMap<String, Object>();\r
- req.put("raw_transaction", txId);\r
- Transaction Transaction =\r
- client.request("decode-raw-transaction", req, Transaction.class);\r
+ private String txID;\r
\r
- logger.info("decode-raw-transaction:");\r
- logger.info(Transaction.toJson());\r
+ private Integer version = 1;\r
\r
- return Transaction;\r
- }\r
+ private Integer size = 0;\r
\r
- public static class Builder {\r
- @SerializedName("tx_id")\r
- public String txID;\r
- public Integer version;\r
- public Integer size;\r
- @SerializedName("time_range")\r
- public Integer timeRange;\r
+ private Integer timeRange;\r
\r
- Transaction tx;\r
- List<AnnotatedInput> inputs;\r
- List<AnnotatedOutput> outputs;\r
+ private List<BaseInput> inputs;\r
+ private List<Output> outputs;\r
\r
public Builder() {\r
this.inputs = new ArrayList<>();\r
this.outputs = new ArrayList<>();\r
}\r
\r
- public Builder addInput(AnnotatedInput input) {\r
+ public Builder addInput(BaseInput input) {\r
this.inputs.add(input);\r
return this;\r
}\r
\r
- public Builder addOutput(AnnotatedOutput output) {\r
+ public Builder addOutput(Output output) {\r
this.outputs.add(output);\r
return this;\r
}\r
\r
-\r
- public Transaction build(Integer version, Integer timeRange, Integer size) {\r
- tx = new Transaction();\r
- tx.inputs = this.inputs;\r
- tx.outputs = this.outputs;\r
- tx.version = version;\r
- tx.timeRange = timeRange;\r
- tx.size = size;\r
- return tx;\r
- }\r
-\r
- public Transaction build(int timeRange) {\r
- tx = new Transaction();\r
- tx.inputs = this.inputs;\r
- tx.outputs = this.outputs;\r
- tx.version = 1;\r
- tx.size = 0;\r
- tx.timeRange = timeRange;\r
- return tx;\r
- }\r
- }\r
-\r
- public static class AnnotatedInput {\r
-\r
- @SerializedName("input_id")\r
- public String inputID;\r
- /**\r
- * address\r
- */\r
- public String address;\r
-\r
- /**\r
- * The number of units of the asset being issued or spent.\r
- */\r
- public long amount;\r
-\r
- // /**\r
-// * The definition of the asset being issued or spent (possibly null).\r
-// */\r
-// @SerializedName("asset_definition")\r
-// private Map<String, Object> assetDefinition;\r
- @SerializedName("asset_definition")\r
- public String assetDefinition;\r
-\r
- /**\r
- * The id of the asset being issued or spent.\r
- */\r
- @SerializedName("asset_id")\r
- public String assetId;\r
-\r
- /**\r
- * The control program which must be satisfied to transfer this output.\r
- */\r
- @SerializedName("control_program")\r
- public String controlProgram;\r
-\r
- /**\r
- * The id of the output consumed by this input. Null if the input is an\r
- * issuance.\r
- */\r
- @SerializedName("spent_output_id")\r
- public String spentOutputId;\r
-\r
- /**\r
- * The type of the input.<br>\r
- * Possible values are "issue" and "spend".\r
- */\r
- public int type;\r
-\r
- public String sourceId;\r
-\r
- public long sourcePosition;\r
-\r
- public String nonce;\r
-\r
- private int controlProgramIndex;\r
- private boolean change;\r
-\r
- public int keyIndex;\r
- public String chainPath;\r
-\r
- @SerializedName("witness_component")\r
- public InputWitnessComponent witnessComponent;\r
-\r
- @Override\r
- public String toString() {\r
- return Utils.serializer.toJson(this);\r
- }\r
-\r
- public int getControlProgramIndex() {\r
- return controlProgramIndex;\r
- }\r
-\r
- public AnnotatedInput setControlProgramIndex(int controlProgramIndex) {\r
- this.controlProgramIndex = controlProgramIndex;\r
+ public Builder setTimeRange(int timeRange) {\r
+ this.timeRange = timeRange;\r
return this;\r
}\r
\r
- public boolean isChange() {\r
- return change;\r
- }\r
-\r
- public AnnotatedInput setChange(boolean change) {\r
- this.change = change;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setAmount(long amount) {\r
- this.amount = amount;\r
- return this;\r
+ public Transaction build() {\r
+ return new Transaction(this);\r
}\r
-\r
- public AnnotatedInput setAssetId(String assetId) {\r
- this.assetId = assetId;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setControlProgram(String controlProgram) {\r
- this.controlProgram = controlProgram;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setType(int type) {\r
- this.type = type;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setSourceId(String sourceId) {\r
- this.sourceId = sourceId;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setSourcePosition(long sourcePosition) {\r
- this.sourcePosition = sourcePosition;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setNonce(String nonce) {\r
- this.nonce = nonce;\r
- return this;\r
- }\r
-\r
- public AnnotatedInput setAssetDefinition(String assetDefinition) {\r
- this.assetDefinition = assetDefinition;\r
- return this;\r
- }\r
-\r
- public int getKeyIndex() {\r
- return keyIndex;\r
- }\r
-\r
- public AnnotatedInput setKeyIndex(int keyIndex) {\r
- this.keyIndex = keyIndex;\r
- return this;\r
- }\r
-\r
}\r
\r
- public static class AnnotatedOutput {\r
-\r
- /**\r
- * address\r
- */\r
- public String address;\r
-\r
- /**\r
- * The number of units of the asset being controlled.\r
- */\r
- public long amount;\r
-\r
- /**\r
- * The definition of the asset being controlled (possibly null).\r
- */\r
- @SerializedName("asset_definition")\r
- public Map<String, Object> assetDefinition;\r
-\r
- /**\r
- * The id of the asset being controlled.\r
- */\r
- @SerializedName("asset_id")\r
- public String assetId;\r
-\r
- /**\r
- * The control program which must be satisfied to transfer this output.\r
- */\r
- @SerializedName("control_program")\r
- public String controlProgram;\r
-\r
- /**\r
- * The id of the output.\r
- */\r
- @SerializedName("id")\r
- public String id;\r
-\r
- /**\r
- * The output's position in a transaction's list of outputs.\r
- */\r
- public Integer position;\r
-\r
- /**\r
- * The type the output.<br>\r
- * Possible values are "control" and "retire".\r
- */\r
- public String type;\r
-\r
- public AnnotatedOutput setAddress(String address) {\r
- this.address = address;\r
- return this;\r
- }\r
-\r
- public AnnotatedOutput setAmount(long amount) {\r
- this.amount = amount;\r
- return this;\r
- }\r
-\r
- public AnnotatedOutput setAssetId(String assetId) {\r
- this.assetId = assetId;\r
- return this;\r
+ private void sign() {\r
+ for (BaseInput input : inputs) {\r
+ try {\r
+ input.buildWitness(txID);\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ throw new RuntimeException(e);\r
+ }\r
}\r
+ }\r
\r
- public AnnotatedOutput setControlProgram(String controlProgram) {\r
- this.controlProgram = controlProgram;\r
- return this;\r
- }\r
+ public String rawTransaction() {\r
+ String rawTransaction;\r
+ //开始序列化\r
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();\r
+ try {\r
+ stream.write(7);\r
+ // version\r
+ if (null != version)\r
+ Utils.writeVarint(version, stream);\r
+ if (null != timeRange)\r
+ Utils.writeVarint(timeRange, stream);\r
+ //inputs\r
+ if (null != inputs && inputs.size() > 0) {\r
+ Utils.writeVarint(inputs.size(), stream);\r
+ for (BaseInput input : inputs) {\r
+ System.out.println(Hex.toHexString(input.serializeInput()));\r
+ stream.write(input.serializeInput());\r
+ }\r
+ }\r
+\r
+ //outputs\r
+ if (null != outputs && outputs.size() > 0) {\r
+ Utils.writeVarint(outputs.size(), stream);\r
+ for (Output output : outputs) {\r
+ stream.write(output.serializeOutput());\r
+ }\r
+ }\r
+ byte[] data = stream.toByteArray();\r
+ rawTransaction = Hex.toHexString(data);\r
+ } catch (IOException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ return rawTransaction;\r
+ }\r
\r
- public AnnotatedOutput setPosition(Integer position) {\r
- this.position = position;\r
- return this;\r
+ private void mapTx() {\r
+ Map<Hash, Entry> entryMap = new HashMap<>();\r
+ ValueSource[] muxSources = new ValueSource[inputs.size()];\r
+ List<InputEntry> inputEntries = new ArrayList<>();\r
+\r
+ try {\r
+ for (int i = 0; i < inputs.size(); i++) {\r
+ BaseInput input = inputs.get(i);\r
+ InputEntry inputEntry = input.convertInputEntry(entryMap, i);\r
+ Hash spendID = addEntry(entryMap, inputEntry);\r
+ input.setInputID(spendID.toString());\r
+\r
+ muxSources[i] = new ValueSource(spendID, input.getAssetAmount(), 0);\r
+ inputEntries.add(inputEntry);\r
+ }\r
+\r
+ Mux mux = new Mux(muxSources, new Program(1, new byte[]{0x51}));\r
+ Hash muxID = addEntry(entryMap, mux);\r
+ for (InputEntry inputEntry : inputEntries) {\r
+ inputEntry.setDestination(muxID, inputEntry.ordinal, entryMap);\r
+ }\r
+\r
+ List<Hash> resultIDList = new ArrayList<>();\r
+ for (int i = 0; i < outputs.size(); i++) {\r
+ Output output = outputs.get(i);\r
+\r
+ AssetAmount amount = new AssetAmount(new AssetID(output.assetId), output.amount);\r
+ ValueSource src = new ValueSource(muxID, amount, i);\r
+\r
+ Hash resultID;\r
+ if (output.controlProgram.startsWith("6a")) {\r
+ Retirement retirement = new Retirement(src, i);\r
+ resultID = addEntry(entryMap, retirement);\r
+ } else {\r
+ Program prog = new Program(1, Hex.decode(output.controlProgram));\r
+ OutputEntry oup = new OutputEntry(src, prog, i);\r
+ resultID = addEntry(entryMap, oup);\r
+ }\r
+\r
+ resultIDList.add(resultID);\r
+ output.id = resultID.toString();\r
+\r
+ ValueDestination destination = new ValueDestination(resultID, src.value, 0);\r
+ mux.witnessDestinations.add(destination);\r
+ }\r
+\r
+ TxHeader txHeader = new TxHeader(version, size, timeRange, resultIDList.toArray(new Hash[]{}));\r
+ Hash txID = addEntry(entryMap, txHeader);\r
+ this.txID = txID.toString();\r
+\r
+ } catch (Exception e) {\r
+ throw new RuntimeException(e);\r
}\r
}\r
\r
- /**\r
- * A single witness component, holding information that will become the input\r
- * witness.\r
- */\r
- public static class InputWitnessComponent {\r
-\r
- /**\r
- * The list of signatures made with the specified keys (null unless type is\r
- * "signature").\r
- */\r
- public String[] signatures;\r
+ private Hash addEntry(Map<Hash, Entry> entryMap, Entry entry) {\r
+ Hash id = entry.entryID();\r
+ entryMap.put(id, entry);\r
+ return id;\r
}\r
}\r