From f950f9b6f97ee41fb2b1ae9ad7b812e983da7bea Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2019 21:18:56 +0000 Subject: [PATCH 01/11] chore(deps): bump com.github.johnrengelman.shadow from 5.1.0 to 5.2.0 Bumps com.github.johnrengelman.shadow from 5.1.0 to 5.2.0. Signed-off-by: dependabot-preview[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 459d1a459..72d2c23ae 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { } plugins { - id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'com.github.johnrengelman.shadow' version '5.2.0' id 'java' id 'com.github.sherter.google-java-format' version '0.8' id 'com.jfrog.bintray' version '1.8.4' From 807e7ac62f8cb1ea9620e85f571ed459f0bc10b8 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 10:09:34 +0800 Subject: [PATCH 02/11] docs: update readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 02171836b..d33f4ea93 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Java SDK for Nervos [CKB](https://github.com/nervosnetwork/ckb). +The ckb-sdk-java is still under development and **NOT** production ready. You should get familiar with CKB transaction structure and RPC before using it. + ### Prerequisites * Java 8 From 714ad0d4614472cdfa06e536623672ab855bff0f Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 14:58:58 +0800 Subject: [PATCH 03/11] chore: refactor examples with ckbToShannon --- .../ckb/MultiKeySingleSigTxExample.java | 13 +- .../ckb/MultiSignTransactionExample.java | 4 +- .../ckb/SendToMultiSigAddressTxExample.java | 6 +- .../ckb/SingleKeySingleSigTxExample.java | 7 +- .../ckb/SingleSigWithIndexerTxExample.java | 7 + .../transaction/CellCollectorWithIndexer.java | 172 ++++++++++++++++++ 6 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java create mode 100644 example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java diff --git a/example/src/main/java/org/nervos/ckb/MultiKeySingleSigTxExample.java b/example/src/main/java/org/nervos/ckb/MultiKeySingleSigTxExample.java index 67eeef6e6..81d3bee50 100644 --- a/example/src/main/java/org/nervos/ckb/MultiKeySingleSigTxExample.java +++ b/example/src/main/java/org/nervos/ckb/MultiKeySingleSigTxExample.java @@ -12,6 +12,7 @@ import org.nervos.ckb.type.Witness; import org.nervos.ckb.type.cell.CellOutput; import org.nervos.ckb.type.transaction.Transaction; +import org.nervos.ckb.utils.Utils; /** Copyright © 2019 Nervos Foundation. All rights reserved. */ public class MultiKeySingleSigTxExample { @@ -49,9 +50,9 @@ public static void main(String[] args) throws Exception { String minerAddress = "ckt1qyqrdsefa43s6m882pcj53m4gdnj4k440axqswmu83"; List receivers1 = Arrays.asList( - new Receiver(Addresses.get(0), new BigInteger("800").multiply(UnitCKB)), - new Receiver(Addresses.get(1), new BigInteger("900").multiply(UnitCKB)), - new Receiver(Addresses.get(2), new BigInteger("1000").multiply(UnitCKB))); + new Receiver(Addresses.get(0), Utils.ckbToShannon(800)), + new Receiver(Addresses.get(1), Utils.ckbToShannon(900)), + new Receiver(Addresses.get(2), Utils.ckbToShannon(1000))); System.out.println( "Before transfer, miner's balance: " @@ -77,9 +78,9 @@ public static void main(String[] args) throws Exception { List receivers2 = Arrays.asList( - new Receiver(Addresses.get(3), new BigInteger("400").multiply(UnitCKB)), - new Receiver(Addresses.get(4), new BigInteger("500").multiply(UnitCKB)), - new Receiver(Addresses.get(5), new BigInteger("600").multiply(UnitCKB))); + new Receiver(Addresses.get(3), Utils.ckbToShannon(400)), + new Receiver(Addresses.get(4), Utils.ckbToShannon(500)), + new Receiver(Addresses.get(5), Utils.ckbToShannon(600))); String changeAddress = "ckt1qyqfnym6semhw2vzm33fjvk3ngxuf5433l9qz3af8a"; diff --git a/example/src/main/java/org/nervos/ckb/MultiSignTransactionExample.java b/example/src/main/java/org/nervos/ckb/MultiSignTransactionExample.java index 982a7ff5f..5aa1242b7 100644 --- a/example/src/main/java/org/nervos/ckb/MultiSignTransactionExample.java +++ b/example/src/main/java/org/nervos/ckb/MultiSignTransactionExample.java @@ -19,6 +19,7 @@ import org.nervos.ckb.type.cell.CellOutput; import org.nervos.ckb.type.transaction.Transaction; import org.nervos.ckb.utils.Numeric; +import org.nervos.ckb.utils.Utils; import org.nervos.ckb.utils.address.AddressGenerator; /** Copyright © 2019 Nervos Foundation. All rights reserved. */ @@ -66,8 +67,7 @@ public static void main(String[] args) throws Exception { + getBalance(targetAddress).divide(UnitCKB).toString() + " CKB"); - String txHash = - sendCapacity(targetAddress, UnitCKB.multiply(BigInteger.valueOf(3000)), privateKeys); + String txHash = sendCapacity(targetAddress, Utils.ckbToShannon(3000), privateKeys); System.out.println("Transaction hash: " + txHash); Thread.sleep(30000); System.out.println( diff --git a/example/src/main/java/org/nervos/ckb/SendToMultiSigAddressTxExample.java b/example/src/main/java/org/nervos/ckb/SendToMultiSigAddressTxExample.java index 1c6a26380..d331eed7d 100644 --- a/example/src/main/java/org/nervos/ckb/SendToMultiSigAddressTxExample.java +++ b/example/src/main/java/org/nervos/ckb/SendToMultiSigAddressTxExample.java @@ -10,6 +10,7 @@ import org.nervos.ckb.transaction.*; import org.nervos.ckb.type.Witness; import org.nervos.ckb.type.cell.CellOutput; +import org.nervos.ckb.utils.Utils; /** Copyright © 2019 Nervos Foundation. All rights reserved. */ public class SendToMultiSigAddressTxExample { @@ -28,8 +29,7 @@ public class SendToMultiSigAddressTxExample { public static void main(String[] args) throws Exception { List receivers = - Collections.singletonList( - new Receiver(MultiSigAddress, new BigInteger("20000").multiply(UnitCKB))); + Collections.singletonList(new Receiver(MultiSigAddress, Utils.ckbToShannon(20000))); System.out.println( "Before transfer, miner's balance: " + getBalance().divide(UnitCKB).toString(10) + " CKB"); @@ -80,7 +80,7 @@ private static String sendCapacity(List receivers, String changeAddres // BigInteger feeRate = Numeric.toBigInt(api.estimateFeeRate("5").feeRate); BigInteger feeRate = BigInteger.valueOf(1024); - // // initial_length = 2 * secp256k1_signature_byte.length + // initial_length = 2 * secp256k1_signature_byte.length List cellsWithAddresses = txUtils.collectInputs( Collections.singletonList(MinerAddress), cellOutputs, feeRate, Sign.SIGN_LENGTH * 2); diff --git a/example/src/main/java/org/nervos/ckb/SingleKeySingleSigTxExample.java b/example/src/main/java/org/nervos/ckb/SingleKeySingleSigTxExample.java index 19a327da5..fa9748031 100644 --- a/example/src/main/java/org/nervos/ckb/SingleKeySingleSigTxExample.java +++ b/example/src/main/java/org/nervos/ckb/SingleKeySingleSigTxExample.java @@ -11,6 +11,7 @@ import org.nervos.ckb.transaction.*; import org.nervos.ckb.type.Witness; import org.nervos.ckb.type.cell.CellOutput; +import org.nervos.ckb.utils.Utils; /** Copyright © 2019 Nervos Foundation. All rights reserved. */ public class SingleKeySingleSigTxExample { @@ -34,8 +35,8 @@ public class SingleKeySingleSigTxExample { public static void main(String[] args) throws Exception { List receivers = Arrays.asList( - new Receiver(ReceiveAddresses.get(0), new BigInteger("8000").multiply(UnitCKB)), - new Receiver(ReceiveAddresses.get(1), new BigInteger("9000").multiply(UnitCKB))); + new Receiver(ReceiveAddresses.get(0), Utils.ckbToShannon(8000)), + new Receiver(ReceiveAddresses.get(1), Utils.ckbToShannon(9000))); System.out.println( "Before transfer, miner's balance: " @@ -81,7 +82,7 @@ private static String sendCapacity(List receivers, String changeAddres // BigInteger feeRate = Numeric.toBigInt(api.estimateFeeRate("5").feeRate); BigInteger feeRate = BigInteger.valueOf(1024); - // // initial_length = 2 * secp256k1_signature_byte.length + // initial_length = 2 * secp256k1_signature_byte.length List cellsWithAddresses = txUtils.collectInputs( Collections.singletonList(MinerAddress), cellOutputs, feeRate, Sign.SIGN_LENGTH * 2); diff --git a/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java b/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java new file mode 100644 index 000000000..0d19b4cea --- /dev/null +++ b/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java @@ -0,0 +1,7 @@ +package org.nervos.ckb; + +/** + * Copyright © 2019 Nervos Foundation. All rights reserved. + */ +public class SingleSigWithIndexerTxExample { +} diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java new file mode 100644 index 000000000..f7c5f75e9 --- /dev/null +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java @@ -0,0 +1,172 @@ +package org.nervos.ckb.transaction; + +import org.nervos.ckb.service.Api; +import org.nervos.ckb.system.SystemContract; +import org.nervos.ckb.system.type.SystemScriptCell; +import org.nervos.ckb.type.Witness; +import org.nervos.ckb.type.cell.*; +import org.nervos.ckb.type.transaction.Transaction; +import org.nervos.ckb.utils.Calculator; +import org.nervos.ckb.utils.Numeric; +import org.nervos.ckb.utils.Serializer; +import org.nervos.ckb.utils.address.AddressParseResult; +import org.nervos.ckb.utils.address.AddressParser; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.*; + +/** Copyright © 2019 Nervos Foundation. All rights reserved. */ +public class CellCollectorWithIndex { + + private static final String PAGE_SIZE = "50"; + + private Api api; + + public CellCollectorWithIndex(Api api) { + this.api = api; + } + + public Map> collectInputs( + List lockHashes, List cellOutputs, BigInteger feeRate, int initialLength) + throws IOException { + List cellOutputsData = new ArrayList<>(); + for (int i = 0; i < cellOutputs.size() - 1; i++) { + BigInteger size = cellOutputs.get(i).occupiedCapacity("0x"); + if (size.compareTo(Numeric.toBigInt(cellOutputs.get(i).capacity)) > 0) { + throw new IOException("Cell output byte size must not be bigger than capacity"); + } + cellOutputsData.add("0x"); + } + SystemScriptCell systemScriptCell = SystemContract.getSystemSecpCell(api); + cellOutputsData.add("0x"); + + Transaction transaction = + new Transaction( + "0", + Collections.singletonList(new CellDep(systemScriptCell.outPoint, CellDep.DEP_GROUP)), + Collections.emptyList(), + Collections.emptyList(), + cellOutputs, + cellOutputsData, + Collections.emptyList()); + + BigInteger inputsCapacity = BigInteger.ZERO; + List cellInputs = new ArrayList<>(); + Map> lockInputsMap = new HashMap<>(); + for (String lockHash : lockHashes) { + lockInputsMap.put(lockHash, new ArrayList<>()); + } + List witnesses = new ArrayList<>(); + + CellOutput changeOutput = cellOutputs.get(cellOutputs.size() - 1); + + BigInteger needCapacity = BigInteger.ZERO; + for (CellOutput cellOutput : cellOutputs) { + needCapacity = needCapacity.add(Numeric.toBigInt(cellOutput.capacity)); + } + List liveCells; + for (int index = 0; index < lockHashes.size(); index++) { + long tipBlockNumber = api.getTipBlockNumber().longValue(); + long pageNumber = 1; + + while (pageNumber <= tipBlockNumber + && inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { + liveCells = + api.getLiveCellsByLockHash( + lockHashes.get(index), String.valueOf(pageNumber), PAGE_SIZE, false); + for (LiveCell liveCell : liveCells) { + CellInput cellInput = new CellInput(liveCell.cellOutput.outPoint, "0x0"); + inputsCapacity = inputsCapacity.add(Numeric.toBigInt(cellOutputWithOutPoint.capacity)); + List cellInputList = lockInputsMap.get(lockHashes.get(index)); + cellInputList.add(cellInput); + cellInputs.add(cellInput); + witnesses.add("0x"); + transaction.inputs = cellInputs; + transaction.witnesses = witnesses; + BigInteger sumNeedCapacity = + needCapacity + .add(calculateTxFee(transaction, feeRate)) + .add(calculateOutputSize(changeOutput)); + if (inputsCapacity.compareTo(sumNeedCapacity) > 0) { + // update witness of group first element + int witnessIndex = 0; + for (String lockHash : lockHashes) { + if (lockInputsMap.get(lockHash).size() == 0) break; + witnesses.set(witnessIndex, new Witness(getZeros(initialLength))); + witnessIndex += lockInputsMap.get(lockHash).size(); + } + transaction.witnesses = witnesses; + // calculate sum need capacity again + sumNeedCapacity = + needCapacity + .add(calculateTxFee(transaction, feeRate)) + .add(calculateOutputSize(changeOutput)); + if (inputsCapacity.compareTo(sumNeedCapacity) > 0) { + // calculate change capacity again + changeOutput.capacity = + Numeric.prependHexPrefix( + inputsCapacity + .subtract(needCapacity) + .subtract(calculateTxFee(transaction, feeRate)) + .toString(16)); + cellOutputs.set(cellOutputs.size() - 1, changeOutput); + transaction.outputs = cellOutputs; + break; + } + } + } + fromBlockNumber = currentToBlockNumber + 1; + } + } + if (inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { + throw new IOException("Capacity not enough!"); + } + return lockInputsMap; + } + + private BigInteger calculateTxFee(Transaction transaction, BigInteger feeRate) { + int txSize = Serializer.serializeTransaction(transaction).toBytes().length; + return Calculator.calculateTransactionFee(BigInteger.valueOf(txSize), feeRate); + } + + public BigInteger getCapacityWithAddress(String address) throws IOException { + AddressParseResult addressParseResult = AddressParser.parse(address); + return getCapacityWithLockHash(addressParseResult.script.computeHash()); + } + + public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { + BigInteger capacity = BigInteger.ZERO; + long toBlockNumber = api.getTipBlockNumber().longValue(); + long fromBlockNumber = 1; + + while (fromBlockNumber <= toBlockNumber) { + long currentToBlockNumber = Math.min(fromBlockNumber + 100, toBlockNumber); + List cellOutputs = + api.getCellsByLockHash( + lockHash, + BigInteger.valueOf(fromBlockNumber).toString(), + BigInteger.valueOf(currentToBlockNumber).toString()); + + if (cellOutputs != null && cellOutputs.size() > 0) { + for (CellOutputWithOutPoint output : cellOutputs) { + capacity = capacity.add(Numeric.toBigInt(output.capacity)); + } + } + fromBlockNumber = currentToBlockNumber + 1; + } + return capacity; + } + + private BigInteger calculateOutputSize(CellOutput cellOutput) { + return BigInteger.valueOf(Serializer.serializeCellOutput(cellOutput).getLength()); + } + + private String getZeros(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append("0"); + } + return sb.toString(); + } +} From cdbe0a0a793033b34ddfcffaa267250ff864636d Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 15:00:57 +0800 Subject: [PATCH 04/11] feat: impl using indexer to collect cells and send transaction --- .../org/nervos/ckb/transaction/LockUtils.java | 6 +- .../org/nervos/ckb/type/cell/LiveCell.java | 1 + .../ckb/SingleSigWithIndexerTxExample.java | 123 +++++++++++++++++- .../transaction/CellCollectorWithIndexer.java | 59 +++++---- .../nervos/ckb/transaction/CollectUtils.java | 36 +++-- 5 files changed, 182 insertions(+), 43 deletions(-) diff --git a/ckb/src/main/java/org/nervos/ckb/transaction/LockUtils.java b/ckb/src/main/java/org/nervos/ckb/transaction/LockUtils.java index 2af9f7b44..818126e10 100644 --- a/ckb/src/main/java/org/nervos/ckb/transaction/LockUtils.java +++ b/ckb/src/main/java/org/nervos/ckb/transaction/LockUtils.java @@ -17,8 +17,12 @@ public static Script generateLockScriptWithPrivateKey(String privateKey, String return new Script(codeHash, blake160, Script.TYPE); } - public static Script generateLockScriptWithAddress(String address, String codeHash) { + public static Script generateLockScriptWithAddress(String address) { AddressParseResult addressParseResult = AddressParser.parse(address); return addressParseResult.script; } + + public static String generateLockHashWithAddress(String address) { + return generateLockScriptWithAddress(address).computeHash(); + } } diff --git a/ckb/src/main/java/org/nervos/ckb/type/cell/LiveCell.java b/ckb/src/main/java/org/nervos/ckb/type/cell/LiveCell.java index 9c5462002..b01943506 100644 --- a/ckb/src/main/java/org/nervos/ckb/type/cell/LiveCell.java +++ b/ckb/src/main/java/org/nervos/ckb/type/cell/LiveCell.java @@ -9,5 +9,6 @@ public class LiveCell { @SerializedName("created_by") public TransactionPoint createdBy; + @SerializedName("cell_output") public CellOutput cellOutput; } diff --git a/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java b/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java index 0d19b4cea..1f13f1b28 100644 --- a/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java +++ b/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java @@ -1,7 +1,124 @@ package org.nervos.ckb; -/** - * Copyright © 2019 Nervos Foundation. All rights reserved. - */ +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.nervos.ckb.crypto.secp256k1.Sign; +import org.nervos.ckb.service.Api; +import org.nervos.ckb.transaction.*; +import org.nervos.ckb.type.Witness; +import org.nervos.ckb.type.cell.CellOutput; +import org.nervos.ckb.utils.Utils; + +/** Copyright © 2019 Nervos Foundation. All rights reserved. */ public class SingleSigWithIndexerTxExample { + + private static final String NODE_URL = "http://localhost:8114"; + private static final BigInteger UnitCKB = new BigInteger("100000000"); + private static Api api; + private static List ReceiveAddresses; + private static String MinerPrivateKey = + "e79f3207ea4980b7fed79956d5934249ceac4751a4fae01a0f7c4a96884bc4e3"; + private static String MinerAddress = "ckt1qyqrdsefa43s6m882pcj53m4gdnj4k440axqswmu83"; + + static { + api = new Api(NODE_URL, false); + ReceiveAddresses = + Arrays.asList( + "ckt1qyqxgp7za7dajm5wzjkye52asc8fxvvqy9eqlhp82g", + "ckt1qyqtnz38fht9nvmrfdeunrhdtp29n0gagkps4duhek"); + } + + /** + * Note: If you want to use indexer to collect cell quickly, you should call index_lock_hash rpc + * firstly. CKB needs time to execute tagging to live cells, so we suggest that if you want to + * collect live cells, you should await some time after call index_lock_hash rpc. + */ + public static void main(String[] args) throws Exception { + + System.out.println("Call index_lock_hash rpc firstly"); + + // Call index_lock_hash rpc firstly before collecting live cells + // api.indexLockHash(LockUtils.generateLockHashWithAddress(MinerAddress), "0x0"); + // Wait some time for ckb to execute tagging to live cells + // Thread.sleep(20000); + + List receivers = + Arrays.asList( + new Receiver(ReceiveAddresses.get(0), Utils.ckbToShannon(8000)), + new Receiver(ReceiveAddresses.get(1), Utils.ckbToShannon(9000))); + + System.out.println( + "Before transfer, miner's balance: " + + getBalance(MinerAddress).divide(UnitCKB).toString(10) + + " CKB"); + + System.out.println( + "Before transfer, first receiver's balance: " + + getBalance(ReceiveAddresses.get(0)).divide(UnitCKB).toString(10) + + " CKB"); + + // miner send capacity to three receiver accounts with 800, 900 and 1000 CKB + String hash = sendCapacity(receivers, MinerAddress); + System.out.println("Transaction hash: " + hash); + Thread.sleep(30000); // waiting transaction into block, sometimes you should wait more seconds + + System.out.println( + "After transfer, receiver's balance: " + + getBalance(ReceiveAddresses.get(0)).divide(UnitCKB).toString(10) + + " CKB"); + } + + private static BigInteger getBalance(String address) throws IOException { + CellCollector cellCollector = new CellCollector(api); + return cellCollector.getCapacityWithAddress(address); + } + + private static String sendCapacity(List receivers, String changeAddress) + throws IOException { + BigInteger needCapacity = BigInteger.ZERO; + List scriptGroupWithPrivateKeysList = new ArrayList<>(); + for (Receiver receiver : receivers) { + needCapacity = needCapacity.add(receiver.capacity); + } + + TransactionBuilder txBuilder = new TransactionBuilder(api); + CollectUtils txUtils = new CollectUtils(api); + + List cellOutputs = txUtils.generateOutputs(receivers, changeAddress); + txBuilder.addOutputs(cellOutputs); + + // You can get fee rate by rpc or set a simple number + // BigInteger feeRate = Numeric.toBigInt(api.estimateFeeRate("5").feeRate); + BigInteger feeRate = BigInteger.valueOf(1024); + + // initial_length = 2 * secp256k1_signature_byte.length + // collectInputsWithIndexer method uses indexer rpc to collect cells quickly + List cellsWithAddresses = + txUtils.collectInputsWithIndexer( + Collections.singletonList(MinerAddress), cellOutputs, feeRate, Sign.SIGN_LENGTH * 2); + int startIndex = 0; + for (CellsWithAddress cellsWithAddress : cellsWithAddresses) { + txBuilder.addInputs(cellsWithAddress.inputs); + for (int i = 0; i < cellsWithAddress.inputs.size(); i++) { + txBuilder.addWitness(i == 0 ? new Witness(Witness.SIGNATURE_PLACEHOLDER) : "0x"); + } + scriptGroupWithPrivateKeysList.add( + new ScriptGroupWithPrivateKeys( + new ScriptGroup(NumberUtils.regionToList(startIndex, cellsWithAddress.inputs.size())), + Collections.singletonList(MinerPrivateKey))); + } + + Secp256k1SighashAllBuilder signBuilder = new Secp256k1SighashAllBuilder(txBuilder.buildTx()); + + for (ScriptGroupWithPrivateKeys scriptGroupWithPrivateKeys : scriptGroupWithPrivateKeysList) { + signBuilder.sign( + scriptGroupWithPrivateKeys.scriptGroup, scriptGroupWithPrivateKeys.privateKeys.get(0)); + } + + return api.sendTransaction(signBuilder.buildTx()); + } } diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java index f7c5f75e9..a1444da80 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java @@ -1,10 +1,17 @@ package org.nervos.ckb.transaction; +import java.io.IOException; +import java.math.BigInteger; +import java.util.*; import org.nervos.ckb.service.Api; import org.nervos.ckb.system.SystemContract; import org.nervos.ckb.system.type.SystemScriptCell; +import org.nervos.ckb.type.OutPoint; import org.nervos.ckb.type.Witness; -import org.nervos.ckb.type.cell.*; +import org.nervos.ckb.type.cell.CellDep; +import org.nervos.ckb.type.cell.CellInput; +import org.nervos.ckb.type.cell.CellOutput; +import org.nervos.ckb.type.cell.LiveCell; import org.nervos.ckb.type.transaction.Transaction; import org.nervos.ckb.utils.Calculator; import org.nervos.ckb.utils.Numeric; @@ -12,18 +19,14 @@ import org.nervos.ckb.utils.address.AddressParseResult; import org.nervos.ckb.utils.address.AddressParser; -import java.io.IOException; -import java.math.BigInteger; -import java.util.*; - /** Copyright © 2019 Nervos Foundation. All rights reserved. */ -public class CellCollectorWithIndex { +public class CellCollectorWithIndexer { - private static final String PAGE_SIZE = "50"; + private static final int PAGE_SIZE = 50; private Api api; - public CellCollectorWithIndex(Api api) { + public CellCollectorWithIndexer(Api api) { this.api = api; } @@ -74,10 +77,15 @@ public Map> collectInputs( && inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { liveCells = api.getLiveCellsByLockHash( - lockHashes.get(index), String.valueOf(pageNumber), PAGE_SIZE, false); + lockHashes.get(index), + String.valueOf(pageNumber), + String.valueOf(PAGE_SIZE), + false); for (LiveCell liveCell : liveCells) { - CellInput cellInput = new CellInput(liveCell.cellOutput.outPoint, "0x0"); - inputsCapacity = inputsCapacity.add(Numeric.toBigInt(cellOutputWithOutPoint.capacity)); + CellInput cellInput = + new CellInput( + new OutPoint(liveCell.createdBy.txHash, liveCell.createdBy.index), "0x0"); + inputsCapacity = inputsCapacity.add(Numeric.toBigInt(liveCell.cellOutput.capacity)); List cellInputList = lockInputsMap.get(lockHashes.get(index)); cellInputList.add(cellInput); cellInputs.add(cellInput); @@ -116,7 +124,7 @@ public Map> collectInputs( } } } - fromBlockNumber = currentToBlockNumber + 1; + pageNumber += PAGE_SIZE; } } if (inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { @@ -137,23 +145,20 @@ public BigInteger getCapacityWithAddress(String address) throws IOException { public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { BigInteger capacity = BigInteger.ZERO; - long toBlockNumber = api.getTipBlockNumber().longValue(); - long fromBlockNumber = 1; - - while (fromBlockNumber <= toBlockNumber) { - long currentToBlockNumber = Math.min(fromBlockNumber + 100, toBlockNumber); - List cellOutputs = - api.getCellsByLockHash( - lockHash, - BigInteger.valueOf(fromBlockNumber).toString(), - BigInteger.valueOf(currentToBlockNumber).toString()); - - if (cellOutputs != null && cellOutputs.size() > 0) { - for (CellOutputWithOutPoint output : cellOutputs) { - capacity = capacity.add(Numeric.toBigInt(output.capacity)); + long tipBlockNumber = api.getTipBlockNumber().longValue(); + long pageNumber = 1; + + while (pageNumber <= tipBlockNumber) { + List liveCells = + api.getLiveCellsByLockHash( + lockHash, String.valueOf(pageNumber), String.valueOf(PAGE_SIZE), false); + + if (liveCells != null && liveCells.size() > 0) { + for (LiveCell liveCell : liveCells) { + capacity = capacity.add(Numeric.toBigInt(liveCell.cellOutput.capacity)); } } - fromBlockNumber = currentToBlockNumber + 1; + pageNumber += PAGE_SIZE; } return capacity; } diff --git a/example/src/main/java/org/nervos/ckb/transaction/CollectUtils.java b/example/src/main/java/org/nervos/ckb/transaction/CollectUtils.java index 347b038d9..587df6749 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CollectUtils.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CollectUtils.java @@ -5,10 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.nervos.ckb.address.CodeHashType; import org.nervos.ckb.service.Api; -import org.nervos.ckb.system.SystemContract; -import org.nervos.ckb.system.type.SystemScriptCell; import org.nervos.ckb.type.cell.CellInput; import org.nervos.ckb.type.cell.CellOutput; import org.nervos.ckb.utils.Numeric; @@ -47,8 +44,31 @@ public List collectInputs( return cellsWithAddresses; } - public List generateOutputs(List receivers, String changeAddress) + public List collectInputsWithIndexer( + List sendAddresses, + List cellOutputs, + BigInteger feeRate, + int initialLength) throws IOException { + List cellsWithAddresses = new ArrayList<>(); + List lockHashes = new ArrayList<>(); + for (String address : sendAddresses) { + AddressParseResult addressParseResult = AddressParser.parse(address); + lockHashes.add(addressParseResult.script.computeHash()); + } + Map> lockInputMap = + new CellCollectorWithIndexer(api) + .collectInputs(lockHashes, cellOutputs, feeRate, initialLength); + + for (Map.Entry> entry : lockInputMap.entrySet()) { + cellsWithAddresses.add( + new CellsWithAddress( + entry.getValue(), sendAddresses.get(lockHashes.indexOf(entry.getKey())))); + } + return cellsWithAddresses; + } + + public List generateOutputs(List receivers, String changeAddress) { List cellOutputs = new ArrayList<>(); for (Receiver receiver : receivers) { AddressParseResult addressParseResult = AddressParser.parse(receiver.address); @@ -66,12 +86,4 @@ public List generateOutputs(List receivers, String changeA return cellOutputs; } - - private SystemScriptCell getSystemScriptCell(CodeHashType codeHashType) throws IOException { - if (codeHashType == CodeHashType.BLAKE160) { - return SystemContract.getSystemSecpCell(api); - } else { - return SystemContract.getSystemMultiSigCell(api); - } - } } From 9fb0689bd5bab218e0ad10b9cb946f50ee3de533 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 15:12:19 +0800 Subject: [PATCH 05/11] refactor: move getZeros to NumberUtils --- .../java/org/nervos/ckb/transaction/CellCollector.java | 10 +--------- .../ckb/transaction/CellCollectorWithIndexer.java | 10 +--------- .../java/org/nervos/ckb/transaction/NumberUtils.java | 8 ++++++++ 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollector.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollector.java index 7879f862d..3691bf2ec 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CellCollector.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollector.java @@ -96,7 +96,7 @@ public Map> collectInputs( int witnessIndex = 0; for (String lockHash : lockHashes) { if (lockInputsMap.get(lockHash).size() == 0) break; - witnesses.set(witnessIndex, new Witness(getZeros(initialLength))); + witnesses.set(witnessIndex, new Witness(NumberUtils.getZeros(initialLength))); witnessIndex += lockInputsMap.get(lockHash).size(); } transaction.witnesses = witnesses; @@ -164,12 +164,4 @@ public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { private BigInteger calculateOutputSize(CellOutput cellOutput) { return BigInteger.valueOf(Serializer.serializeCellOutput(cellOutput).getLength()); } - - private String getZeros(int length) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - sb.append("0"); - } - return sb.toString(); - } } diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java index a1444da80..d330a4054 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java @@ -101,7 +101,7 @@ public Map> collectInputs( int witnessIndex = 0; for (String lockHash : lockHashes) { if (lockInputsMap.get(lockHash).size() == 0) break; - witnesses.set(witnessIndex, new Witness(getZeros(initialLength))); + witnesses.set(witnessIndex, new Witness(NumberUtils.getZeros(initialLength))); witnessIndex += lockInputsMap.get(lockHash).size(); } transaction.witnesses = witnesses; @@ -166,12 +166,4 @@ public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { private BigInteger calculateOutputSize(CellOutput cellOutput) { return BigInteger.valueOf(Serializer.serializeCellOutput(cellOutput).getLength()); } - - private String getZeros(int length) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - sb.append("0"); - } - return sb.toString(); - } } diff --git a/example/src/main/java/org/nervos/ckb/transaction/NumberUtils.java b/example/src/main/java/org/nervos/ckb/transaction/NumberUtils.java index efb4b31af..57f83ea29 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/NumberUtils.java +++ b/example/src/main/java/org/nervos/ckb/transaction/NumberUtils.java @@ -13,4 +13,12 @@ public static List regionToList(int start, int length) { } return integers; } + + public static String getZeros(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append("0"); + } + return sb.toString(); + } } From 3903bcdfd0098840177a911b914e5d40c63a8171 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 15:57:28 +0800 Subject: [PATCH 06/11] fix: update cell collecting condition --- .../transaction/CellCollectorWithIndexer.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java index d330a4054..dfd4f4bf2 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java @@ -70,17 +70,16 @@ public Map> collectInputs( } List liveCells; for (int index = 0; index < lockHashes.size(); index++) { - long tipBlockNumber = api.getTipBlockNumber().longValue(); long pageNumber = 1; - while (pageNumber <= tipBlockNumber - && inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { + while (inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { liveCells = api.getLiveCellsByLockHash( lockHashes.get(index), String.valueOf(pageNumber), String.valueOf(PAGE_SIZE), false); + if (liveCells == null || liveCells.size() == 0) break; for (LiveCell liveCell : liveCells) { CellInput cellInput = new CellInput( @@ -145,18 +144,15 @@ public BigInteger getCapacityWithAddress(String address) throws IOException { public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { BigInteger capacity = BigInteger.ZERO; - long tipBlockNumber = api.getTipBlockNumber().longValue(); long pageNumber = 1; - while (pageNumber <= tipBlockNumber) { + while (true) { List liveCells = api.getLiveCellsByLockHash( lockHash, String.valueOf(pageNumber), String.valueOf(PAGE_SIZE), false); - - if (liveCells != null && liveCells.size() > 0) { - for (LiveCell liveCell : liveCells) { - capacity = capacity.add(Numeric.toBigInt(liveCell.cellOutput.capacity)); - } + if (liveCells == null || liveCells.size() == 0) break; + for (LiveCell liveCell : liveCells) { + capacity = capacity.add(Numeric.toBigInt(liveCell.cellOutput.capacity)); } pageNumber += PAGE_SIZE; } From eac67e38e5f141b0523b019554fadd1c666630ea Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 17:03:52 +0800 Subject: [PATCH 07/11] chore: use CellCollectorWithIndexer for indexer example --- .../nervos/ckb/SingleSigWithIndexerTxExample.java | 13 ++++--------- .../ckb/transaction/CellCollectorWithIndexer.java | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java b/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java index 1f13f1b28..9d57ecd7a 100644 --- a/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java +++ b/example/src/main/java/org/nervos/ckb/SingleSigWithIndexerTxExample.java @@ -56,24 +56,19 @@ public static void main(String[] args) throws Exception { + getBalance(MinerAddress).divide(UnitCKB).toString(10) + " CKB"); - System.out.println( - "Before transfer, first receiver's balance: " - + getBalance(ReceiveAddresses.get(0)).divide(UnitCKB).toString(10) - + " CKB"); - // miner send capacity to three receiver accounts with 800, 900 and 1000 CKB String hash = sendCapacity(receivers, MinerAddress); System.out.println("Transaction hash: " + hash); Thread.sleep(30000); // waiting transaction into block, sometimes you should wait more seconds System.out.println( - "After transfer, receiver's balance: " - + getBalance(ReceiveAddresses.get(0)).divide(UnitCKB).toString(10) + "After transfer, miner's balance: " + + getBalance(MinerAddress).divide(UnitCKB).toString(10) + " CKB"); } - private static BigInteger getBalance(String address) throws IOException { - CellCollector cellCollector = new CellCollector(api); + private static BigInteger getBalance(String address) throws Exception { + CellCollectorWithIndexer cellCollector = new CellCollectorWithIndexer(api); return cellCollector.getCapacityWithAddress(address); } diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java index dfd4f4bf2..ff477bc24 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java @@ -145,9 +145,9 @@ public BigInteger getCapacityWithAddress(String address) throws IOException { public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { BigInteger capacity = BigInteger.ZERO; long pageNumber = 1; - + List liveCells; while (true) { - List liveCells = + liveCells = api.getLiveCellsByLockHash( lockHash, String.valueOf(pageNumber), String.valueOf(PAGE_SIZE), false); if (liveCells == null || liveCells.size() == 0) break; From 6ca3f525620ad4be1c5ce47c15935116c82ff257 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 17:50:23 +0800 Subject: [PATCH 08/11] fix: update indexer page number --- .../nervos/ckb/transaction/CellCollectorWithIndexer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java index ff477bc24..b6b75dbb8 100644 --- a/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java +++ b/example/src/main/java/org/nervos/ckb/transaction/CellCollectorWithIndexer.java @@ -70,7 +70,7 @@ public Map> collectInputs( } List liveCells; for (int index = 0; index < lockHashes.size(); index++) { - long pageNumber = 1; + long pageNumber = 0; while (inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { liveCells = @@ -123,7 +123,7 @@ public Map> collectInputs( } } } - pageNumber += PAGE_SIZE; + pageNumber += 1; } } if (inputsCapacity.compareTo(needCapacity.add(calculateTxFee(transaction, feeRate))) < 0) { @@ -144,7 +144,7 @@ public BigInteger getCapacityWithAddress(String address) throws IOException { public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { BigInteger capacity = BigInteger.ZERO; - long pageNumber = 1; + long pageNumber = 0; List liveCells; while (true) { liveCells = @@ -154,7 +154,7 @@ public BigInteger getCapacityWithLockHash(String lockHash) throws IOException { for (LiveCell liveCell : liveCells) { capacity = capacity.add(Numeric.toBigInt(liveCell.cellOutput.capacity)); } - pageNumber += PAGE_SIZE; + pageNumber += 1; } return capacity; } From 6e4d3b9c2666976c1cc03e0bf078657f3a5b8213 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 18:05:39 +0800 Subject: [PATCH 09/11] docs: update changelog for v0.24.9 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9505c73a..8ce15ce2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [v0.24.9](https://github.com/nervosnetwork/ckb-sdk-java/compare/v0.24.8...v0.24.9) (2019-11-13) + +### Feature + +* Impl indexer to collect cells and send transaction [cdbe0a0](https://github.com/nervosnetwork/ckb-sdk-java/commit/cdbe0a0a793033b34ddfcffaa267250ff864636d)) + + # [v0.24.8](https://github.com/nervosnetwork/ckb-sdk-java/compare/v0.24.7...v0.24.8) (2019-11-13) ### BugFix From 39c728bb187955cafd817732967e9365a3ba8674 Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 18:06:25 +0800 Subject: [PATCH 10/11] chore: bump version to v0.24.9 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a5dbaa02c..86d5a88ba 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ allprojects { targetCompatibility = 1.8 group 'org.nervos.ckb' - version '0.24.8' + version '0.24.9' apply plugin: 'java' @@ -113,7 +113,7 @@ configure(subprojects.findAll { it.name != 'tests' }) { publications { mavenJava(MavenPublication) { groupId 'org.nervos.ckb' - version '0.24.8' + version '0.24.9' from components.java } } From fa0114f5e59361221f27256e327d10198582b44c Mon Sep 17 00:00:00 2001 From: duanyytop Date: Wed, 13 Nov 2019 18:15:41 +0800 Subject: [PATCH 11/11] docs: update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce15ce2f..304302eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ### Feature -* Impl indexer to collect cells and send transaction [cdbe0a0](https://github.com/nervosnetwork/ckb-sdk-java/commit/cdbe0a0a793033b34ddfcffaa267250ff864636d)) +* Impl indexer to collect cells and send transaction ([cdbe0a0](https://github.com/nervosnetwork/ckb-sdk-java/commit/cdbe0a0a793033b34ddfcffaa267250ff864636d)) # [v0.24.8](https://github.com/nervosnetwork/ckb-sdk-java/compare/v0.24.7...v0.24.8) (2019-11-13)